Capitolo 12: Capitan Fato: terza! ================================= W was a watchman, and guarded the door; X was expensive, and so became poor. Usando troppo la parola "gabinetto" ci siamo posti una sfida interessante, e ora vi mostreremo come risolvere le ambiguita' che abbiamo introdotto. Inoltre e' il momento adatto per sviluppare in pieno l'eponimo proprietario del Bar di Benny. Troppi gabinetti **************** Se controllate le proprieta' name della porta del gabinetto, della chiave del gabinetto e del gabinetto, noterete che la parola del dizionario 'gabinetto' compare in tutte e tre. Non ci sono problemi se il giocatore menziona le parole PORTA o CHIAVE, ma raggiungiamo uno strano impasse se il giocatore prova a compiere qualche azione usando solo la parola GABINETTO. L'interprete deve pensare velocemente: il giocatore sta parlando della chiave? della porta? del gabinetto? Incapace di decidere, l'interprete chiede "Cosa intendi, la porta che conduce al gabinetto, il gabinetto oppure la chiave del gabinetto?" Provate a indovinare... Il giocatore non potra' mai riferirsi all'oggetto gabinetto (a meno che non scriva TOILET o RITIRATA, non una scelta ovvia visto che non abbiamo mai usato questi termini in modo che risultino visibili). Se il giocatore risponde GABINETTO il giocatore avra' sempre tre oggetti con quella parola di dizionario come possibile nome, e quindi chiedera' ancora, e ancora - fino a che non daremo una qualche parola di dizionario che non e' ambigua. Un lettore umano sarebbe in grado di capire che la parola GABINETTO da sola si riferisce alla stanza , ma l'interprete no - a meno che non non lo aiutiamo un poco. Potremmo aggirare questo problema in piu' di un modo, ma approfitteremo di questa opportunita' per dimostrare l'uso di una libreria esterna (in gergo, prodotta da terze parti). Quando sviluppator esperti trovano un problema che non e' facilmente risolvibile, possono venir fuori con una soluzione birllante e poi tener di conto che altri possono beneficiare dei loro sforzi. Il prodotto di tale generosita' prende la forma di un estensione della libreria: la soluzione bellamente impacchettata in un file che altri sviluppatori possono incorporare nel loro codice sorgente. Questi file possono essere trovati nell'"IF Archive": andate a http://www.ifarchive.org/indexes/if-archive.html e poi selezionate ".../infocom", ".../compilers", ".../inform6", ".../library", e ".../contributions". Tutti questi file contegono codice Infrom. Per usare un'estensione della libreria (detta anche contributo alla libreria), dovete scaricarla e leggere le istruzioni (normalmente incluse come commento nel file, ma occasionalmente fornite separatamente) per scoprire cosa fare dopo. Normalmente la Include-te nel vostro codice (come avete gia' fatto con Parser, VerbLib, Replace e ItalianG), ma spesso ci sono regole sul dove piazzare esattamente l'Include nel vostro codice sorgente. Non e' insolito trovare altri suggerimenti e avvertimenti. Per avere aiuto con il nostro problema di ambiguita' della parola GABINETTO, andremo ad usare l'estensione di Neil Cerutti pname.h, che e' stata sviluppata proprio per situazioni come questa. Anzitutto seguiami il link all'IF Archive e scarichiamo il file compresso pname.zip, che contiene due file ulteriori, pname.h e pname.txt. Mettiamo questi file nella cartella dove stiamo sviluppando il nostro gioco e, se usiamo l'ambiente proposto in "I ferri del mestriere", nella cartella Inform\Lib\Contrib. Il file di testo contiene istruzioni per l'installazione e l'uso. In esso troviamo un avviso: This version of pname.h is recommended for use only with version 6/10 of the Inform Library (Si raccomanda di usare questa versione di pname.h solo con la versione 6/10 della libreria Inform) che e' esattamente la versione che stiamo usando, cosi' non c'e' problema. La maggior parte delle estensioni non sono cosi' pedanti, ma pname.h traffica con qualche routine nel cuore della libreria standard; queste potrebbero non essere identiche in altre versioni di Inform. L'introduzione spiega cosa fa esattamente pname.h; precisamente, essa ti permette di evitare l'uso di complicate routine parse_name per risolvere le ambiguita' dell'input del giocatore quando la stessa parola di dizionario si riferisce a piu' di un oggetto. Una routine parse_name sarebbe stata la soluzione del nostro problema se questo file pname.h non fosse esistito, ed e' decisamente un argomento di programmazione avanzata, difficile da apprendere in un primo approccio. Fortunatamente non dobbiamo preoccuparcene. Neil Cerutti spiega: Il pacchetto pname.h definisce una nuova proprieta' per gli oggetti, pname (abbreviazione di phrase name), con un aspetto simile a quello della normale proprieta' name: entrabe contengono una lista di parole del dizionario. Pero' nella proprieta' pname l'ordine delle parole e' importante, e degli operatori speciali '.p' '.or' e '.x' ti permettono di dare un po' di intelligenza alla lista. Nella maggior parte dei casi in cui la normale proprieta' name non e' abbastanza, potete semplicemente rimpiazzarla con una proprieta' pname, piuttosto che scrivere una routine parse_name. Vedremo presto come funziona. Guardiamo le istruzioni di installazione: Per introdurre questo pacchetto nel vostro programma dovete fare tre cose: 1. Abbiungere quattro righe verso l'inizio del programma (prima di includere Parser.h) Replace MakeMatch; Replace Identical; Replace NounDomain; Replace TryGivenObject; 2. Includete il file pname.h subito dopo Parser.h Include "Parser"; Include "pname"; 3. Aggiungete la proprieta' pname a quegli oggetti che richiedono il riconoscimento delle frasi. Sembra abbastanza facile. Cosi', seguendo i passi 1 e 2, aggiungiamo quelle righe Replace... prima dell'inclusione di Parser, e includiamo pname.h subito dopo. Replace dive al compilatore che stiamo fornendo dei rimpiazzamenti per alcune routine standard. Constant Story "Capitan FATO"; Constant Headline "^Semplice esempio in Inform ^di Roger Firth and Sonja Kesserich.^"; ! Traduzione di Paolo Lucchesi !Release 1; Serial "020428"; ! prima edizione IBG (public beta) Release 2; Serial "020827"; ! per tener conto delle release pubbliche Constant MANUAL_PRONOUNS; Replace MakeMatch; ! richiesto da pname.h Replace Identical; Replace NounDomain; Replace TryGivenObject; Include "Parser"; Include "pname"; ! pname.h la trovate nell'Archivio ... Adesso il nostro codice sorgente e' pronto per beneficiare del pacchetto di libreria. Come funziona? Abbiamo guadagnato una nuova proprieta' " pname " che puo' essere aggiunta ad alcuni dei nostri oggetti e che funziona in modo simile alla proprieta' name. In effetti, dovrebbe essere usata al posto della proprieta' name dove esiste un problema di ambiguita'. Cambiamo le linee rilevanti per la porta del gabinetto e la chiave del gabinetto: Object porta_del_gabinetto with pname 'porta' '.x' 'rossa' '.x' 'del' '.x' 'gabinetto' '.x' 'bagno', short_name [; ... Object chiave_del_gabinetto "chiave del gabinetto" benny with pname 'chiave' '.x' 'del' '.x' 'gabinetto' '.x' 'bagno', article "la", ... mentre lasciamo senza modifiche il fuori_dal_gabinetto: Object fuori_dal_gabinetto "gabinetto" bar with name 'gabinetto' 'cesso' 'toilette' 'toilet' 'ritirata' 'bagno', before [; ... Stiamo usando un nuovo operatore - '.x' - nella nostra lista di parole pname. Il file di testo pname.txt spiega che la prima parola di dizionario alla destra dell'operatore '.x' e' interpretata come opzionale. e questo rende le parole 'gabinetto' e 'bagno' di minore importanza per tali oggitti, in modo che durante il gioco il giocatori possa riferirs alla PORTA o alla PORTA DEL GABINETTO, o alla CHIAVE o alla CHIAVE DEL GABINETTO - ma non semplicemente al GABINETTO - quando si riferisce alla porta o alla chiave. E, visto che abbiamo lasciato immutata la proprieta' name dell'oggetto fuori_dal_gabinetto - dove c'e' un'altra parola 'gabinetto' - la proprieta' pname dira' all'interprete di trascurare la chiave e la porta come possibili oggetti quando il giocatore si riferisce solo al GABINETTO. Vedendola in termini presi da quelli della grammatica italiana, abbiamo detto che "GABINETTO" e' un aggettivo nelle frasi "PORTA DEL GABINETTO" e "CHIAVE DEL GABINETTO", ma un sostantivo quando viene usato per riferirsi alla stanza. Il pacchetto pname.h ha delle funzionalita' aggiuntive che permettono di gestire frasi piu' complesse, ma non ne abbiamo bisogno nel nostro gioco d'esempio. Sentitevi liberi, comunque, di leggere il file pname.txt e scoprire cosa puo' fare per voi questa notevole estensione della libreria: e' una facile risposta per molti mal di testa da ambiguita'. Non sparate sul barista *********************** Molta azione, nel gioco, ruota attorno a Benny, e la sua definizione merita un po' di attenzione. Spieghiamo cosa vogliamo che succeda. Allora, la porta e' chiusa e il giocatore, dopo aver scoperto quel che dice la nota affissa sulla porta del gabinetto, vorra' eventualmente chiedere a Benny la chiave. Purtroppo Benny permette l'uso del gabinetto solo ai clienti, un commento che fara' indicando il menu' alle sue spalle. Il giocatore dovra' chiedere un caffe' prima, in questo modo qualificandosi agli occhi di Benny come un cliente, e quindi persona autorizzata ad usare il gabinetto. Alla fine! Il giocatore potra' piombarsi dentro il gabinetto, indossare il costume di Capitan Fato e volare via a risolvere la situazione! Peccato che il giocatore non ha pagato il caffe', ne' restituito la chiave del bagno. In queste circostanze Benny dovra' impedire al giocatore di lasciare il locare. Per prevenire inutili complicazioni, ci sara' una moneta vicino al wc, abbastanza per pagare il caffe'. E cosi' tutto e' risolto; abbastanza semplice da descrivere - non cosi' semplice da programmare. Ricordate la definizione base di Benny dal capitolo precedente: Object benny "Benny" bar with name 'benny', description "Un uomo ingannevolmente GRASSO dotato di incredibile agilità, Benny intrattiene i clienti schiacciandosi noci di cocco sulla fronte quando è dell'umore giusto.", has scenery animate male proper transparent; Ora possiamo aggiungere un po' di complessita, iniziando dalla proprieta' life. In forma generica: life [; Give: ... codice per dare oggetti a Benny Attack: ... codice per gestire le mosse aggressive del giocatore Kiss: --- codice per il giocatore che diventa affettuoso verso Benny Ask,Tell,Answer: ... codice per gestire le conversazioni ], Abbiamo visto qualcuna di queste azioni prima. Prendiamoci cura delle piu' facili: Attack: if (costume has worn) { deadflag = 4; print "Davanti agli occhi pieni di orrore della gente circostante, salti MAGNIFICIENTEMENTE OLTRE il bancone e attacchi Benny con RIMARCHEVOLE, anche se NON sufficiente velocità. Benny ti riceve con uno sleale uppercut che spedisce la tua MASCELLA DI GRANITO attraverso tutto il locale.^^ ~Questi uomini in pigiama pensano di potersela prendere con gente innocente,~ sbuffa Benny, mentre la SPETTRALE mano dell'OSCURITA' cala sulla tua vista e tu perdi conoscenza."; } else "Questo non è un atto che potrebbe compiere il MITE John Covarth."; Kiss: "Non c'è tempo per INSENSATE infatuazioni."; Ask,Tell,Answer: "Benny è troppo occupato per mettersi a chiaccherare."; Attaccare Benny non e' una mossa saggia. Se il giocatore e' sempre vestito come John Covarth, il gioco mostra un messaggio che rifiuta l'atto di violenza per continuare ad interpretare un inutile fallito. Invece, se Capitan Fato tenta l'azione, scopriremo che Benny ha delle doti nascoste, e il gioco sara perso. Baciare e conversare sono proibite da un paio di messaggi appropriati. L'azione Give (dare) e' un po' piu' complicata, visto che Benny reagisce a certi oggetti in modo speciale. Tenete in mente che la definizione di Benny deve memorizzare se il giocatore ha chiesto un caffe (diventando quindi un cliente e percio' meritevole della chiave), se il caffe' e' stato pagato, e se la chiave del gabinetto e' stata restituita. La soluzione, ancora una volta (questa e' proprio una funzionalita' utile), e' quella di usare ulteriori proprieta' locali: Object benny "Benny" bar with name 'benny', description "Un uomo ingannevolmente GRASSO dotato di incredibile agilità, Benny intrattiene i clienti schiacciandosi noci di cocco sulla fronte quando è dell'umore giusto.", chiesto_caffe false, ! il giocare ha chiesto un caffè? caffe_non_pagato false, ! Benny aspetta di essere pagato? chiave_non_resa false, ! Benny aspetta la chiave indietro? life [; ... Ora siamo pronti per sistemare l'azione Give della proprieta' Life, che gestisce i comandi come DAI LA CHIAVE A BENNY (tra poco arriveremo all'azione Give della proprieta' Orders, che gestisce comandi come BENNY, DAMI LA CHIAVE): Give: switch (noun) { vestiti: "Hai BISOGNO degli anonimi vestiti di John Covarth."; costume: "Hai BISOGNO della tua stupenda tuta ANTI-ACIDO."; chiave_del_gabinetto: self.chiave_non_resa = false; move chiave_del_gabinetto to benny; "Benny annuisce mentre tu MIRABILMENTE gli rendi la chiave."; moneta: remove moneta; self.caffe_non_pagato = false; "Con meravigliose movenze da ILLUSIONISTA, fai apparire una moneta dal tuo costume come se fosse uscita fuori dall'orecchio di Benny! Le persone attorno a te applaudono educatamente. Benny prende la moneta, e la morde SOSPETTOSO. ~Grazie, signore. Torni quando vuole,~ dice."; } L'azione Give della proprieta' Life tiene nella variabile noun l'oggetto offerto al PNG. Ricordate che potete usare l'istruzione switch come abbreviazione per: if (noun == costume) { qualcosa }; if (noun == vestiti) { qualcosa }; ... Non permetteremo al giocatore di dar via i suoi vestiti o il suo costume (un'azione improbabile, ma non si sa mai). La chiave del gabinetto e la moneta sono trasferiti con successo. Alla proprieta' chiave_non_resa assegneremo il valore true quando riceveremo la chiave del gabinetto da Benny (non abbiamo ancora scritto il codice per questo), e ora, quando ridiamo la chiave indietro, le assegnamo di nuovo il valore false. L'istruzione move serve a trasferire l'oggetto dall'inventario del giocatore a Benny, e finalmente mostriamo un messaggio di conferma. Con la moneta troviamo una nuova istruzione, remove. Questa toglie l'oggetto dall'albero degli oggetti, in modo che non abbia genitore. L'effetto e' quello di farlo scomparire dal gioco (anche se non state distruggendo l'oggetto permanentemente - e infatti potete riportarlo nell'albero degli oggetti con un'istruzione move); per quanto riguarda il giocatore, non c'e' un'altra MONETA da trovare. Alla proprieta' caffe_non_pagato assegneremo il valore true quando Benny ci serve una tazza di caffe' (lo vedremo tra poco); ora la riportiamo a fale, il che libere il giocatore dal suo debito. Il tutto culmina con l'istruzione "...", stampa e ritorna, che dice al giocatore che l'azione ha avuto successo. Perche' abbiamo spostato la chiave ridandola a Benny, ma abbiamo rimosso la moneta? Una volta che il giocatore si e' qualificato come cliente ordinando un caffe', potra' chiedere e restituire la chiave quante volte vuole, cosi' sembra importante tenere la chiave a disposizione. La moneta, invece, serve una volta sola. Non permetteremo al giocatore di ordinare piu' di un caffe', in modo da impedire che il suo debito possa crescere all'infinito - inoltre e' venuto qui a cambiarsi, non a sorbire bevande a base di caffeina. Una volta che la moneta viene usata per pagare, essa sparisce, probabilmente nelle avide tasche di Benny. Non c'e' motivo di preoccuparsene ulteriormente. L'oggetto benny ha bisogno anche di una proprieta' orders, che si occupi delle richieste del giocatore per un caffe' e per la chiave, e per respingere altre richieste. L'azione Give in una proprieta' orders si occupa porprio di comandi come CHIEDI LA CHIAVE A BENNY e BENNY, DAMMI LA CHIAVE. La sintassi e' simile a quella della proprieta' life: orders [; ! gestisce CHIEDI LA CHIAVE A BENNY e BENNY, DAMMI LA CHIAVE Give: switch (noun) { chiave_del_gabinetto: if (chiave_del_gabinetto in player) "Ma tu HAI già la chiave."; if (self.chiesto_caffe == true) { move chiave_del_gabinetto to player; self.chiave_non_resa = true; "Benny getta la chiave delle toilettes sul bancone, da cui tu la prendi con un destro e preciso movimento della tua mano SUPER-AGILE."; } else "~Il gabinetto è solo per i clienti.~ mormora, indicando con il dito il menu alle sue spalle."; caffe: if (self.chiesto_caffe == true) "Un caffè mi sembra abbastamza."; move caffe to bancone; self.chiesto_caffe = true; self.caffe_non_pagato = true; "In sole due mosse aggraziate, Benny posa di fronte a te il suo famosissimo Caffè macchiato."; cibo: "Mangiare ti prenderebbe troppo tempo, devi cambiarti ORA."; menu: "Con solo un piccolissimo singhiozzo, Benny fa un cenno verso il menu affisso al muro alle sue spalle."; default: "~Non credo sia sul menù, signore.~"; } ], - Chiave del gabinetto: anzitutto controlliamo se il giocatore ha gia' la chiave oppure no, e nel caso mostriamo un messaggio e interrompiamo l'esecuzione grazie al return implicito dell'istruzione "...". Se il giocatore non ha la chiave, controlliamo se ha gia' chiesto un caffe, valutando la proprieta' chiesto_caffe. Se cio' e' vero, il giocatore ottene la chiave, e questo vuol dire che dovra' restituirla - la proprieta' chiave_non_resa prende il valore true - e mostriamo un messaggio adatto. Se cio' non e' vero (la clausola else, che si associa alla piu' vicina istruzione if), Benny rifiuta di consegnare la chiave, menzionando per la prima volta il menu' dove il giocatore potra' vedere il disegno di una tazza di caffe' quando lo ESAMINA. - Caffe': controlliamo se il giocatore ne ha gia' chiesto uno, valutando la proprieta' chiesto_caffe, e rifiutiamo di servirne un altro se fosse vero. Se la proprieta' ha valore false, piazziamo un caffe' sul bancone e assegniamo true alle proprieta' chiesto_caffe e caffe_non_pagato. La parte del messaggio la conoscete di gia'. - Cibo: introdurremo un oggetto per gestire tutte quelle cose commestibili e deliziose che possiamo trovare nel bar, specialmente quelle (come i 'pezzi dolci' e i 'sandwich') menzionati nelle nostre descrizioni. Anche se questo oggetto non e' ancora stato definito, introduciamo anche questo caso per reagire alla gola del giocatore se provasse a chiedere a Benny del cibo. - Menu': la nostra risposta di default "Non credo sia sul menu', signore" non e' molto appropriata se il giocatore chiede un menu', cosi' ne forniamo una migliore. - Default: questo si occupa di ogni altra cosa che il giocatore puo' chiedere a Benny, visualizzando la sua risposta. E prima che ve ne siate potuti rendere conto, l'oggetto Benny e' finito; pero' non festeggiate subito. Ci sono ancora dei comportamenti di Benny che, curiosamente, non sono codificate all'interno dell'oggetto Benny; stiamo parlando del come reagisce Benny se il giocatore prova ad andarsene senza pagare o senza restituire la chiave. Vi abbiamo promesso che Benny avrebbe fermato il giocatore, e cosi' sara'. Ma dove? Dobbiamo rivisitare l'oggetto bar: Room bar "Il bar di Benny" with description [; print "Benny offre la MIGLIORE selezione di pezzi dolci e sandwich. I clienti riempiono il bancone dove Benny in persona riesce a servire, cucinare e riscuotere senza la minima esistazione. Sulla parete nord del bar vedi una porta rossa che conduce al gabinetto."; if (costume has worn && self.appenauscito == false) { self.appenauscito = true; StartDaemon(clienti); print "^^I clienti osservano il tuo costume con evidente curiostità."; } new_line; ], appenauscito false, ! Prima apparizione di Capitan Fato? before [; Go: if (noun ~= s_obj) return false; if (benny.caffe_non_pagato == true || benny.chiave_non_resa == true) { print "Come accenni ad uscire per strada, la grossa mano di Benny si appoggia sulla tua spalla."; if (benny.caffe_non_pagato == true && benny.chiave_non_resa == true) "^^~Hey! Hai ancora la mia chiave e non hai pagato il caffè. Ti sembro forse uno stupido?~ Ti scusi come soltanto un EROE sa fare e torni all'interno."; if (benny.caffe_non_pagato == true) "^^~Aspetta un minuto, Amico,~ dice. ~Stiamo cercando di sgattaiolare via senza pagare, vero?~ Mormodi velocemente una scusa e torni all'interno del bar. Benny torna ai suoi compiti continuando a guardarti sospettoso."; if (benny.chiave_non_resa == true) "^^~Dove credi di andare con la chiave del gabinetto?~ dice. ~Sei forse un ladro?~ Mentre Benny ti spinge di nuovo all'interno del locale, velocemente lo rassicuri spiegando che si è trattato solo di uno STUPEFACENTE errore."; } if (costume has worn) { deadflag = 5; ! hai vinto! "Esci in strada, dove le persone di passaggio riconoscono la STRAVAGANZA arcobaleno del costume di Capitan FATO e gridano il tuo nome con stupore mentre tu SALTI con forza sensazionale nel cielo BLU del mattino!"; } ], s_to strada, n_to porta_del_gabinetto; Ancora una volta notiamo che la soluzione ad un problema non e' necessariamente unica. Ricordate cosa abbiamo visto quando ci occupavamo della descrizione del giocatore: avremmo potuto assegnare un nuovo valore alla variabile player.description, ma abbiamo scelto di usare l'oggetto LibraryMessages. Questo e' un caso simile. Il codice che permette a Benny di intercettare il giocatore distratto, forse, avrebbe potuto essere aggiunto ad una proprieta' daemon nella definizione di Benny. Pero', visto che l'azione che vogliamo intercettare e' sempre la stessa, ed e' un'azione di movimento (quando il giocatore prova ad uscire dal bar), e' possibile scrivere il suo codice intercettando l'azione Go dell'oggetto bar. Entrambe le soluzioni sarebbero state giuste, ma questa e' un po' piu' semplice. Abbiamo aggiunto una proprieta' before all'oggetto bar (una un po' lunghetta), che si occupa solo dell'azione Go. Questa tecnica ti permette di intercettare il giocatore che prova ad uscire da una stanza prima che il movimento abbia luogo, un buon momento per interferire se vogliamo impedire la fuga. La prima riga: if (noun ~= s_obj) return false; dice all'interprete che vogliamo occuparci solo dei movimenti che conducono verso sud, lasciando che l'interprete applichi le normali regole per le altre direzioni possibili; l'operatore ~= sta per "non uguale a". Da qui in poi sono solo condizioni e altre condizioni. Il giocatore puo' provare ad uscire: - senza aver pagato il caffe' e senza aver restituito la chiave - avendo pagato il caffe', ma senza restituire la chiave - avendo restituito la chiave, ma senza aver pagato il caffe' - pulito da ogni peccato e di nulla colpevole agli occhi degli uomini (beh, agli occhi di Benny, almeno) Le prime tre sono coperte dal test if (benny.caffe_non_pagato == true || benny.chiave_non_resa == true) ... cioe', se il caffe' non e' stato pagato o la chiave non e' stata restituita. Quando questa condizione e' falsa, vuol dire che entrambi i malcomportamenti sono stati evitati e che il giocatore puo' uscire liberamente. Se pero' questa condizione e' vera, la mano di Benny cadra' sulla spalla del giocatore e il gioco mostrera' un diverso messaggio, a seconda di quale errore o quali errori il personaggio ha commesso. Se il giocatore puo' andare, e sta' indossando il suo costume da combattente del crimine, il gioco e' vinto. Vi diremo come nel prossimo capitolo, dove concluderemo la creazione del gioco.