Capitolo 6: Gugliemo Tell: raccontiamone la storia ================================================== K was King William, once governed the land L was a lady, who had a white hand Questo capitolo (e i tre che seguono) mostrerà come si procede nella creazione di quel gioco ispirato a "Guglielmo Tell" che abbiamo incontrato all'inizio della guida. Molti dei principi saranno gli stessi che abbiamo spiegato mentre creavamo Heidi e la sua foresta, così non perderemo troppo tempo a descrivere il terreno ormai familiare. "Guglielo Tell" è un gioco più lungo e più complesso, così cerceremo di arrivare più velocemente possibile a esaminare le nuove caratteristiche. Setup Iniziale ************** Il nostro punto di partenza è abbastanza simile a quello della volta scorsa. Ecco il primo abbozzo del file "Tell.inf": !============================================================================ Constant Story "Raccontami una Storia"; Constant Headline "^Semplice esempio in Inform ^di Roger Firth and Sonja Kesserich.^"; ! Traduzione di Paolo Lucchesi Release 1; Serial "020428"; ! per tener conto delle release pubbliche Constant MAX_SCORE = 4; Include "Parser"; Include "VerbLib"; Include "Replace"; !============================================================================ ! Classi !============================================================================ ! Oggetti !============================================================================ ! Oggetti del giocatore !============================================================================ ! Entry point routines [ Initialise; location = strada; lookmode = 2; ! in modo VERBOSO move arco to player; move faretra to player; give faretra worn; player.description = "Indossi i tradizionali abiti dei montanari svizzeri."; print_ret "^^ Il luogo: Altdorf, nel cantone Svizzero di Uri. Siamo nell'anno 1307, e la Svizzera è sotto il governo dell'imperatore Alberto di Asburgo. Il governatore locale - il balivo - è il gradasso Hermann Gessler, che ha posto il suo cappello su di un palo di legno nel centro della piazza principale; chiuque passi attraverso la piazza deve inchinarsi a questo odioso simbolo del potere imperiale. ^^ Sei arrivato dalla tua baita sui monti, accompagnato dal tuo figlio più giovane, per acquistare derrate. Sei un uomo fiero e indipendente, una guida e un cacciatore, conosciuto sia per la tua abilità come arciere e, forse poco saggiamente (visto che i suoi soldati sono ovunque), per essere incapace di nascondere il tuo disprezzo per il balivo. ^^ E' giorno di mercato: la città è piena di gente proveniente dai vicini villaggi.^"; ]; !============================================================================ ! Grammatica standard e estensioni Include "ItalianG"; !============================================================================ * Noterete che abbiamo abbiunto un paio di divisioni extra nel file, per aiutarci a meglio organizzare il materiale che aggiungeremo in seguito, ma la struttura generale è identica a quella del nostro primo gioco. Evidenziamo velocemente qualche piccola aggiunta: Se guardate l'intestazione del gioco, noterete due informazioni aggiuntive: "Versione" e "Numero di Serie" (oltre alle indicazioni sulle librerie usate). Raccontami una Storia Semplice esempio in Inform di Roger Firth and Sonja Kesserich. Versione 1 / Numero di Serie 020428 / Inform v6.21 Libreria 6/10 Infit Versione 2.1/ Numero di serie 030106 / (c) 2003 Giovanni Riccardi SD Questi due campi sono scritti automaticamente dal compilatore, che imposta la Versione a 1 e il Numero di Serie alla data odierna (anno/mese/giorno). Comunque, possiamo esplicitamente aggirare questo comportamente usando i comandi Release e Serial, per tener traccia di differendi versioni del nostro gioco. Tipicamente, nel tempo pubblicheremo diversi aggiornamenti al nostro gioco, con ogni versione che risolve problemi riscontrati nella versione precedente. Se qualcuno riferisce problemi che ha riscontrato nel gioco, noi vorremmo sapere esattaùmente che versione stava usando; così, piuttosto che prendere i valori di default, impostiamo i nostri propri valori. Quando verrà il momento di rilasciare una nuova versione, tutto quello che dobbiamo fare è commentare le linee precedenti e aggiungere una nuova subito dopo: !Release 1; Serial "020128"; ! Prima release beta !Release 2; Serial "020217"; ! Seconda release beta Release 3; Serial "020315"; ! Versione per concorso If-Library * Implementeremo un semplice sistema per assegnare punti quando il giocatore fa' qualcosa di giusto, quindi definiamo il valore massimo: Constant MAX_SCORE = 4; * La routine Initialise che abbiamo scritto l'ultima volta conteneva una sola istruzione, per impostare la posizione di partenza del giocatore. Lo faremo anche questa volta, ma faremo anche altre cose. * Anzitutto assegneremo il valore 2 alla variabile di libreria lookmode. Il comportamento standard di Inform per visualizzare le descrizioni delle stanze è NORMALE (brief: la descrizione viene mostrata solo quando la stanza viene visitata per la prima volta) e, cambiando questa variabile, noi la impostiamo a COMPLETA (verbose: la descrizione viene visualizzata ad ogni visita). Questa è soprattutto una questione di gusto personale, e in ogni caso non è niente di più che una convenienza; ci evita soltanto di dover ricordare di scrivere LUNGO ogni volta che proviamo il gioco. * All'inizio del gioco, vogliamo che Guglielmo sia equipaggiato con il suo arco e la sua faretra. Il modo più corretto di far ciò e quello di effettuare i necessari aggiustamenti all'albero degli oggetti con un paio di istruzioni move all'interno della routine Initialise. move arco to player; move faretra to player; e inoltre è il metodo più chiaro per piazzare oggetti nell'inventario del giocatore all'inizio dell'avventura. NOTA: aspetta! direte voi. Nel capitolo scorso per rendere un oggetto figlio di un altro oggetto tutto quello che dovevamo fare era definire l'oggetto figlio specificando internamente il suo oggetto padre alla fine dell'intestazione: Object uccello "uccellino" foresta Perche non facciamo la stessa cosa con il giocatore? Perche l'oggetto che rappresenta il giocatore è definito della libreria (piuttosto che essere parte del nostro gioco), e ha selfobj come identificatore interno; player è una variabile il cui valore è tale identificatore. Piuttosto che preoccuparsi di tutto ciò, è più facile usare le istruzioni move. C'è da fare un'ulteriore azione legata alla faretra; è un articolo d'abbigliamento e Guglielmo la sta indossando, cosa che è marcata dall'attributo worn. Normalmente l'interprete dovrebbe applicare automaticamente tale attributo gestendo comandi come INDOSSA LA FARETRA, ma siccome abbiamo assegnato direttamente la faretra al giocatore, dobbiamo anche settare l'attributo worn. Il comando give è sufficiente: give faretra worn; (Per resettare un attributo, fra l'altro, si userà l'istruzione - leggibile come "assegna alla faretra non-indossato"; Inform usa spesso ~ per come "non".) * Se il giocatore scrive "ESAMINA ME STESSO", l'interprete mostra la proprietà descrizione dell'oggetto player. Il valore di default è "Hai sempre lo stesso bell'aspetto", ormai un clichè nel mondo dei giochi Inform. E' però facile da cambiare non appena realizzate che, siccome le proprietà di un oggetto sono variabili, potete assegnarli nei nuovi valori, esattamente come assegnate nuovi valori a location e lookmode. L'unico problema è quello di specificare la giusta sintassi; non potete scrivere semplicemente: description = "Indossi i tradizionali abiti dei montanari svizzeri."; perchè ci sono dozzine di oggetti nel gioco, ognuno con la sua proprietà description; dovrete essere un po' più espliciti. Dovete scrivere player.description = "Indossi i tradizionali abiti dei montanari svizzeri."; * Finalmente, la routine Initialise finisce con una lunga istruzione print_ret. Siccome l'interprete chiama Initialise proprio all'inizio del gioco, quello è il punto in cui tutto questo materiale viene stampato, in modo da fungere da preambolo e da contestualizzare la scena prima che il gioco vero e proprio inizi. Infatti, tutto ciò che volete voglia venir impostato e fatto all'inizio del gioco deve andare nella routine Initialise. Il gioco non verrà compilato in questo stato, poichè contiene riferimenti a oggetti che ancora non abbiamo definito. In ogni caso, non abbiamo intenzione di costruire il gioco a livelli come abbiamo fatto l'ultima volta, ma piuttosto vogliamo costruirlo in frammenti legati logicamente. Per vedere (e, se volete, per scrivere) l'intero sorgente, andate alla storia "Guglielmo Tell", nel capitolo... Classi di Oggetti ***************** Vi ricordate come abbiamo definito le stanze in "Heidi"? Il nostro primo tentativo è iniziato così: Object davanti_baita "Di fronte a una baita" with description "Ti trovi davanti a una baita. Verso est si stende la foresta.", has light; Object foresta "Nel folto del bosco" with description "Attraverso la folta vegetazione, ti pare di scorgere un edificio verso ovest. Un sentiero porta verso nord-est.", has light; ... e abbiamo spiegato come più o meno ogni stanza abbia bisogno dell'attributo light, o sltrimenti il giocatore brancolerebbe letteralmente nel buio. E' una piccola scocciatura dover specificare lo stesso attributo tutte le volte; sarebbe più pulito se potessimo dire che tutte le stanze sono illuminate. Così possiamo scrivere questo: Class Room has light; Room davanti_baita "Di fronte a una baita" with description "Ti trovi davanti a una baita. Verso est si stende la foresta.", has ; Room foresta "Nel folto del bosco" with description "Attraverso la folta vegetazione, ti pare di scorgere un edificio verso ovest. Un sentiero porta verso nord-est.", has ; ... Abbiamo fatto quattro cose: 1. Abbiamo detto che alcuni oggetti del nostro gioco saranno definiti dalla parola Room, piuttosto che dalla più generale parola Object. In effetti abbiamo insegnato ad Inform una nuova parola che verrà usata per definire gli oggetti, e che da ora in poi potremo usare come se avesse sempre fatto parte del linguaggio. 2. Abbiamo inoltre detto che ogni oggetto che definiremo con la parola Room avrà automaticamente l'attributo light. 3. Abbiamo cambiato il modo in cui definiamo i quatto oggetti "stanza", iniziando la loro definizione con la parola Room. Il resto della definizione per questo oggetti - l'interstazione, il blocco delle proprietà, il blocco degli attributi e il punto e virgola finale - rimangono gli stessi; con un'eccezione: 4. Non dobbiamo specificare ogni volta l'attributo light; ogni oggetto Room lo avrà automaticamente. Una classe è una famiglia di oggetti strettamente legati, che si comportano nello stesso modo. Ogni proprietà definita per una classe, e ogni attributo definito per una classe, sono automaticamente assegnati ad ogni oggetto che viene specificatamente definito come appartenente a quella classe; questo processo di acquisizione dovuta all'essere membro di una classe è chiamato ereditarietà. Nel nostro esempio abbiamo definito una classe Room con l'attributo light, e dopo abubamo specificato quattro oggetti, ognuno dei quali era memtro di tale classe, e ognuno dei quali riceveva l'attributo light solo per essere membro della classe. Perchè ci siamo dati tanto da fare? Per tre ragioni principali: * Spostando le parti di definizione comuni dagli oggetti alla classe che tali oggetti condividono, la definizione di tali oggetti diviene più corta e più semplice. Anche se abbiamo cento stanze, dovremmo definire l'attributo light una sola volta. * Creando una parola specializzata che identifica la nostra classe di oggetti, rendiamo il nostro file sorgente più facile da leggere. Piuttosto che anonimi Object che possono essere qualsiasi cosa, riconosceremo immediatamente che alcuni oggetti sono stanze (e altri appartengono ad altre classi differenti che creeremo presto). * Raccogliendo le definizioni comuni in un solo posto, sarà per noi molto più facile apportare modifiche di ampio respiro. Se dobbiamo fare qualche modifica alla definizione di tutte le stanze, modificheremo soltando la classe Room, e tutti i membri di tale classe erediteranno le modifiche. Per questa ragione, l'uso delle classi è una tecnica incredibilmente potente, più facile di quanto possa sembrare, e vale la pena di impadronirsente. DFa ora, definiremo classi di oggetti ogni volta che conviene (il che vuol dire generalmente quando due o più oggetti devono comportarsi nella stessa maniera). Vi potreste chiedere: supponiamo che io voglia definire una stanza che, per qualche ragione, non è illuminata; posso continuare ad usare la classe Room? Certo che potete: Room cantina "Cantina buia" with description "La tua torcia mostra soltanto muri coperti da ragnatele.", has ~light; Ciò illustra un'altra simpatica caratteristica dell'ereditarietà: la definizione di un oggetto può annullare la definizione di classe. La classe dice "has light", ma l'oggetto dice "has ~light" (non ha light) e l'oggeto vinve. La cantina è buia, e il giocatore avrà bisogno di una torcia per vedere cosa si trova all'interno. In effetti, per ogni oggetti sia il blocco delle proprietà che il blocco degli attributi sono opzionali e possono essere omessi se non c'è niente da specificare. Ora che l'attributo luce è assegnato automaticamente e che non ci sono altri attributi da assegnare, la parola "has" può essere trascurata: Class Room has light; Room davanti_baita "Di fronte a una baita" with description "Ti trovi davanti a una baita. Verso est si stende la foresta."; Room foresta "Nel folto del bosco" with description "Attraverso la folta vegetazione, ti pare di scorgere un edificio verso ovest. Un sentiero porta verso nord-est."; ... Noterete come, se un oggetto non ha un blocco di attributi, il punto e virgola che chiude la sua definizione si sposta alla fine della sua ultima proprietà. *** Una classe per le scenografie Useremo la classe Room in "Guglielmo Tell", e qualche altra classe. Ecco una classe Prop (oggetto di scena), utile per oggetti scenici il cui solo ruolo è quello di star fermi sullo sfondo aspettando soltanto che il giocatore voglia esaminarli: Class Prop with before [; Examine: return false; default: print_ret "Non hai bisogno di preoccuparti ", (artdi) self, "."; ], has scenery; Vedete che tutti gli oggetti di questa classe ereditano l'attributo scenery, che significa che questi oggetti non sono menzionati nella descrizione della stanza. Più interessante è la proprietà before. più complessa di ciò che abbiamo visto in precedenza. Vi ricorderete che la prima volta che l'abbiamo incontrata aveva questo aspetto: before [; Listen: print "Sembra spaventato e bisognoso d'aiuto.^"; return true; ], Lo scopo della before originale era quello di intercettare le azioni Listen, lasciando in pace ogni altra azione. Il ruolo della before nella classe Prop è più ampio: deve intercettare (a) le azioni Examine, e (b) tutte le altre. Se l'azione è Examine, allore il return false scritto esplicitamente indica che l'azione procede normalmente. Se l'azione è default - ovvero una fra tutte quelle non esplicitamente elencate - allora viene eseguita l'istruzione print_ret, dopo di che l'interprete non fa altro. Così un oggetto Prop può essere esaminato, ma ogni altra azione rivolta ad esso si risolve in un messaggio di "non preoccuparti". Questo messaggio è inoltre più elaborato di altri messaggi visti fino ad ora. L'istruzione che lo produce è print_ret "Non hai bisogno di preoccuparti ", (artdi) self, "."; che può essere letta in questo modo: 1. visualizza la stringa "Non hai bisogno di preoccuparti ", 2. visualizza la proposizione articolata derivata da "di" e adatta, per numero e genere, all'oggetto che stiamo trattando (e quindi visualizza "del", o "della", o "dei"...), seguita da uno spazio e il nome dell'oggetto, 3. visualizza un punto, 4. torna a capo e esci dalla procedura ritornando true, come sempre per un'istruzione print_ret. Le cose interessanti che sono dimostrate da questa istruzione sono: * Le istruzioni print e print_ret possono visualizzare più di un singolo frammento d'informazione: possono visualizzare una lista di oggetti separati da virgole. L'istruzione termina con un punto e virgola, al solito. * Oltre che stringhe posso visualizzare anche nomi di oggetti: per fare qualche semplice esempio preso dal nostro primo gioco, (the) nido visualizzarebbe "il nido di uccelli", (The) nido visualizzerebbe "Il nido di uccelli", (a) nido visualizzarebbe "un nido di uccelli", (artdi) nido visualizzerebbe "del nido di uccelli", (inda) nido visualizzerebbe "nel nido di uccelli" e (name) nido visualizzerebbe semplicemente "nido di uccelli". Questo uso di una parola tra parentesi, indicando in questo modo come l'interprete dovrebbe visualizzare l'oggetto riferito dall'indicatore che segue, è chiamata regola di stampa. * C'e' una variabile di libreria self, veramente conveniente quando si usa una classe, che contiene sempre un'indicatore riferito all'oggetto costante. Usando questa variabile nella nostra istruzione print_ret, ci assicuriamo che il messaggio contenga sempre il nome dell'oggetto appropriato. Vediamo un esempio pratico di tutto ciò; ecco un oggetto Prop da "Gugliemo Tell": Prop "porta meridionale" with name 'cancello' 'porta' 'meridionale', description "Nelle mura della città si apre una larga porta. Il pesante cancello di legno ora è aperto.", ... Se il giocatore scrive ESAMINA LA PORTA, vedrà "Nelle mura della..."; se egli scrive CHIUDI LA PORTA, allora la proprietà before della porta entrerà in funzione e visualizzarà "Non hai bisogno di preoccuparti della porta meridionale", prendendo il nome dell'oggetto dalla variabile self. Il motivo per cui facciamo tutto questo, piuttosto che creare un semplice oggetto scenery come l'albero e la baita di Heidi, e quello di gestire il verbo ESAMINA per avere maggior realismo, e allo stesso tempo far capire chiaramente al giocatore che provare altri verbi sarebbe solo uno spreco di tempo. *** Una classe per il mobilio L'ultima classe per ora - parleremo del prossimo capitolo delle classi Arrow e NPC - è per gli oggetti di tipo mobilio. Se marchi un oggetto con l'attributo static, un tentativo di prenderlo darà come risultato la frase "E' fisso al suo posto" - accettabile nel caso del ramo di Heidi (che effettivamente possiamo supporre essere parte dell'albero), un po' meno nel caso di oggetti che sono soltanto ingombranti e pesanti. Questa classe Furniture potrà talvolta essere più appropriata: Class Furniture with before [; Take,Pull,Push,PushDir: print_ret (The) self, " è troppo pesante."; ], has static supporter; La sua struttura è simile a quella della classe Prop: qualche attributo appropriato e una proprietà before per intercettare le azioni dirette verso di essa. Ancora una volta visualizziamo un messaggio che è "personalizzato" per l'oggetto interessato usando la regola di stampa (The) self. Questa volta intercettiamo quattro azioni; avremmo potuto scrivere la proprietà in questo modo: before [; Take: print_ret (The) self, " è troppo pesante."; Pull: print_ret (The) self, " è troppo pesante."; Push: print_ret (The) self, " è troppo pesante."; PushDir: print_ret (The) self, " è troppo pesante."; ], ma siccome vogliamo la stessa riposta ogni volta, è meglio accorpare tutte quelle azioni un una sola lista, separate da virgole. Se ve lo state chiedendo, PushDir è l'azione che viene eseguita in risposta a comandi come SPINGI IL TAVOLO VERSO NORD. Incidentalmente, un altro vantaggio nel definire classi come queste è che probabilmente potrete riusarle nel vostro prossimo gioco. Ora che qualche definizione di classe è al suo posto, possiamo procedere definendo qualche stanza e qualche oggetto reali.