Capitolo 9: Gugliemo Tell: la fine è vicina =========================================== Rimangono da definire ancora un po' di oggetti, così parleremo di questi per primi. Poi spiegheremo come fare aggiunte al repertorio di verbi standard di Inform, e come definire le azioni che vengono invocate da questi verbi. La piazza del mercato ********************* La stanza "piazza del mercato" non è assolutamente notevole, e l'albero che vi cresce ha solo una caratteristica interessante: Room mercato "Piazza del mercato" with description "Il mercato di Altdorf, vicino alla piazza principale, è stato velocemente sgombrato dai banchi. Un gruppo di soldati ha spinto indietro la folla per lasciare spazio libero di fronte al tiglio che è cresciuto qui fin da quando la gente ha memoria. Normalmente esso fa ombra ai vecchi della città che si ritrovano qui per chiaccherare, guardare le ragazze e giocare a carte. Oggi però, esso è solitario... eccetto che per Walter, che è stato legato al tronco. A circa quaranta metri dall'albero, tu sei tenuto bloccato da due degli uomini del balivo.", cant_go "Cosa? E lasceresti tuo figlio legato a quell'albero?"; Object albero "tiglio" mercato with name 'albero' 'tiglio' 'tronco', description "Un grosso albero.", before [; FireAt: if (NotBowOrArrow(second)) return true; deadflag = 3; print_ret "La mano ti trema, e la freccia vola alta, andando a colpire il tronco, almeno una spanna sopra la testa di Walter."; ], has scenery; La proprietà before dell'albero intercetta l'azione FireAt, che definiremo tra pochi momenti. Questa azione è il risultato di un comando come SPARA ALL'ALBERO CON L'ARCO - potremmo simularla con il comando <> - e ha bisogno di cura particolare per assicuarsi che il secondo oggetto sia un'arma adatta. Per gestire comandi stupidi come SPARA ALL'ALBERO CON HELGA dobbiamo verificare che il secondo oggetto sia l'arco, una delle frecce, o niente (accettiamo anche SPARA ALL'ALBERO). Siccome questo è un controllo abbastanza complesso, e che dobbiamo eseguire in diversi punti, è appropriato scrivere una routine per svolgere questo compito. Una routine indipendente, come le familiari routine incapsulate come valore di una proprietà come before o each_turn, è semplicemente una collezione di istruzioni da eseguire. Le principali differenze sono nel contenuto e nel momento in cui vengono invocate: * Mentre una routine incapsulata deve contenere istruzioni che fanno qualcosa di appropriato per la proprietà a cui è associata, una routine indipendente può contenere istruzioni che fanno qualsiasi cosa voi vogliate. Avete la completa libertà riguardo a quello che fa la routine e al valore che essa restituisce. * Una routine incapsulata è chiamata quando l'interprete sta trattando quella proprietà per quell'oggetto; voi fornite la routine, ma non controllate direttamente quando questa viene chiamata. Una routine indipendente, invece, è completamente sotto il vostro controllo; viene eseguita solo quando voi la chiamate esplicitamente. Quello che generalmente viene fuori da tutto ciò è questo: se avete una collezione di istruzioni che eseguono un qualche compito specifico, e avete bisogno di eseguire queste stesse istruzioni in più di un punto del vostro gioco, allore spesso è più sensato trasformare queste istruzione in una routine indipendente. I vantaggi sono: scrivete le istruzioni una volta sosa, e così ogni modifica che verrà sarà più facile da fare; inoltre il gioco diventa più semplice e più facile da leggere. Vediamo qualche semplice esempio. All'inizio del capitolo precedente, abbiamo definito questi oggetti: Prop "palo" with name 'palo' 'legno', description "Sei troppo lontano per vedere qualsiasi dettaglio.", found_in piazza_sud piazza_nord; Prop "cappello" with name 'cappello', description "Sei troppo lontano per vedere qualsiasi dettaglio.", found_in piazza_sud piazza_nord; Le due descrizioni sono identiche: forse potremmo visualizzarle usando una routine? [ troppo_lontano; print_ret "Sei troppo lontano per vedere qualsiasi dettaglio."; ]; Prop "palo" with name 'palo' 'legno', description [; troppo_lontano(); ], found_in piazza_sud piazza_nord; Prop "cappello" with name 'cappello', description [; troppo_lontano(); ], found_in piazza_sud piazza_nord; Questo non è un approccio molto realistico - ci sono modi più eleganti per evitare di scrivere la stessa stringa due volte - ma funziona, e mostra come possiamo definire una routine che fa qualcosa di utile e poi chiamarla dove ne abbiamo bisogno. Ecco un altro semplice esempio che mostra come, restituendo un valore, la rotuine può riferire un risultato al pezzo di codice che l'ha chiamata. Abbiamo usato un paio di volte il test if (self has visited) ...; potremmo creare una routine che esegue lo stesso controllo e restituisce vero o falso per indicare cosa ha scoperto: [ gia_stato_qui; if (self has visited) return true; else return false; ]; E poi riscriviamo i nostri controlli come if (gia_stato_qui() ==true) ...; non è più breve o più veloce, ma forse è più descrittivo riguardo a cosa sta accadendo. Un ultimo esempio sull'uso delle routine. Così come abbiamo controllato if (self has visited) ..., abbiamo anche controllato qualche volta if (location has visited) ..., così potremmo scrivere un'altra routine per fare il controllo. [ gia_stato_stanza; if (location has visited) return true; else return false; ]; Ma le due routine sono molto simili; la sola differenza è il nome della variabile - self o locarion - che viene controllata. Un approccio migliore potrebbe essere quella di rifare la nostra routine gia_stato_qui in modo che svolga entrambi i compiti, ma dobbiamo dirgli in qualche modo quale variabile deve controllare. Questo è facile: scriviamo la routine in modo che aspetti un argomento: [ gia_stato luogo; if (luogo has visited) return true; else return false; ]; Notate che il nome dell'argomento l'abbiamo inventato in modo che descrivesse il proprio contenuto. La routine non si preoccupa del fatto che noi lo definiamo come "x", "luogo" o "piero_piero". Qualunque sia il suo nome, l'argomento funziona come segnaposto per il suo valore (nel nostro esempio una delle due variabili, self o location), che noi dobbiamo fornire quando chiamiamo la routine: if (gia_stato(self) == true) ... if (gia_stato(location) == true) ... Nella prima riga forniamo self come argomento per la routine. Comunque alla routine non interessa da dove viene l'argomento; lei vede solo un valore che conosce come "luogo", e che usa per verificare l'attributo visited. Nella seconda riga forniamo location come argomento, ma la routine vede soltanto un'altro valore nella sua variabile luogo. La variabile luogo è chiamata variabile locale della routine gia_stato, una variabile a cui deve essere assegnato un valore adatto ogni volta che la routine viene chiamata. In effetti alla routine non interessa da dove viene l'argomento, basta che esso rappresenti un oggetto stanza; potremmo anche controllare una stanza nominata esplicitamente: if (gia_stato(centro_piazza) == true) ... Dopo tutta questa introduzione, torniamo finalmente all'azione FireAt. Nel nostro esempio dobbiamo controllare le caratteristiche di un oggetto e dopo possibilmente visualizzare un messaggio. Non sappiamo esattamente che oggetto deve essere controllato, così dobbiamo scrivere la nostra routine in modo generalizzato, facendo sì che sia capace di controllare qualsiasi oggetto vogliamo; ovvero dobbiamo fornire l'oggetto da controllare come argomento. Ecco la routine: [ NotBowOrArrow o; if (o == arco or nothing || o ofclass Arrow) return false; print_ret "Non è granchè come arma, vero?"; ]; La routine è stata pensata per ispezionare qualsiaio oggetto che le è stato passato come argomento o; questo vuol dire che possiamo chiamare la routine così: NotBowOrArrow(proprietaria) NotBowOrArrow(mela) NotBowOrArrow(albero) NotBowOrArrow(arco) Se viene fornito un oggetto come Helga, la mela o l'albero, la routine stamperà un messaggio e restituirà true per indicare che in effetti questo oggetto non è un'arma adatta. Comunque, dato l'oggetto arco, o ogni oggetto definito nella classe Arrow, restituirà silenziosamente false, per negare il fatto che l'oggetto non sia un'arma adatta, perchè in effetti lo è (scusate la doppia negazione). Il controllo che facciamo è: if (o == arco or nothing || o ofclass Arrow) ... che è soltanto un modo più breve di dire: if (o == arco || o == nothing || o ofclass Arrow) ... Il risultato è che noi chiediamo tre cose: "o" è l'oggetto arco? oppure è nothing? oppure è un qualunque oggetto membro della classe Arrow? Questo significa che il valore restituito quando chiamiamo NotBowOrArrow(albero) è true, mentre il valore restituito quando chiamiamo NotBowOrArrow(arco) è false. O, in termini più generali, il valore restituito quando chiamiamo NotBowOrArrow(second) sarà o true o false, a seconda delle caratteristiche dell'oggetto definito dal valore della variabile second. Così possiamo scrivere questa coppia di istruzioni: if (NotBowOrArrow(second)) return true; e l'effetto potrebbe essere: * second non è un'arma: NotBowOrArrow visualizza un messaggio e restituisce il valore true, l'istruzione if reagisce a quel valore eseguendo la seguente istruzione return true, che interrompe l'esecuzione dell'azione FireAt; oppure * second è un'arma: NowBowOrArrow non visualizza nulla e restituisce il valore false, l'istruzione if reagisce a quel valore ignorando l'istruzione successiva, e l'azione FireAt continua. Pfff! Questo è stato un po' fonte di confusione, ma il resto dell'azione FireAt è lineare. Una volta che l'albero ha determinato che è stato colpito con qualcosa di adatto, assegna a deadflag il valore 3 - la fine "Hai rovinato..." - stampa un messaggio, e ha finito. Gessler il governatore ********************** Non c'è niente nella definizione di Gessler che non abbiamo già incontrato in precedenza: NPC balivo "balivo" mercato with name 'governatore' 'balivo' 'hermann' 'gessler', description "Basso e robusto, ma dal volto subdolo e affilato, Gessler usa il potere affidatogli opprimendo la comunità locale.", initial [; print "Gessler sta osservando da lontano, con un sogghigno sul volto.^"; if (location hasnt visited) print "~A quanto pare hai bisogno di una buona lezione, stupido. Nessuno deve attraversare la piazza senza prestare omaggio a Sua Altezza Imperiale Alberto; nessuno, hai capito? Potrei farti decapitare per tradimento, ma voglio essere clemente. Se ti comporterai ancora follemente, non potrai aspettarti alcuna pietà da parte mia, ma questa volta sei libero di andare... non appena mi avrai dimostrato la tua abilità di arciere colpendo questa mela da dove ti trovi. Non dovrebbe essere troppo difficile. Sergente, prenda; la appoggi sulla testa di quel piccolo bastardo.~^"; ], life [; Talk: print_ret "Non hai intenzione di rivolgergli la parola."; ], before [; FireAt: if (NotBowOrArrow(second)) return true; deadflag = 3; print_ret "Prima che i soldati possano reagire, ti volti e scagli una freccia contro Gessler; la tua frecca colpisce il suo cuore ed egli muore miseramente. La folla ha un sussulto, seguito da un applauso."; ], has male; Come la maggior parte dei PNG, Gessler ha una proprietà life che si occupa delle azioni che si applicano solo agli oggetti animati. Questa risponde soltanto a Talk (ovvero al comando PARLA AL GOVERNATORE). [N.D.T.: L'attributo male è superfluo, visto che in italiano non esiste il genere neutro e che quindi gli oggetti sono di genere maschile a meno che non venga specificato altrimenti. ] Walter e la mela **************** Visto che è stato con te tutto il tempo, è il momento di definire Walter: NPC figlio "tuo figlio" with name 'figlio' 'tuo' 'ragazzo' 'bambino' 'walter', description [; if (location == mercato) print_ret "Ti sta guardando, cercando di apparire coraggioso e rimanere fermo. Le sue braccia sono legate dietro al tronco, e la mela è stata posata tra i suoi capelli biondi."; else print_ret "Un tranquillo ragazzo biondo di otto primavere, rapido ad imparare lo stile di vita dei montanari."; ], life [; Give: score = score + 1; move noun to self; print_ret "~Grazie, Papi.~"; Talk: if (location == mercato) print_ret "~Stai calmo, figliolo, e confida in Dio.~"; else print_ret "Spieghi a tuo figlio la tua visione della vita."; ], before [; Examine,Listen,Salute,Talk: return false; FireAt: if (location == mercato) { if (NotBowOrArrow(second)) return true; deadflag = 3; print_ret "Oops! Sicuramente non volevi far ciò..."; } default: if (location == mercato) print_ret "Le guardie non lo permetterebbero."; else return false; ], found_in [; return true; ], has male proper scenery transparent; I suoi attributi sono male [N.D.T.: inutile] (è tuo figlio, dopo tutto), proper (così l'interprete non scriverà "il tuo figlio"), scenery (così non verrà elencato nella descrizione di ogni stanza) e transparent (poichè puoi vedergli attraverso). No, questo è sbagliato: un oggetto transparent non è fatto di vetro; è un'oggetto che ti permette di vedere gli oggetti che possiede. Abbiamo fatto ciò perche vorremmo essere sempre in grado di ESAMINARE LA MELA anche se è Walter che la sta portando. Senza l'attributo transparent, sarebbe come se la mela fosse nella sua tasca o comunque non in vista; l'interprete risponderebbe "Non vedi nulla del genere". Walter ha una properità found_in che automaticamente lo muove ad ogni turno nella stessa locazione del giocatore. Possiamo accettare ciò perchè in un gioco così piccolo e semplice, egli in effetti ti segue dovunque. In mondi simulati più realistici, i PNG di solito si muovono indipendentemente, ma noi non abbiamo bisogno di tanta complessità in questo caso. Diverse proprietà di Walter controllano se (location == mercato); ovvero, se il giocatore (e quindi Walter) si trova al momento in quella stanza. Gli eventi nella piazza del mercato sono tali che risposte specializzate sono più appropriate delle risposte genereche. La proprietà life di Walter reagisce a Give (ovvero al comando DAI LA MELA A WALTER) e Talk (ovvero PARLA A TUO FIGLIO); durante Give, aumentiamo il valore della variabile di libreria score, ricompensando così la natura generosa del giocatore. La sua proprietà before è un po' più complessa. Essa dice: 1. Le azioni Examine, Listen, Salute e Talk sono sempre disponibili (quindi l'azione Talk viene passata alla proprietà life di Walter). 2. L'azione FireAt è permessa nella piazza del mercato, anche se con risultati sfortunati. In qualunque altro posto, essa attiva la risposta generica per FireAt, "Sembra pericoloso, non trovi?". 3. Tutte le altre azioni sono prevenute nella piazza del mercato, mentre in altri luoghi permettiamo che seguano il loro normale svolgimento (grazie all'istruzione return false). Il momento di gloria della mela è arrivato! La sua proprietà before risponde all'azione FireAt impostando la variabile deadflag a 2. Quando ciò succede, il gioco è finito; il giocatore ha vinto. Object mela "mela" with name 'mela', description [; if (location == mercato) print_ret "A questa distanza puoi appena vederla."; else print_ret "La mela è a chiazze verdi e marroni."; ], before [; Drop: print_ret "Una mela vale sempre qualcosa, meglio tenersela."; Eat: print_ret "Helga te l'ha data per Walter..."; FireAt: if (location ~= mercato) return false; if (NotBowOrArrow(second)) return true; score = score + 1; deadflag = 2; print_ret "Con calma e fermezza incocchi una freccia nell'arco, tendi la corda e prendi la mira con più cura di quanto tu abbia mai fatto in vita tua. Trattenendo il fiato, senza un batter d'occhio, timorosamente, rilasci la freccia. Questa vola attraverso la piazza, verso tuo figlio, e pianta la mela contro il tronco dell'albero. La folla esulta; Gessler sembra decisamente deluso."; ]; E con ciò, abbiamo definito tutti gli oggetti. Così facendo abbiamo aggiunto un bel carico di nuovi nomi e aggettivi al dizionario del gioco, ma nessun verbo. Questo è il nostro ultimo compito. Verbi, Verbi, Verbi ******************* La libreria di Inform fornisce un insieme standad di circa un centinaio di azioni che il giocatore può eseguire; all'incirca una ventina di queste sono "meta-azioni" (come SALVA e QUIT) dirette all'interprete stesso, mentre le restanti operano all'interno del mondo virtuale. Avere un così grande insieme di partenza è sicuramente una grande benedizione; significa che molte delle azioni che un giocatore potrebbe trovare sono già gestite, o dall'interprete che fa qualcosa di utile o spiegando perchè non può farlo. Nonostante ciò, la maggior parte dei giochi ha bisogno di definire azioni addizionali, e "Guglielmo Tell" non fa eccezione a ciò. Aggiungeremo quattro azioni da noi definite: Untie (slega), Salute (saluta, riverisci), FireAt (spara a) e Talk (parla). *** Untie Non è l'azione più utile, ma è la più semplice. Nella piazza del mercato, quando Walter è legato all'albero, è possibile che il giocatore sia tentato dall'idea di provare SLEGA WALTER o LIBERA WALTER; improbabile ma, come abbiamo detto prima, prevedere l'improbabile è parte della creazione di Avventure Testuali. Per questa, e per tutte le nuove azioni, abbiamo bisogno di due cose. Abbiamo bisogno di una definizione grammaticale, che specifichi la struttura delle sentenze in Italiamo che noi siamo preparati ad accettare: Verb 'slega' 'sciogli' 'libera' 'rilascia' * noun -> Untie; e abbiamo bisogno di una routine per gestire l'azione nella situazione standard (quando l'azione non è intercettata dalla proprietà before di un oggetto): [ UntieSub; print_ret "Non dovresti provarci."; ]; La grammatica è meno complessa di come forse appare a primo colpo d'occhio: 1. I verbi italiani SLEGA, SCIOGLI, LIBERA e RILASCIA sono sinonimi. 2. L'asterisco * indica l'inizio di uno schema che definisce quali parole possono seguire il verbo. 3. In questo esempio c'è un solo schema: l'elemento "noun" rappresenta il nome di un oggetto che è correntemente "in scope" - presente nella stessa stanza del giocatore. 4. Il segno -> indica l'azione che deve essere attivata. 5. Se il giocatore scrive qualcosa che corrisponde allo schema - uno di quei quattro verbi seguito dal nome di un oggetto presente nella stanza - l'interprete attiva un'azione Untie, che normalmente è gestita da una routine che abbia lo stesso nome dell'azione con il suffisso Sub. In questo esempio è la routine UntieSub. 6. La grammatica è disposta in questo modo solo per renderla più semplice da leggere. Tutti quegli spazi non sono importanti; avremmo potuto scrivere: Verb 'slega' 'sciogli' 'libera' 'rilascia' * noun -> Untie; Mostriamo come tutto questo funziona nella strada di Altdorf: Una strada di Altdorf La piccola strada conduce verso nord alla piazza principale. La gente del luogo sta sciamando nella città attraverso la porta a sud, salutando a voce alta, offrendo prodotti in vendita, scambiando notizie, informandosi con incredulità esagerata sui prezzi delle merci esposte dai mercanti, i cui banchi rendono ancora più difficile l'avanzare in mezzo alla folla. "Stammi vicino, figliolo," dici, "altrimenti potresti perderti fra tutta questa gente." >SLEGA Cosa vuoi slegare? >SCIOGLI IL CANE Non vedi nulla del genere. >RILASCIA LA GENTE Non hai bisogno di preoccuparti della gente. >LIBERA TUO FIGLIO Non dovresti provarci. Questo esempio mostra quattro tentativi di usare la nuova azione. Nel primo il giocatore non menziona l'oggetto a cui si riferisce l'azione; l'interprete sa (da quel noun nella grammatica che implica che l'azione ha bisogno di un complemento oggetto) che manca qualcosa, e fornisce una utile risposta. Nel secondo caso, il giocatore menziona un oggetto che non è presente, che non è "in scope" (in effetti non c'è nessun cane in nessuna parte del gioco, ma l'interprete non vuole suggerire così tanto al giocatore). Nel terzo caso, l'oggetto è "in scope", ma la sua proprietà before intercetta l'azione Untie (e in effetti, visto che questo oggetto è della classe Prop, tutte le azioni eccetto Examine) per visualizzare un messaggio di diniego personalizzato. Finalmente, il quarto utilizzo si riferisce ad un oggetto che non intercetta l'azione, così l'interprete chiama la routine che gestisce di default l'azione - UntieSub - che visualizza un rifiuto generale ad eseguire l'azione. I principi presentati qui sono quelli che dovreste generalmente usare: scrivere una routine per la gestione generica dell'azione che rifiuta di fare qualsiasi cosa (vedi, per esempio, SCHIACCIA o COLPISCI), o che esegue l'azione senza modificare lo stato del mondo (vedi, per esempio, SALTA o CANTA); poi intercettate quella non-azione (generalmente usando una proprietà before) per quegli oggetti che potrebbero essere un bersaglio legittimo per l'azione, e quindi fornire una risposta più specifica, eseguendo o rifiutando l'azione. Nel caso di Untie, non ci sono oggetti nel gioco che possono essere slegati, così noi generiamo sempre un rifiuto di un qualche tipo. *** Salute La prossima azione è Salute, fornita nel caso Wilhem scelga di prestare omaggio al cappello sul palo. Questa routine è il gestore generico dell'azione: [ SaluteSub; if (noun has animate) print_ret (The) noun, " restituisce il saluto."; print_ret (The) noun, " non mostra alcuna reazione."; ]; Noterete che questa è leggermente più intelligente del gestore per Untie, visto che produce risposte differenti a seconda che l'oggetto salutato - contenuto nella variabile noun - sia animato o no. Ma basilarmente sta facendo lo stesso lavoro. Ed ecco la grammatica: Verb 'inchinati' 'genuflettiti' * 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle'/'verso' noun -> Salute; Verb 'riverisci' * noun -> Salute; Verb 'presta' 'rendi' * 'omaggio'/'omaggi' 'a'/'ad'/'all^'/'allo'/'alla'/ 'al'/'agli'/'ai'/'alle' -> Salute Questa grammatica dice che: 1. I verbi italiani 'inchinati', 'genuflettiti', 'riverisci', 'presta' e 'rendi' sono sinonimi. 2. I primi due (ma non il terzo) possono essere seguiti da una delle seguenti proposizioni A, AD, ALL', ALLO, ALLA, AL, AGLI, AI, ALLE o VERSO: le parole tra apici devono corrispondere esattamente, con la barra / che separa varie alternatice. Invece il quarto e il quinto possono essere seguiti da 'omaggio' o 'omaggi' e una delle proposizioni di cui sopra (eccetto VERSO). 3. Dopo di ciò viene il nome di un'oggetto "in scope" - nella stessa stanza del giocatore. 4. Se il giocatore scrive qualcosa che corrisponde a questi schemi, l'interprete attiva un'azione Saluta, che normalmente è gestita dalla routine SaluteSub. Così noi possiamo scrivere INCHINATI AL CAPPELLO, GENUFLETTITI VERSO IL CAPPELLO, PRESTA OMAGGIO AL CAPPELLO ma non semplicemente INCHINATI CAPPELLO o RENDI AL CAPPELLO. Inoltre possiamo scrivere RIVERISCI IL CAPPELLO, ma non RIVERISCI VERSO IL CAPPELLO. Non è perfetto, ma è un onesto tentativo per definire qualche nuovo verbo per gestire il saluto. Ma supponiamo di pensare che ci sono altri modi in cui il giocatore può tentare ciò (ricordate, loro non sanno quali verbi abbiamo definito; stanno provando alla cieca, provando cose che secondo loro dovrebbero funzionare). Che ne pensate di SALUTA IL CAPPELLO, o OFFRI OMAGGIO AL CAPPELLO? Sembrerebbe abbastanza ragionevole, no? Peccato però che se noi avessimo scritto Verb 'riverisci' 'saluta' * noun -> Salute; Verb 'presta' 'rendi' 'dai' 'offri' * 'omaggio'/'omaggi' 'a'/'ad'/'all^'/'allo'/'alla'/ 'al'/'agli'/'ai'/'alle' -> Salute avremmo provocato un errore in compilazione: two different verb definitions refer to "saluta", due diverse definizioni di verbo per "saluta" (e lo stesso errore vale anche per offri e dai). ItalianG.h [n.d.t.: grammar.h nella versione originale], il solo file di libreria di cui un principiante ha bisogno di studiare il contenuto, contiene queste righe: Verb 'saluta' * creature -> WaveHands; Verb 'dai' 'paga' 'offri' 'da' * held 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle' creature -> Give * 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle' creature held -> Give reverse; Il problema è che i verbi saluta, dai e offri sono già definiti nella libreria, e un verbo può apparire in una sola definizione Verb. La soluzione sbagliata: editare ItalianG.h per modificare fisicamente la definizione per 'saluta' (non è quasi mai una buona idea apportare cambiamenti ai file della libreria standard o di infit). La soluzione giusta: usare Extend per aggiungere logicamente qualcosa alla definizione: Extend 'saluta' first * noun -> Salute; Extend 'dai' * 'omaggio'/'omaggi' 'a'/'ad'/'all^'/'allo'/ 'alla'/'al'/'agli'/'ai'/'alle'/'verso' noun -> Salute; e così l'effetto è come se avessimo editato ItalianG.h e lo avessimo modificato così: Verb 'saluta' * noun -> Salute * creature -> WaveHands; Verb 'dai' 'paga' 'offri' 'da' * held 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle' creature -> Give * 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle' creature held -> Give reverse * 'omaggio'/'omaggi' 'a'/'ad'/'all^'/'allo'/ 'alla'/'al'/'agli'/'ai'/'alle'/'verso' noun -> Salute; [n.d.t.: abbiamo usato anche la clausola first che mette la nuova definizione logica al primo posto] e ora il giocatore può SALUTARE qualsiasi oggetto, e DARE o (OFFRIRE) OMAGGIO a qualsiasi oggetto. (Poichè DAI, PAGA, OFFRI e DA sono definiti come sinonimi, il giocatore può anche PAGARE OMAGGIO A qualcosa, ma è improbabile che qualcuno noti questa minima aberrazione; i giocatori in genere sono troppo occupati a provare possibilità logiche.) *** FireAt Al solito, mostriamo il gestore generico dell'azione: [ FireAtSub; if (noun == nothing) print_ret "Cosa? Scagliare frecce a caso?"; if (NotBowOrArrow(second)) return true; print_ret "Sembra pericoloso, non trovi?"; ]; Seguito dalla grammatica: Verb 'spara' 'mira' 'scaglia' * noun -> FireAt * 'con' noun -> FireAt * -> FireAt * 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle'/'su'/'sul'/'sullo'/'sull^'/'sulla'/'sui'/ 'sugli'/'sulle'/'sopra'/'contro' noun -> FireAt * 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle'/'su'/'sul'/'sullo'/'sull^'/'sulla'/'sui'/ 'sugli'/'sulle'/'sopra'/'contro' noun 'con' noun -> FireAt * 'con' noun 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/ 'ai'/'alle'/'su'/'sul'/'sullo'/'sull^'/'sulla'/ 'sui'/'sugli'/'sulle'/'sopra'/'contro' noun -> FireAt reverse * noun 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle'/'su'/'sul'/'sullo'/'sull^'/'sulla'/'sui'/ 'sugli'/'sulle'/'sopra'/'contro' noun -> FireAt reverse; Questa è la grammatica pià complessa che scriveremo, e la prima che offre diverse opzioni per le parole che seguono il verbo iniziale. La prima righa della grammatica * -> FireAt ci permettere di scrivere soltanto SPARA (o MIRA, o SCAGLIA). Le due righe che seguono: * noun -> FireAt * 'con' noun -> FireAt supportano SPARA UNA FRECCIA e SPARA CON L'ARCO (e anche qualcosa di meno adatto, come SPARA L'ALBERO). Il gruppo di righe successivo * 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle'/'su'/'sul'/'sullo'/'sull^'/'sulla'/'sui'/ 'sugli'/'sulle'/'sopra'/'contro' noun -> FireAt accetta SPARA ALL'ALBERO, SPARA ALLA MELA, e così via. Notate che c'è soltanto un punto e virgola in tutta la grammatica, proprio alla fine. Le prime due istruzioni in FireAtSub gestiscono la prima riga della grammatica: SPADA (o MIRA, o SCAGLIA) da solo. Se il giocatore scrive solo quello, sia noun che second non conterranno nulla, e così noi rifiutiamo il tentatico on il messaggio "Cosa?...". Altrimenti abbiamo per lo meno un valore nella variabile noun, e possibilmente un valore anche in second, così generalmente controlleremo se second è qualcosa che può sparare o essere sparato, e poi rifiuteremo l'azione con il messaggio "Sembra pericoloso". Ci sono un paio di motivi per cui potreste trovare insidiosa questa grammatica. Il primo motivo è che in qualche riga la parola noun appare due volte: dovete ricordare che in questo contesto noun è un elemento di parsing (riconoscimento della frase) che corrisponde con ogni singolo oggetto visibile da parte del giocatore. Perciò la riga (semplificato): * 'a' noun 'con' noun -> FireAt corrisponde a SPARA A qualche_bersaglio_visibile CON qualche_arma_visibile; forse creando confusione, ma il valore dell'oggetto bersaglio è poi memorizzato nella variabile noun e il valore dell'oggetto arma nella variabile second. La seconda difficoltà può essere data dagli ultimi due blocchi di definizione della grammatica. Mentre nelle righe precedenti il primo oggetto individuava il bersaglio ed il secondo, se presente, l'arma, questi blocchi finali corrispondono a SPARA CON qualche_arma_visibile A qualche_bersaglio_visibile e SPARA qualche_arma_visibile A qualche_bersaglio_visibile - i due oggetti sono menzionati in ordine inverso. Se non facessimo nulla, la nostra FireAtSub sarebbe parecchio confusa a questo punto, ma noi possiamo scambiare di posto i due oggetti, rimettendoli nel solito ordine, aggiungendo quella parola reverse alla fine della riga, e allora FireAtSub funzionerà nello stesso modo in ogni caso. [N.D.T.: Estendiamo la grammatica di lancia per gestire anche LANCIA LA FRECCIA ALLA MELA Extend 'lancia' first * noun 'a'/'ad'/'all^'/'allo'/'alla'/'al'/'agli'/'ai'/ 'alle'/'su'/'sul'/'sullo'/'sull^'/'sulla'/'sui'/ 'sugli'/'sulle'/'sopra'/'contro' noun -> FireAt reverse; ] *** Talk L'ultima azione che definiamo - Talk - fornisce un semplice sistema di conversazioni già pronte, un rimpiazzo semplificante per le classiche azioni Answer, Ask e Tell. Il gestore generico TalkSub è basato strettamente su TellSub (definita nel file di libreria verblibm.h, se siete curiosi), e fa queste cose: 1. Gestisce le frasi PARLA CON ME e PARLA CON ME STESSO. 2. Controlla (a) se la creatura con cui si sta parlando ha una proprietà life, (b) se tale proprietà è predisposta a gestire l'azione Talk e (c) se l'azione Talk restituisce true. Se tutti e tre questi controlli riescono, allora TalkSub non deve fare altro; se almeno uno di questi fallisce, allora TalkSub... 3. Visualizza un generico messaggio di rifiuto "niente da dire". [ TalkSub; if (noun == player) print_ret "Niente di quello che dici può sorprenderti."; if (RunLife(noun,##Talk) ~= false) return; ! consult life[; Talk: ] print_ret "Al momento, non ti viene niente da dire."; ]; NOTA: quella seconda condizione (RunLife(noun,##Talk) ~= false) è complessa e oscura. Non preoccupatevi di capire come funziona - non non lo capiamo - e fidatevi, fa quello che deve fare. La grammatica è normale; notate l'uso di 'c//' per definire C come sinonimo per parla: [N.D.T.: notate anche l'uso di extend e first per ridefinire il verbo 'parla', già definito in ItalianG.h; in inglese Tell e Talk sono due verbi distinti, in italiano no. ] Verb 'chiacchera' 'conversa' 'c//' * 'con' creature -> Talk; Extend 'parla' first * 'a'/'ad'/'all^'/'allo'/'alla'/'al'/ 'agli'/'ai'/'alle'/'con' creature -> Talk; Ecco il più semplice gestore di Talk che abbiamo visto - è quello di Gessler il governatore. Ogni tentativo come PARLA A GESSLER otterrà la risposta "Non hai intenzione di rivolgergli la parola". life [; Talk: print_ret "Non hai intenzione di rivolgergli la parola."; ], Il gestore di Talk di Walter è solo leggermente più complesso: life [; Talk: if (location == mercato) print_ret "~Stai calmo, figliolo, e confida in Dio.~"; else print_ret "Spieghi a tuo figlio la tua visione della vita."; ], E quello di Helga è il più sofisticato (anche se lei non dice poi molto): frasi_dette 0, ! per contare gli argomenti di conversazione life [; Talk: self.frasi_dette = self.frasi_dette + 1; switch (self.frasi_dette) { 1: score = score + 1; print_ret "Ringrazi calorosamente Helga per la mela."; 2: score = score + 1; print_ret "~Ci vediamo presto.~"; default: return false; } ], Questo gestore usa la proprietà di Helga frasi_dette - non una proprietà di libreria, ma una che abbiamo inventato, come le proprietà centro_piazza.avvertimenti e palo.salutato - per tener conto di quello che è stato detto, permettendo due frammenti di conversazione (e assegnando qualche punto) prima di ricadere nell'imbarazzante silenzio di "Al momento, non ti viene niente da dire". Questa è la fine della nostra piccola favola; troverete una trascrizione e l'intero sorgente nell'appendice C. E ora è il momento di incontrare Capitan Fato!