*****Capitan Fato: ultimo atto. =============================== Y era uno yuppy, che non amava la scuola; Z era una zuppa, dal sapore di suola. Probabilmente sarai contento di sapere che l'avventura di Capitan Fato è vicina alla sua conclusione. Ci sono ancora alcuni oggetti secondari da definire -il gabinetto, i vestiti dell'eroe e naturalmente il suo costume- ma cominciamo innanzitutto dal decorare meglio il nostro bar. ***Più guarnizioni culinarie ---------------------------- Non dobbiano dimenticare un paio di piccoli dettagli nella locazione del bar: Object cibo "Gli spuntini di Benny" bar with name 'pezzi' 'dolci' 'cibo' 'sandwich' 'pezzo' 'dolce' 'pasta' 'paste' 'spuntino' 'spuntini', before [; "Adesso non è il momento di pensare al CIBO."; ], has scenery proper; Object menu "menu" bar with name 'menu' 'lista' 'listino', description "Il menu appeso al muro elenca tutti i cibi e bevande che Benny può servire. Peccato che tu non abbia mai imparato a leggere, ma fortunatamente c'è il disegno di una grossa tazza di caffè fra tutte le altre scritte incomprensibili.", before [; Take: "Il menu è affisso al muro alle spalle di Benny. Inoltre è inutile SCRITTURA."; ] has scenery; Ed alcuni oggetti non così semplici: Object caffe "tazza di caffè" benny with name 'tazza' 'di' 'caffe' 'caffè' 'macchiato' 'cappuccino' 'capucino' 'cappucino' 'capuccino', initial "Sul bancone, il caffè fumante ti stà aspettando.", description [; if (self in benny) "Il disegno sul menù ha SICURAMENTE un bell'aspetto."; else "Aroma delizioso."; ], before [; Take,Drink,Taste: if (self in benny) "Forse dovresti ordinarne uno a Benny."; else { move self to benny; "Prendi la tazzina e ne bevi un sorso. La REPUTAZIONE MONDIALE di Benny è ben meritata. Appena finisci, Benny porta via la tazzina. ~Il caffè viene un'euro, signore.~"; } Buy: if (moneta in player) <>; else "Non hai soldi."; Smell: "Se la tua IPERATTIVA ghiandola pituitaria è affidabile, è una miscela colombiana."; ] has female; Non vi è nulla di veramente nuovo in questo oggetto (oltre al fatto che la proprietà *name* sorvoli su alcuni errori di ortografia del giocatore), ma nota come non abbiamo *rimosso* il caffè dopo che il giocatore lo ha bevuto. Per un apparentemente assurdo capriccio, il caffè ritorna magicamente nelle mani di Benny (sebbene questa non sia una informazione che il giocatore debba sapere). Perchè? Ti chiederai... E' presto detto. Dopo che si è rimosso un oggetto dal gioco, se il giocatore tenta di ESAMINARLO, l'interprete dirà, poco pertinentemente, "Non vedi nulla del genere". Per di più, se il giocatore chiede un secondo caffè a Benny, una volta che il primo sia rimosso, Benny risponderà "Non credo sia sul menù, signore" - una sfacciata bugia - ossia la risposta di default della proprietà *orders* di Benny. Dal momento che l'oggetto caffè rimosso non appartiene più a Benny, non è più un un sostantivo che il giocatore gli può CHIEDERE. Mentre facendolo tornare un oggetto figlio (child) del barista (che ha impostato l'attributo *trasparent*), il caffè rimane ancora un oggetto a cui il giocatore può fare riferimento. Ci assicureremo, poi, che il giocatore non possa ordinare più tazze di caffè grazie alla proprietà *chiesto_caffe* di Benny, che rimarrà vera dopo la prima ordinazione. ***Gabinetto o spogliatoio? --------------------------- Propenderemo certamente per la seconda definizione, visto che, al momento, è l'unico posto al riparo da sguardi curiosi dove il nostro eroe potrà trasformarsi da uomo deboluccio in nemesi di ogni essere maligno. E non è *proprio* il caso di farsi troppi scrupoli e di impantanarci nei dettagli sulle reali funzioni di una stanza. Non c'è molto da dire sulla locazione gabinetto e su i suoi contenuti, sebbene vi siano alcuni effetti speciali: Room gabinetto "Un gabinetto unisex" with description "Una stanza quadrata, incredibilmente PULITA, dalle pareti ricoperte di mattonelle di ceramica, che non contiene molto di più di un gabinetto e un interruttore. L'unica uscita è a sud, attraverso la porta che riconduce al bar.", s_to porta_del_gabinetto, has ~light scored; Appliance cesso "gabinetto" gabinetto with name 'gabinetto' 'wc' 'cesso' 'water' 'water-closed' 'tazza', before [; Examine: if (moneta in self) { move moneta to parent(self); "L'ultima persona ha CIVILMENTE tirato l'acqua dopo aver usato il gabinetto, ma ha dimenticato di raccogliere la PREZIOSA moneta che è caduta dai suoi pantaloni."; } ]; Object moneta "moneta" gabinetto with name 'moneta' 'euro' 'soldi', description "E' una moneta da un EURO.", before [; Drop: "Lasciare una moneta tanto preziosa? Ha, ha! Questa deve essere una dimostrazione del tuo ULTRA-FRIVOLO senso dell'umorismo!"; ], after [; Take: "Ti accosci nella posizione del DRAGO DORMIENTE e senza indugio raccogli e intaschi la moneta con la PRESA SOVRANA."; ], has scored female; Inizialmente abbiamo posto la moneta come oggetto figlio (child) del gabinetto (così che si possa facilmente verificare *if(coin in self)*). Dal momento che non abbiamo impostato l'attributo trasparent per il gabinetto, la moneta non sarà visibile al giocatore fino a quando non tenterà di ESAMINARE il gabinetto, azione che sposterà la moneta nella locazione gabinetto. Una volta raccolta, la moneta rimarrà nell'inventario fino a quando il giocatore la darà a Benny, visto che abbiamo bloccato l'azione POSA (drop) MONETA per aiutare il giocatore a FARE la Cosa Giusta. L'oggetto gabinetto include una serie di utili sinonimi nella sua proprietà *name*, inclusa la nostra parola preferita 'gabinetto'. Ciò non costituirà un problema: gli altri oggetti qui presenti che possono avere la parola GABINETTO nel loro nome -ovvero, la chiave e la porta- usano entrambi la propretà *pname* che pone per loro l'uso della parola 'gabinetto' come aggettivo di importanza secondaria. Nota che qui sono presenti gli unici due attributi *scored* del gioco. Il giocatore guadagnerà un punto entrando nella locazione gabinetto, e un'altro punto trovando e raccogliendo la moneta. Potresti anche aver notato che abbiamo forzatamente eliminato l'attributo *light*, ereditato dalla classe *Room*. Ci troviamo infatti in una stanza senza finestre e, per aggiungere un tocco di realismo, è il caso di rendere il gabinetto una locazione priva di luce, il che ci permetterà di parlarti del comportamento di default di Inform nel caso non vi sia una sorgente di luce in una locazione. Comunque, andiamo prima a definire l'interruttore della luce menzionato nella descrizione della stanza per aiutare il giocatore nel suo cambio d'abito. Appliance interruttore "interruttore" gabinetto with name 'interruttore', description "Un notevole PRODIGIO di tecnologia e SCIENZA, elegante e FACILE da usare.", before [; Push: if (self has on) <>; else <>; ], after [; SwitchOn: give self light; "Accendi la luce del gabinetto."; SwitchOff: give self ~light; "Spegni la luce del gabinetto."; ], has switchable ~on; Per favore, nota l'inserimento dei nuovi attributi *switchable* e *on*. *switchable* permette all'oggetto di essere acceso o spento, ed è un attributo tipico di lanterne, calcolatori, televisioni, radio, e così via. La libreria automaticamente completa la descrizione di questi oggetti indicando se al momento sono accesi o spenti: > X INTERRUTTORE DELLA LUCE Un noto PRODOTTO della tecnologia MODERNA, elegante e FACILE da usare. L'interruttore della luce è al momento acceso. A questo punto vi sono due nuove azioni pronte per l'uso, *SwitchOn* (accendi) e *SwitchOff* (spegni). Laciate a se stesse, esse modificano lo stato dell'oggetto portandolo da ACCESO a SPENTO o viceversa e visualizzando un messagio del tipo: Hai acceso la lanterna. Esse su preoccupano anche di controllare se il giocatore si sbaglia e prova ad accendere (o spegnere) un oggetto che è già acceso (o spento). Come fa la libreria a sapere lo stato di un oggetto? Ciò avviene grazie all'attributo *on*, che viene impostato o cancellato automaticamente secondo la necessità. Puoi, naturalmente, impostarlo o cancellarlo manualmente, come ogni altro attributo, con l'istruzione *give*: give self on; give self ~on; e controllare se un occetto *switchable* è acceso o spento con il test: if (interruttore has on)... if (interruttore hasnt on)... Un oggetto *switchable* di default è SPENTO. Comunque, potrai vedere che la line *has* della definizione dell'oggetto include ~on: has switchable ~on; Sei sicuro che questa istruzione stia dicendo "non-acceso"? Sei sicuro che non sarebbe accaduto lo stesso se la linea non avesse menzionato del tutto l'attributo? has switchable; Bene, hai perfettamente ragione. Aggiungere l'attributo ~on non ha alcun effetto sul gioco, ma ciò nonostante è una buona idea. E' un promemoria, un modo per ricordarci che iniziamo il gioco con questo attributo disattivato, e che in qualche punto dovremo impostarlo per i nostri scopi. Credici: vale la pena approfittare di queste piccole opportunità di aiutare te stesso. Andiamo a vedere come funziona il nostro interruttore. Intercettiamo le azioni *SwitchOn* e *SwitchOff* nella proprietà *after* (quando oramai l'accensione o spegnimento dell'interruttore ha fatto effetto) e usiamole per dare *luce* (attributo *light*) all'interruttore. Uh, aspetta. Luce all'interruttore? Perchè non invece alla locazione gabinetto? Bene, c'è una ragione e la vedremo fra un minuto. Per adesso, ricorda solo che, per far si che il giocatore possa vedere ciò che lo circonda, basta che vi sia un oggetto nella locazione con l'attributo *light* impostato. Non c'è bisogno che sia la locazione stessa (anche se normalmente è conveniente che lo sia). Dopo avere impostato l'attributo *light*, visualizziamo un messaggio a schermo, per evitare quello di default: Hai acceso l'interrutore. che non è proprio elegante. Il giocatore potrebbe provare anche a PREMERE l'INTERRUTTORE, così abbiamo intercettato questo tentativo nella proprietà *before* e lo abbiamo ridirezionato verso le azioni *SwitchOn* e *SwitchOff*, controllando prima che sia necessario testando l'attributo *on*. Infine, abbiamo reso l'interruttore un membro della classe *Appliance*, così il giocatore non potrà portarselo via. NOTA: ricordi cosa abbiamo detto a proposito dell'ereditarietà delle classi? Non importa cosa defisci nella classe, la definizione dell'oggetto ha la priorità. La classe *Appliance* definisce una risposta per l'azione *Push* (premi), ma noi la sovrascriviamo qui con un nuovo comportamento. ***E fu la luce --------------- Il giocatore entra nel gabinetto e Oscurità E' completamente buio, non riesci a vedere niente. Oops! nessuna descrizione del gabinetto, nessun riferimento all'interruttore della luce, niente. E' però ragionevole pensare che se abbiamo aperto la porta del gabinetto per accedervi, un po' di luce del bar filtri attraverso l'uscio illuminando il bagno - almeno fino a quando il giocatore non deciderà di chiedere la porta del gabineto. Così potrebbe essere una buona idea aggiungere qualche linea di codice all'oggetto porta per tenere conto di questa situazione. Un paio di istruzioni nella proprietà *after* saranno suficienti: after [ ks; Unlock: if (self has locked) return false; print "Apri ", (the) self, ".^"; ks = keep_silent; keep_silent = true; ; keep_silent = ks; return true; Open: give gabinetto light; Close: give gabinetto ~light; ], E questa è la ragione per cui l'interruttore della luce non imposta l'attributo *light* sulla locazione gabinetto, ma lo fa su se stesso. Avremmo qualche difficoltà se permettessimo allo stato aperto/chiuso della porta di controllare la luce dell'oggetto locazione gabinetto, e allo stato acceso/spento dell'interruttore la luce dell'interruttore stesso. Così è meglio avere un unico interruttore per la luce che fa tutto. Fortunatamente, il giocatore non si accorgerà mai di questo artifizio. NOTA: potrebbe accorgersene in qualche modo? Beh, se il giocatore potesse PRENDERE l'interruttore della luce (cosa che abbiamo evitato), dando il comando INVENTARIO, il trucco verrebbe svelato, visto che ogni sorgente luce viene contrassegnata dalla libreria con le parole (acceso). Il giocatore entra nel gabinetto e Un gabinetto unisex. Una stanza quadrata, incredibilmente PULITA, dalle pareti ricoperte di mattonelle di ceramica, che non contiene molto di più di un gabinetto e un interruttore. L'unica uscita è a sud, attraverso la porta che riconduce al bar. [Il tuo punteggio è appena aumentato di un punto.] Meglio. Ora, supponi che il giocatore chiuda la porta. >CHIUDI PORTA Ora hai chiuso la porta che conduce al bar. E' completamente buio qui! >L Oscurità È completamente buio, e non riesci a vedere niente. Si, nessun problema. Abbiamo già detto che c'è un interruttore della luce. Sicuramente il giocatore ora proverà: >ACCENDI L'INTERRUTTORE Non vedi nulla del genere. Oops! Le cosè si fanno complicate al buio. Probabilmente è il momento di lasciare questo posto e provare un altro approccio: >APRI LA PORTA Non vedi nulla del genere. E ciò mostra una di quelle cose terribili che accadono quando in un gioco si cade in una locazione buia. Non puoi vedere nulla e puoi fare veramente poco in questa situazione. Tutti gli oggetti, con l'eccezione di quelli presenti nel tuo inventario sono fuori portata (scope), irraggiungibili, come se non esistessero. Peggio ancora, se POSI un oggetto tra quelli che stai portando, esso sarà irremidiabilmente perso nel buio, introvabile fin quando non tornerà la luce. Il giocatore che senza dubbio è immerso nella fantasia del gioco, sarà ora un poco interdetto. "Sono in un piccolo bagno, e non posso raggiungere la porta che ho appena chiuso?" Il giocatore ha ragione, naturalmente (nota n°1: Stiamo alludendo qui al classico concetto di *mimesi*. In un articolo scritto nel 1996, Roger Giner-Sorolla ha scritto: "Considero la narrativa ben fatta un’imitazione, o una “mimesi”, della realtà, sia che si svolga in questo mondo che in un altro. La buona narrativa permette al lettore di entrare, temporaneamente, nella realtà di quel mondo e di crederci. Un crimine contro la mimesi è ogni aspetto di un gioco di IF che spezza la coerenza di questo mondo fittizio, di questa rappresentazione di una realtà." Il testo completo dell'articolo tradotto in italiano può essere trovato su http://www.avventuretestuali.com). Le locazioni buie sono un classico dei giochi tradizionali. Solitamente ci si sposta in una direzione mentre si sta cercando un tesoro in una qualche sorta di caverna, e improvvisamente si arriva in un antro oscuro. E' considerato un buon comportamento da parte dell'avventura disabilitare l'esplorazione di un territorio buio e sconosciuto, ed è una convenzione sbarrare il passaggio al giocatore fino a quando non torna con una sorgente di luce. Comunque, se lo scenario del gioco prevede, supponiamo, la casa del pesonaggio giocatore, un piccolo appartamento di due stanze, e non c'è luce nella cucina, dovremmo aspettarci che il proprietario della casa sappia muoversi lo stesso al suo interno, forse anche solo per raggiungere un interruttore o che riesca a raggiungere il frigorifero anche al buio. In questo caso siamo in una situazione simile. La logica del gioco richiede che un giocatore al buio dovrebbe essere in grado di aprire la porta e probabilmente di accendere un interruttore della luce che ha appena incontrato. Abbiamo detto che un oggetto è a portata (scope) quando è nella medesima locazione del giocatore. L'oscurità cambia questa regola. Tutti gli oggetti non trasportati direttamente dal giocatore sono fuori portata (out of scope). Uno dei vantaggi di un sistema di programmazione avanzato come Inform è la flessibilità di cambiare tutti i comportamenti di default per venire incontro alle esigenze particolari. Anche qui il problema non è differente. C'è una serie di routine e funzioni atte a metter mano su cosa sia a portata (scope) del giocatore e quando. Vedremo solo un piccolo esempio che ci aiuterà a risolvere il nostro problema. Nella sezione "*entry point routines*" del nostro gioco -dopo la routine *Initialise*, per esempio- includeremo le seguenti linee di codice: [ InScope person; if (person == player && location == thedark && real_location == gabinetto) { PlaceInScope(interruttore); PlaceInScope(porta_del_gabinetto); } return false; ]; *InScope(oggetto_attore_id)* è una routine punto d'entrata che permette di intervenire sulle regole che mettono alla portata dell'oggetto_attore_id (personaggio giocatore o un qualsiasi personaggio non giocatore) gli altri oggetti. La definiamo con una variabile (il cui nome può essere scelto a piacere, ma è una buona idea scegliere un nome che ci ricordi cosa rappresenta la variabile), *person*, e quindi eseguiamo una verifica per vedere se il giocatore è contemporaneamente nel gabinetto ed al buio. Ti abbiamo già accennato al fatto che la variabile *location* contiene il valore corrente della locazione in cui il giocatore si trova. Quando, però, non c'è luce, la variabile *location* assume il valore dello speciale oggetto della libreria chiamato *thedark* (oscurità). Non importa se abbiamo 10 stanze buie nel nostro gioco; *location* assumerà in tutti i casi il valore *thedark*. C'è un'altra variabile, chiamata real_location, che contiene il valore della locazione in cui è il giocatore *anche quando non vi sia luce per vedere*. Pertanto la verifica: if (person == player && location == thedark && real_location == gabinetto) .... sta controllando: se l'attore specificato è il personaggo giocatore *e* se si trova al buio *e* se al momento sia per caso nel gabinetto... In seguito richiamiamo una delle routine della libreria, *PlaceInScope(oggetto_id)* che possiede un nome piuttosto chiaro: essa pone alla portata del giocatore l'oggetto specificato. Nel nostro caso, vogliamo che la porta e l'interruttore della luce siano entrambi raggiungibili dal giocatore, ecco spiegate le altre due linee di codice. Infine, dobbiamo restituire il valore *false*, poichè abbiamo bisogno che le normali regole sulla portata degli oggetti si applichino per l'attore definito - il giocatore - al resto degli oggetti del gioco (se avessimo restituito il valore *true*, il giocatore avrebbe scoperto che poteva interagire con qualsiasi piccolo dettaglio definito. Ora abbiamo un risultato più logico e piacevole: Oscurità È completamente buio, e non riesci a vedere niente. >ACCENDI L'INTERRUTTORE Accendi la luce del bagno. Un bagno unisex Una stanza quadrata, incredibilmente PULITA, dalle pareti ricoperte di mattonelle di ceramica, che non contiene molto di più di un gabinetto e un interruttore. L'unica uscita è a sud, attraverso la porta che riconduce al bar. E lo stesso accadrà per la porta. Nota come la descrizione della locazione venga visualizzata dopo il passaggio dal buio alla luce; questo è il comportamente normale della libreria. C'e ancora un ultimo problema che, ammettiamolo, potrebbe originarsi da una improbabile, ma non impossibile, serie di azioni. Supponiamo che il giocatore entri nella locazione, chiuda la porta a chiave - il che è possibile al buio ora che abbiamo messo la porta alla portata (scope) del giocatore - e che quindi posi la chiave. Non c'è modo di uscire dal gabinetto e la chiave è scomparsa, inghiottita dall'oscurità- se il giocatore non pensa alla possibilità di accendere l'interruttore della luce, facendo tornare la chiave alla sua portata. Perchè non aggiungere un *PlaceInScope(chiave_del_gabinetto)* alle precedenti routine? Beh, per cominciare, la chiave può essere mossa e trasportata (a differenza della porta e dell'interruttore della luce, che sono invece oggetti fissi al loro posto nella locazione del gabinetto). Supponi che il giocatore apra la porta del gabinetto, ma posi la chiave all'interno del bar, quindi entri nel gabinetto e chiuda la porta. La condizione viene soddisfatta e la chiave viene posta alla portata del giocatore,anche se era in un'altra locazione. Come secondo motivo, questo è un gioco semplice con solo pochi oggetti, quindi si può definire una regola per ognuno di essi; ma in quasiasi gioco più corposo, potresti trovarti alle prese con un notevole quantità di oggetti, con la necessità di creare una regola generale che si applichi ad ognuno (o ad alcuni) di essi. Abbiamo pertanto bisogno di aggiungere del codice nella routine *InScope* per dire al gioco di mettere alla portata del giocatore tutti gli oggetto che egli posa nel buio, così che si possano recuperare (magari mettendosi a quattro zampe e andando a tentoni). Non vogliamo che il giocatore abbia altri oggetti alla sua portata (come la moneta, per esempio), così potrebbe essere una buona idea avere un modo di verificare se l'oggetto è stato manipolato o trasportato dal giocatore. L'attributo *moved* si adatta perfettamente a questo scopo. La libreria lo imposta per ogni oggetto che il giocatore ha raccolto almeno una volta durante il gioco; oggetti con l'attributo *scenery* o *static*, o quelli che non sono ancora stati visti non possiedono l'attributo *moved*. Ecco qui la rielaborazione della routine *InScope*. Ci sono un paio di nuovi concetti da approfondire: [ InScope person item; if (person == player && location == thedark && real_location == gabinetto) { PlaceInScope(interruttore); PlaceInScope(porta_del_gabinetto); } if (person == player && location == thedark) objectloop (item in parent(player)) if (item has moved) PlaceInScope(item); return false; ]; Abbiamo aggiunto una seconda variabile locale, *item* -ancora una volta, questa è una variabile che abbiamo creato e nominato per conto nostro; non è parte della libreria. Facciamo ora una nuova verifica: se l'attore è il giocatore e la locazione è una qualsiasi locazione buia, esegui una determinata azione. Non abbiamo bisogno di specificare la locazione gabinetto, visto che vogliamo che questa regola si applichi a tutte le locazioni buie (probabilmente ti sarai accorto che il gabinetto *è* l'unica locazione buia del gioco, ma stiamo cercando di usare regole generali). objectloop (variabile) istruzione; è una istruzione ciclica, una delle quattro definite in Inform. Un ciclo è un costrutto che permette di eseguire diverse volte una istruzione (o un blocco di istruzioni). *objectloop* esegue *l'istruzione* una volta per ogni oggetto definito nella *(variabile)*. Se dovessimo codificarlo: objectloop (item) istruzione; quindi l'istruzione verrebbe eseguita una volta per ogni oggetto nel gioco. Naturalmente, noi vogliamo eseguire l'istruzione solo per quegli oggetti il cui genitore oggetto (parent) è lo stesso dell'oggetto genitore (parent) del giocatore: ovvero, per gli oggetti nella stessa locazione del giocatore, quindi il codice sarà: objectloop (item in parent(player)) istruzione; Quali sono le *istruzioni* che vogliamo vedere eseguite ripetutamente? if (item has moved) PlaceInScope(item); La verifica: *if (item has moved)* assicura che *PlaceInScope(item)* si relazioni solo con gli oggetti che possiedono l'attributo *moved*. Così: se il giocatore è al buio, la condizione è verificata per ogni oggetto che è nella medesima locazione, uno alla volta. Per ognuno di essi, controlla se è un oggetto che è stato mai trasportato dal giocatore, nel qual caso, lo pone alla sua portata. Tutti gli oggetti posati all'interno della locazione sono stati trasportati almeno una volta, così lasceremo al giocatore la possibilità di recuperarli anche se non può vederli. Come vedi, l'oscurità è in parte rognosa da implementare. Se pensi di utilizzare delle locazioni buie nel tuo gioco, tieni a mente che dovrai cimentarti con un codice abbastanza elaborato (a meno che tu non mantenga le regole della libreria, nel qual caso il giocatore non potrà fare un gran che). ***Un fantastico costumino con effetti speciali ----------------------------------------------- Pensiamo come prima cosa ai nostri vestiti, che richiedono alcune azioni fatte su misura. Ecco gli indumenti indossati normalmente da John Covarth: Object vestiti "i tuoi vestiti" with name 'vestiti' 'vestito' 'ordinari' 'abiti' 'abito', description "Vestiti perfettamente ORDINARI per un NESSUNO come John Covarth.", before [; Disrobe,Change: switch (location) { strada: if (player in cabina) "Non avendo la super-velocità di Superman, realizzi che sarebbe sconveniente cambiarti sotto gli occhi delle persone che passano."; else "In mezzo di strada? Questo sarebbe uno SCANDALO, e inoltre rivelerebbe la tua identità segreta."; bar: "Benny non sopporta le buffonate nel suo locale."; gabinetto: if (porta_del_gabinetto has open) "La porta rimane aperta, e ci sono decine di occhi curiosi. Dovresti arrestarti da solo per condotta IMMORALE."; print "Ti togli rapidamente i vestiti e li raccogni in un pacco ULTRA-MINUSCOLO facilmente trasportabile. "; if (porta_del_gabinetto has locked) { give vestiti ~worn; give costume worn; "Poi spiehi il tuo costume in COTONE INVULNERABILE e ti trasformi in Capitan FATO, difensore della libertà e avversario della tirannia!"; } else { deadflag = 3; "Stai per infilarti il costume di Capitan FATO, quando la porta si apre e una giovane donna entra. LEI ti guarda e inizia ad urlare, ~UNO STUPRATORE. UNO STUPRATORE NUDO NEL BAGNO!!!~^^ Tutti coloro che erano nel bar giungono in soccorso, solo per vederti saltare in modo ridicolo su una gamba sola mentre cerchi di vestirti. Le loro risate segnano la RAPIDA FINE della tua carriera di combattente del crimine!"; } thedark: "L'ultima volta che ti sei cambiato al buio, hai indossato il costume al contrario!"; } Wear: if (self has worn) "Sei già vestito come John Covarth."; "La città ha BISOGNO dei poteri di capitan FATO, non dell'anonimato di John Covarth."; ], has clothing proper pluralname; Osserva come l'oggetto si relazioni solo con le azioni *Disrobe* (togli), *Change*(cambiati) e *wear*(indossa). *Disrobe* e *wear* sono azioni standard della libreria, già definite in Inform, mentre invece dovremo creare un nuovo verbo per permettere il comando CAMBIATI I VESTITI. In questo gioco, *Disrobe* e *Change* sono considerati sinonimi in ogni caso. L'obiettivo del giocatore è cambiarsi i vestiti, così potremmo aspettarci che egli provi a farlo un po' ovunque. Il compito dell'istruzione *switch* è di offrire risposte diverse a seconda del valore della variabile *location*. Se l'azione viene comandata nella strada (dentro o fuori della cabina) o nel bar allora viene visualizzato un qualche messaggio di rifiuto, solo quando il giocatore riuscirà ad entrare nel gabinetto, e ci si sarà chiuso dentro a chiave, riuscirà a cabiarsi d'abito con successo. Se la porta è chiusa, ma non a chiave, il giocatore viene interrotto mentre è mezzo nudo da una donna nervosa che comincia a bussare, e la partita è persa (la situazione per il giocatore non è così tragica come sembra, infatti egli potrà sempre tornare alla situazione precedente l'ultima mossa attraverso il comando UNDO). Se la porta è chiusa a chiave, invece, la trasformazione riesce (in questo caso togliamo l'attributo *wear* dai *vestiti* e lo diamo al *costume*). Abbiamo aggiunto anche un rifiuto speciale a cambiarsi d'abito al buio, forzando il giocatore ad accendere la luce e quindi, speriamo, a trovare la moneta. L'azione *wear* si limita a controllare se i vestiti sono già stati indossati, per offrire due tipi di risposte: l'obiettivo del gioco è quello di cambiarsi con il nostro costume da eroe, dopo di che impedire al giocatore di ritornare a vestire i suoi panni abituali. In questo modo alla fine abbiamo per le mani il nostro Capitan Fato in tutta la stupefacenza del suo costume: Object costume "il tuo costume" with name 'capitan' 'fato' 'costume' 'tuta', description "Manifattura allo STATO DELL'ARTE, 100% COTONELASTICO(tm) rinforzato chimicamente.", before [; Wear: if (vestiti has worn) "Prima dovresti toglierti gli ordinari e ANONIMI vestiti di John Covarth."; Disrobe,Change: if (vestiti has worn) "Non lo stai indossando!"; else "Hai BISOGNO del tuo costume per combattere il crimine!"; Drop: "Il tuo UNICO costume multicolore da Capitan FATO? Il più desiderato capo d'abbigliamento in tutta la città? Certamente NO!"; ], has clothing proper; Nota che abbiamo intercettato l'azione INDOSSA COSTUME e suggerito al giocatore di provare invece TOGLI I VESTITI. Inoltre non permetteremo al giocatore di togliersi il costume una volta che lo abbia indossato, e certamente gli impediremo di lasciarlo da qualche parte, rifiutando il comando *Drop* (posa). ***Incartiamo il regalo ----------------------- Ci siamo quasi; uncora un paio di piccoli ritocchi ed avremo finito. **La routine Initialise Ora che tutti gli oggetti del nostro gioco sono stati definiti, dobbiamo aggiungere un paio di linee di codice alla routine *Initialise* per essere sicuri che il giocatore non cominci il gioco nudo: [ Initialise; #Ifdef DEBUG; pname_verify(); #Endif; ! suggerito da pname.h location = strada; move costume to player; move vestiti to player; give vestiti worn; lookmode = 2; "^^Impersonando il tranquillo John Covarth, assistente garzone in una insignificante drogheria, ti FERMI di colpo quando il tuo udito finissimo decifra una chiamata radio della POLIZIA. Un FOLLE stà attaccando la popolazione al Parco Granaio! Devi indossare velocemente il tuo costume da Capitan FATO...!^^"; ]; Ricordi che abbiamo incluso la libreria aggiuntiva, *pname*, per eliminare la canfusione tra i nomi? Ci sono alcuni commenti aggiuntivi nel file di testo che la accompagna che devono essere presi in considerazione: pname.h prevede una routine pname_verify. Quando la modalità DEBUG è attiva, si può richiamare pname_verify() nella routine Initialise() per verificare le proprietà pname del tuo oggetto. L'autore della libreria aggiuntiva ha creato uno strumento di debug (una routine) per controllare i possibi errori nell'uso della libreria stessa, e ci raccomanda di usarlo. Quindi includiamo le linee di codice suggerite nella nostra routine *Initialise*: #Ifdef DEBUG; pname_verify(); #Endif; ! suggerito da pname.h Come spiega il testo, ciò che fa questo codice è: innanzitutto controllare se il gioco è stato compilato con la modalità Debug attiva (i giochi vengono compilati di default in modalità Stretta, che include il Debug); in tal caso, eseguire la routine *pname_verify*, verificando cosìche tutte le proprietà *pname* siano scritte correttamente. **La morte del nostro eroe Abbia creato tre possibili conclusioni in questo gioco: 1. Il giocatore cerca di cambiarsi nel gabinetto senza che la porta sia chiusa a chiave. 2. Il giocatore prova a colpire Benny mentre sta indossando il costume. 3. Il giocatore riesce ad uscire dal bar vestito da Capitan Fato. Le conclusioni numero (1) e (2) corrispondono a una partita persa, mentre la (3) è la soluzione vincente del gioco. La libreria di default prevede per le partite perse o vinte la visualizzazione di due messaggi, rispettivamente, *** Sei morto *** *** Hai vinto *** Questi messaggi corrispondono ai valori della variabile *deadflag*: 1 per la sconfitta, 2 per la vittoria. Naturalmente è possibile cambiare tali messaggi, come nel nostro caso, visto che il nostro eroe in realtà non muore veramente, ma incappa in un DESTINO ben peggiore della morte stessa. Intendiamo ,infatti, dargli un messaggio un poco più dettagliato sulla conclusione della partita. Dobbiamo, perciò, definire una routine *DeathMessage* come abbiamo fatto nel gioco di "Guglielmo Tell", per scrivere i nostri messaggi personalizzati ed assegnar loro un valore di *deathflag* maggiore di 2. [ DeathMessage; if (deadflag == 3) print "La tua identità segreta è stata rivelata."; if (deadflag == 4) print "Sei stato VERGOGNOSAMENTE sconfitto."; if (deadflag == 5) print "Voli via, diretto a RISOLVERE la SITUAZIONE!"; ]; **Grammatica Infine, abbiamo bisogno di estendere la grammatica esistente, per permettere un paio di cosette. Abbiamo già visto che abbiamo bisogno del verbo CHANGE (cambiati). Lo creeremo semplicemente in questo modo: [ ChangeSub; if (noun has pluralname) print "Non sono"; else print "Non è"; " qualcosa che devi cambiare per risolvere la situazione."; ]; Verb 'cambiati' * noun -> Change; Nota solamente come il verbo gestisce il controllo se il sostantivo dato è plurale o singolare, per visualizzare il giusto pronome. Un ulteriore dettaglio: quando il giocatore entra nel bar, potrebbe chiedere a Benny un caffè (come noi abbiamo voluto e pesantemente suggerito), un panino o un dolcetto (entrambi menzionati nella descrizione del bar), del cibo o uno stuzzichino (menzionati qui e la, e abbiamo già provveduto a questo); ma cosa accade se il giocatore prova a mangiare una mela? O a rompere le uova? Ci sono così tante elementi decorativi che possono essere inseriti in un gioco, e prevederli tutti nel menu di Benny ci affaticherebbe non poco. Qualcuno protrebbe ragionevolmente immaginare che la linea di *default* alla fine dell'azione *Give* nella proprietà *orders* gestisca ogni input che non sia altrimenti specificato: orders [; Give: switch (noun) { chiave_del_gabinetto: codice per la chiave... caffe: codice per il caffè... cibo: codice per il cibo... menu: codice per il menu... default: "~Non credo sia sul menù, signore.~"; } ], Non è così. La grammatica della libreria che tratta il comando CHIEDI A BENNY ... è questa (specificamente, gli ultimi due punti): Verb 'chiedi' 'domanda' * 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle' creature 'circa'/'su'/'sul'/'sui'/ 'sullo'/'sull^'/'sulla'/'sugli'/ 'sulle'/'di'/'dello'/'della'/'dell^'/'dei'/ 'degli'/'delle' topic -> Ask * 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle' creature -> Ask * 'scusa'/'scuse' -> Sorry * 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle' creature noun -> Askfor * noun 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle' creature -> AskFor reverse; Come vedi viene preso il *noun*, il che significa che il giocatore deve chiedere a Benny un oggettto del gioco reale presente in quel momento. Assumendo che il giocatore menziona tale oggetto, l'interprete lo trova nel dizionario e mette il suo ID (identificativo) interno nella variabile *noun*, dove la nostra istruzione *switch* può gestirlo. In qesto modo il comando CHIEDI LA CHIAVE A BENNY o CHIEDI A BENNY LA CHIAVE assegna l'oggetto *chiave_del_bagno* alla variabile *noun*, e Benny risponde. Anche CHIEDI A BENNY I CLIENTI funziona, è infatti compreso dal caso di *default*. Ma CHIEDI A BENNY GLI SPAGHETTI ALLA BOLOGNESE non funzionerà: non abbiamo alcun oggetto chiamato Spaghetti alla Bolognese (o quasiasi altra prelibatezza della cucina di Benny) - le parole 'spaghetti' e 'bolognese' semplicemente non sono nel dizionario. Questa è forse una delle maggiori deficienza del nostro gioco, ma ci porterà via davvero poco tempo permettere a Benny di usare la sua linea di codice di default per QUALSIASI parola usata dal giocatore. Abbiamo bisogno di estendere la grammatica esistente del verbo CHIEDI: Extend 'chiedi' * 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle' creature topic -> Askfor * topic 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle' creature -> AskFor reverse; Questa linea sarà aggiunta alla fine della grammatica esistente di Chiedi, così che non sovrascriverà la normale linea di verifica del sostantivo (noun). *topic* è un simbolo che pressappoco significa "quasiasi tipo di input"; il valore di *noun* non è importante, poichè sarà gestito dal caso di *default*. Ora il giocatore può chiedere a Benny un sandwich di tonno o dei divertimenti, ottenendo come risposta: "Non credo che sia sul menù, signore", il che fa di Benny un ottimo barista. E questo è tutto; sulla risposta leggeremente surreale al comando CHIEDI A BENNY I DIVERTIMENTI, abbiamo mantenuto lo stile che ci proponevamo scrivendo "Capitan Fato". La guida è quasi completa. Tutto ciò che rimane da dire riguarda la ricapitolazione di alcuni degli argomenti più importanti e un paio di parole in più sulla compilazione e la correzione dei bug. E quindi potremo gettarti nel grande e profondo mondo della creazione di IF. -------------------------------------------------------------------------------------------------------------------- NOTA DI TRADUZIONE: l'estensione alla grammatica qui riportata non è stata controllata, anzi paolo lucchesi nella sua traduzione dei giochi non la ha menzionata è quindi possibile, anzi probabile che non sia corretta.