Inform e Glulx/RUINS, l'avventura comincia/Il primo tesoro (speriamo che non sia un anello)

Wikibooks, manuali e libri di testo liberi.
Indice del libro

Siete pronti a dirigervi verso sud, impazienti di vedere cosa nasconde l’imponente porta di pietra, ma c’è uno strano oggetto che attira la vostra attenzione: una statuetta minacciosa di uno spirito pigmeo, di aspetto quasi grottesco, che giace per terra alla vostra destra. Se non fosse per quel serpente intorno al collo, potreste usarla come soprammobile, ma decidete comunque di prenderla e riporla nella cassa, se non altro per non tornare a mani vuote da quelli della fondazione. Posate quindi a terra tutti gli oggetti che possedete, ad eccezione dell'elefantiaca macchina fotografica a lastre che sistemate invece accanto alla lampada al sodio. Mettete poi in posa la statuetta... et voilà, la foto è fatta. E mentre ritornate verso la superficie, vi chiedete quando inventeranno la Polaroid.


Il primo tesoro (speriamo che non sia un anello)[modifica]

In Inform esiste la possibilità di creare delle azioni nuove. Quelle elencate nell’appendice A infatti, sono "solo" quelle standard ma se, come nel nostro caso, volessimo fotografare un oggetto? Come potete vedere, questo verbo non c’è, ed ecco perché proprio verso la fine del listato troviamo la seguente riga di codice:

Verb 'fotografa' * noun                           -> Photograph;

che può essere così interpretata: il verbo (Verb) FOTOGRAFA ('fotografa') [OGGETTO] (* noun) equivale all’azione PHOTOGRAPH (-> Photograph;). Ricordatevi però, che ogni nuova azione ha bisogno di un'apposita subroutine definita come [nome_azione + sub] (nel nostro specifico caso PhotographSub):

[ PhotographSub;
        if (camera notin player) "Non puoi farlo senza la tua macchina 
            fotografica.";
        if (noun == player) "Meglio di no. Non ti sei fatto la barba da 
                             quando hai lasciato il Messico.";
        if (children(player) > 1)
            "Fare fotografie @`e una cosa impegnativa, che necessita 
             dell'uso di entrambe le mani. Devi posare qualcosa.";
        if (location == Forest) "In questa foresta inzuppata di pioggia? 
                                 Meglio di no.";
        if (location == thedark) "@`E assolutamente troppo buio.";
        if (AfterRoutines()) return;
        print_ret "Prepari l'elefantiaca macchina fotografica a lastre, 
                   sistemi la lampada al sodio e metti pazientemente in          
                   posa ",(the) noun, ".";
        ];

Se il giocatore non possiede la macchina fotografica (camera notin player) Inform stampa a video il messaggio corrispondente ("Non puoi farlo senza la tua macchina fotografica."); se prova invece a fotografare se stesso ("fotografa me stesso") viene stampato a video il messaggio "Meglio di no. Non ti sei fatto la barba da quando hai lasciato il Messico.", un pizzico di umorismo che mette però in risalto un’altra importante caratteristica di Inform: il player. Esso infatti, viene considerato come un oggetto vero e proprio, che di default ha la seguente descrizione:

>esamina me stesso
Hai sempre lo stesso bell'aspetto.

Se poi il giocatore possiede più di un oggetto (children(player) > 1), ecco che diventa impossibile fare delle fotografie. I children (in inglese "bambini" o "figli") del player sono proprio tutti gli oggetti che lui possiede. E se avete capito la descrizione della lampada al sodio, dovreste già sapere che gli oggetti del player hanno come parent il player stesso. Chiaro, no?

Dal momento però, che questo povero archeologo non si è fatto la barba da quando ha lasciato il Messico, non credo che questo messaggio corrisponda proprio alla verità. Come vedete, è molto importante personalizzare "l’ambiente", se non altro per rendere più veritiera l’avventura in sé. Ecco quindi come possiamo modificare anche questo messaggio:

[ Initialise;
        location = Forest;
        .
        .
        .
        player.description = "Assomigli proprio a Matusalemme.";
        .
        .
        .
        "^^^Dopo giorni di inutili ricerche, passati senz'acqua 
         attraversando i rovi della foresta, alla fine la tua pazienza 
         @`e stata ricompensata: hai fatto una scoperta!^";
        ];

ottenendo così il seguente risultato:

>esamina me stesso
Assomigli proprio a Matusalemme.

Volendo, è perfino possibile aggiungere delle parti del corpo:

.
.
.
Object  tiny_claws "suono di piccoli artigli" thedark...

Object nose "naso"
  with name 'naso',
       description 
          "A punta e pieno di lentiggini.";
           
[ Initialise;
        location = Forest;
        selfobj.add_to_scope = nose;
        move map to player;
        move sodium_lamp to player;
        move dictionary to player;
        StartDaemon(sodium_lamp);
        thedark.description =
            "L'oscurit@`a intorno a te @`e opprimente e ti senti
             quasi soffocare.";
        "^^^Dopo giorni di inutili ricerche, passati senz'acqua  
         attraversando i rovi della foresta, alla fine la tua pazienza 
         @`e stata ricompensata: hai fatto una scoperta!^";
        ];

In questo esempio abbiamo definito il naso. Di per sé, l’oggetto nose non ha nulla di particolare, mentre la riga selfobj.add_to_scope = nose; contenuta nella funzione Initialise è quella che dice a Inform di assegnare l’oggetto in questione al player (selfobj). Il risultato che si ottiene è il seguente:

>esamina il mio naso
A punta e pieno di lentiggini.

Nel caso in cui si vogliano aggiungere più parti del corpo, si deve allora ricorrere al seguente stratagemma:

.
.
.
Object  tiny_claws "suono di piccoli artigli" thedark...

Object nose "naso"
  with name 'naso',
       description 
          "A punta e pieno di lentiggini.";

Object hair "capelli"
  with name 'capelli' 'capello',
       description
          "Li hai persi tutti quando eri piccolo.";

[ IncludeBodyParts; PlaceInScope(nose); PlaceInScope(hair); ];
         
[ Initialise;
        location = Forest;
        selfobj.add_to_scope = IncludeBodyParts;
        move map to player;
        move sodium_lamp to player;
        move dictionary to player;
        StartDaemon(sodium_lamp);
        thedark.description =
            "L'oscurit@`a intorno a te @`e opprimente e ti senti
             quasi soffocare.";
        "^^^Dopo giorni di inutili ricerche, passati senz'acqua  
         attraversando i rovi della foresta, alla fine la tua pazienza 
         @`e stata ricompensata: hai fatto una scoperta!^";
        ];

Quella che viene adesso associata al player è l’intera funzione IncludeBodyParts al cui interno vengono resi visibili, tramite l’istruzione PlaceInScope, il naso e i capelli. Ecco allora il risultato:

>esamina i miei capelli
Li hai persi tutti quando eri piccolo.

>esamina il mio naso
A punta e pieno di lentiggini.

Provare per credere.

La rimanente parte del codice dovrebbe essere ormai abbastanza chiara, tranne che per quella strana istruzione denominata AfterRoutines. Una cosa molto importante da tenere a mente quando si descrive una nuova azione, è a quale gruppo essa deve appartenere (in genere 2 o 3). Ora, come dice Paolo Lucchesi[1], se l’azione non fa praticamente nulla (ed è quindi molto semplice), appartiene al gruppo 3 e dipende dalla proprietà before:

[ XyzzySub; "Non succede nulla."; ];

Verb "xyzzy"    *                                -> Xyzzy; 

Se invece l’azione compie delle operazioni complesse, appartiene allora al gruppo 2 e dipende dalla proprietà after. Quindi, dopo quanto detto, è facile dedurre che l’azione Photograph fa parte del gruppo 2, e l’istruzione AfterRoutines si prende cura di tutto quello che avviene in questa azione quando fa parte della proprietà after di un oggetto qualsiasi: se ritorna true, tutto è andato bene, se invece ritorna false, qualcosa è andato storto:

Object -> statuette "statuetta pigmea"
  with  name 'serpente' 'maya' 'pigmea' 'spirito' 'preziosa' 'statuetta' 
             'statua',
        initial
            "C'@`e una preziosa statuetta maya qui!",
        description
            "@`E una statuetta minacciosa di uno spirito pigmeo di 
             aspetto quasi grottesco. Ha un serpente intorno al collo.",
        before [;
            Take, Remove:
                if (self in packing_case)
                    "@`E meglio aspettare che sia la fondazione Carneige 
                     a disimballare dalla cassa un manufatto cos@`i 
                     prezioso.";
                if (self.photographed_in_situ == false)
                    "Questi sono gli anni '30 e non i tempi andati. 
                     Prendere un manufatto senza prima registrarlo 
                     equivale a un saccheggio.";
            Photograph:
                if (self has moved) "Cosa? Vorresti contraffare una 
                    registrazione archeologica?";
                if (self.photographed_in_situ) "Non di nuovo.";
            ],
        after [;
            Insert:
                if (second == packing_case) {
                    score = score + self.cultural_value;
                    if (score == MAX_SCORE) deadflag = 2;
                    "Depositat", (genderandnumber) noun, " al sicuro!";
                    }
            Photograph:
                self.photographed_in_situ = true;
            ],
        cultural_value 5,
        photographed_in_situ false,
  has   female;

La variabile locale photographed_in_situ vale inizialmente false. Quando il giocatore riesce in qualche modo a fotografare la statuetta, il valore della variabile diventa true. Ora, se omettiamo la riga di codice relativa alla AfterRoutines() in PhotographSub, ecco cosa accade:

Corridoio in pendenza
Un corridoio basso e squadrato va da nord verso sud, inclinandosi verso la fine.

Il passaggio è bloccato da una massiccia porta di pietra gialla.

C'è una preziosa statuetta maya qui!

>prendi la statuetta
Questi sono gli anni '30 e non i tempi andati. Prendere un manufatto senza prima registrarlo equivale a un saccheggio.

>fotografa la statuetta
Fare fotografie è una cosa impegnativa, che necessita dell'uso di entrambe le mani. Devi posare qualcosa.

>posa tutto eccetto la macchina
chiave di pietra: Posata.
dizionario maya di Waldeck: Posato.
mappa di Quintana Roo: Posata.

>fotografa la statuetta
Prepari l'elefantiaca macchina fotografica a lastre, sistemi la lampada al sodio e metti pazientemente in posa la statuetta pigmea.

>prendi la statuetta
Questi sono gli anni '30 e non i tempi andati. Prendere un manufatto senza prima registrarlo equivale a un saccheggio.

Nonostante la statuetta sia stata fotografata, non si può ancora prendere. Ecco quindi spiegato il motivo della presenza della AfterRoutines: senza, viene ignorata l’azione Photograph della proprietà after dell’oggetto statuette, la variabile locale photographed_in_situ continua a valere false e di conseguenza la statuetta continua a non poter essere fotografata (e a non poter essere presa dal giocatore)[2].

Occupiamoci adesso del punteggio:

Constant MAX_SCORE = 30;  
.
.
.
Object -> statuette "statuetta pigmea"
        .
        .
        .
        after [;
            Insert:
                if (second == packing_case) {
                    score = score + self.cultural_value;
                    if (score == MAX_SCORE) deadflag = 2;
                    "Depositat", (genderandnumber) noun, " al sicuro!";
                    }
                    .
                    .
                    .
                    ],
        cultural_value 5,
        .
        .
        .
  has   female;
.
.
.
[ PrintRank;
        print ", guadagnando il rango di ";
        if (score == 30) "Direttore della Fondazione Carneige.";
        if (score >= 20) "Archeologo.";
        if (score >= 10) "Rigattiere.";
        if (score >= 5) "Esploratore.";
        "Turista.";
        ];

La costante MAX_SCORE determina il punteggio massimo, mentre la variabile locale cultural_value determina il punteggio della statuetta. Nella proprietà after, l’azione Insert ("posa la statuetta nella cassa) incrementa il valore della variabile di libreria score di 5 (il punteggio della statuetta); se score vale 30, allora il nostro archeologo ha raccolto tutti i tesori e ha finito la sua avventura nel migliore dei modi (non è stato cioè ucciso ma ha invece vinto). PrintRank è una funzione di libreria che, a seconda del punteggio, stampa a video il relativo messaggio; questo avviene però, solo se il gioco termina o se si danno i comandi "punteggio" e "punteggio pieno".

Esiste poi, una modalità completa del punteggio, nel senso che è possibile stabilire, per un’azione qualsiasi, quanti punti assegnarle. Se, ad esempio, vogliamo dare 5 punti al giocatore quando mangia il fungo e 10 punti quando posa il bozzolo nel raggio di sole, ecco cosa dobbiamo fare:

  • Per prima cosa, bisogna definire un array di nome task_scores nel seguente modo:
Constant Story "RUINS";
.
.
.
Constant MAX_CARRIED = 7;
Constant TASKS_PROVIDED;
Constant NUMBER_TASKS = 2;
Constant MAX_SCORE = 30;

Array    task_scores -> 5 10;

Il numero 5 è il punteggio che viene assegnato alla prima azione (quando il giocatore mangia il fungo) mentre il numero 10 viene assegnato alla seconda azione (quando il giocatore posa il bozzolo nel raggio). A questo array, si associano sempre le costanti NUMBER_TASKS (che contiene, a sua volta, il numero dei punteggi totali - in questo caso 5 e 10  ➨  2) e TASKS_PROVIDED. Ricordatevi inoltre, che l’array deve avere una lunghezza massima di 255 celle (da 0 a 254) e che i punteggi non possono essere negativi o maggiori di 255.

  • Occorre poi, modificare l’azione Eat dell’oggetto mushroom come segue:
Eat:
    Achieved(0);
    steps.rubble_filled = false;
    "Lo sgranocchi ad un angolo, incapace di capire l'origine di un 
     gusto cos@`i acre, distratto dal volo di un macao sopra la tua 
     testa che sembra quasi un'esplosione nel sole. Il battito delle 
     sue ali @`e quasi assordante, e delle pietre crollano una 
     sull'altra.";

L’istruzione Achieved(0); è proprio quella che assegna 5 punti alla prima azione, e li va a prelevare dalla posizione 0 dell’array task_scores. Una modifica abbastanza simile va fatta per l’azione Insert dell’oggetto Square_Chamber:

Insert:
    if (noun == eggsac && second == sunlight) {
        achieved(1);
        remove eggsac;
        move stone_key to self;
        "Lasci cadere il bozzolo nel bagliore solare. Ribolle 
         oscenamente, si dilata e poi scoppia. Centinaia di piccoli 
         insetti corrono in tutte le direzioni nell'oscurit@`a; gli
         spruzzi di melma e una curiosa chiave di pietra gialla sono 
         tutto ci@`o che rimane sul pavimento.";
         }

L’unica differenza, sta nel fatto che ora l’istruzione Achieved va a prelevare il valore 10 anziché il valore 5. Per verificare che le azioni di assegnamento dei punteggi abbiano avuto effettivamente luogo, basta testarle con l’istruzione task_done:

if (task_done->0 == 0)... ! se i 5 punti non sono stati assegnati...
if (task_done->0 == 1)... ! se i 5 punti sono stati assegnati...
if (task_done->1 == 0)... ! se i 10 punti non sono stati assegnati...
if (task_done->1 == 1)... ! se i 10 punti sono stati assegnati...

Vediamo allora in pratica quanto è stato appena detto:

>prendi il fungo
Hai raccolto abilmente il fungo, senza staccarlo dal suo gambo sottile.

>mangia il fungo
Lo sgranocchi ad un angolo, incapace di capire l'origine di un gusto così acre, distratto dal volo di un macao sopra la tua testa che sembra quasi un'esplosione nel sole. Il battito delle sue ali è quasi assordante, e delle pietre crollano una sull'altra.

[Il tuo punteggio è appena aumentato di cinque punti.]

>giù

La Sala Quadrata
Sei in una sala di pietra oscura e profonda, larga circa dieci metri. Un raggio di sole, proveniente dalla cima della scalinata, la illumina diffusamente, ma le ombre del livello più basso rivelano dei passaggi verso est e sud, che conducono verso la più profonda oscurità del Tempio.

Delle iscrizioni scolpite riempiono le pareti, il pavimento e il soffitto.

>e

Tana Del Verme
Un groviglio di cunicoli disordinati come una ragnatela si dirige verso le fessure tra le pietre. I soli abbastanza larghi da poterci strisciare dentro sono quelli che si dirigono verso l'alto, a nordest e a sud.

Un bozzolo bianco e scintillante, grande come un pallone da spiaggia, è appiccicato alla fessura di una parete.

>prendi il bozzolo
Bleah!

>o

La Sala Quadrata

Delle iscrizioni scolpite riempiono le pareti, il pavimento e il soffitto.

>posa il bozzolo nel raggio
Lasci cadere il bozzolo nel bagliore solare. Ribolle oscenamente, si dilata e poi scoppia. Centinaia di piccoli insetti corrono in tutte le direzioni nell'oscurità; gli spruzzi di melma e una curiosa chiave di pietra gialla sono tutto ciò che rimane sul pavimento.

[Il tuo punteggio è appena aumentato di dieci punti.]

>punteggio
Finora hai totalizzato 15 punti su 30 possibili, in 8 turni, guadagnando il rango di Rigattiere.

>

Bisogna anche considerare, come dice lo stesso Paolo Lucchesi, la modalità a punteggio pieno, dove è necessario inserire la funzione di libreria PrintTaskName:

[ Initialize...
.
.
.
[ TitlePage...
.
.
.
[ PrintTaskName achievement;
  switch(achievement)
  {   0: "hai mangiato il fungo";
      1: "hai trovato la chiave di pietra";
  }
];

In poche parole, se il giocatore ha mangiato il fungo e ha posato il bozzolo nel raggio di sole, ecco cosa stampa a video Inform con il comando "punteggio pieno":

>punteggio pieno
Finora hai totalizzato 15 punti su 30 possibili, in 8 turni, guadagnando il rango di Rigattiere.

Il punteggio è così composto:

    5 hai mangiato il fungo
   10 hai trovato la chiave di pietra

   15 in totale (su 30 possibili)

>

A ogni modo, non siete obbligati a supportare questa funzione nella gestione del punteggio in modalità completa; se il giocatore, durante il gioco, dà questo comando senza che la funzione PrintTaskName sia stata implementata, Inform stampa a video lo stesso messaggio previsto per il comando "punteggio".

Non sempre le azioni effettuate dal giocatore meritano di essere premiate. Ecco allora come procedere per far sì che il punteggio possa anche essere diminuito:

  • La prima cosa da fare è quella di definire un array di nome task_scores nel seguente modo:
Constant Story "RUINS";
.
.
.
Replace TaskScore;

Constant MAX_CARRIED = 7;
Constant TASKS_PROVIDED;
Constant NUMBER_TASKS = 2;
Constant MAX_SCORE = 30;

Array task_scores --> 10 (-5);
  • Dal momento che è presente l’istruzione replace, occorre poi ridefinire la funzione di libreria TaskScore:
.
.
.
[ PrintRank;...

[ TaskScore i; return task_scores-->i; ];

Ecco fatto. Ora potete sia punire che premiare il giocatore a seconda dell’azione da lui effettuata:

>prendi il fungo
Hai raccolto abilmente il fungo, senza staccarlo dal suo gambo sottile.

>mangia il fungo
Lo sgranocchi ad un angolo, incapace di capire l'origine di un gusto così acre, distratto dal volo di un macao sopra la tua testa che sembra quasi un'esplosione nel sole. Il battito delle sue ali è quasi assordante, e delle pietre crollano una sull'altra.

[Il tuo punteggio è appena aumentato di dieci punti.]

>giù

La Sala Quadrata
Sei in una sala di pietra oscura e profonda, larga circa dieci metri. Un raggio di sole, proveniente dalla cima della scalinata, la illumina diffusamente, ma le ombre del livello più basso rivelano dei passaggi verso est e sud, che conducono verso la più profonda oscurità del Tempio.

Delle iscrizioni scolpite riempiono le pareti, il pavimento e il soffitto.

>e

Tana Del Verme
Un groviglio di cunicoli disordinati come una ragnatela si dirige verso le fessure tra le pietre. I soli abbastanza larghi da poterci strisciare dentro sono quelli che si dirigono verso l'alto, a nordest e a sud.

Un bozzolo bianco e scintillante, grande come un pallone da spiaggia, è appiccicato alla fessura di una parete.

>prendi il bozzolo
Bleah!

>o

La Sala Quadrata

Delle iscrizioni scolpite riempiono le pareti, il pavimento e il soffitto.

>posa il bozzolo nel raggio
Lasci cadere il bozzolo nel bagliore solare. Ribolle oscenamente, si dilata e poi scoppia. Centinaia di piccoli insetti corrono in tutte le direzioni nell'oscurità; gli spruzzi di melma e una curiosa chiave di pietra gialla sono tutto ciò che rimane sul pavimento.

[Il tuo punteggio è appena diminuito di cinque punti.]

>

Supponendo per assurdo che il bozzolo debba in qualche modo rimanere intatto, se il giocatore lo lascia cadere nel bagliore solare il punteggio viene diminuito di 5 punti.

Definendo, infine, la costante NO_SCORE nel seguente modo:

Constant Story "RUINS";
Constant Headline
       "^Un esempio di lavoro interattivo.^
        Copyright (c) 1999 di Graham Nelson.^
         Traduzione e adattamenti di Vincenzo Scarpa e Raffaello 
         Valesio (c) 2002-2003 su permesso dell'autore.^^";

Constant MAX_CARRIED = 7;  
Constant NO_SCORE;    
.
.
.

è possibile disabilitare il punteggio, che non viene più segnalato sulla status line. Inoltre, perdono la loro efficacia le funzioni di libreria PrintRank; e PrintTaskName;

Note[modifica]

  1. Autore di "La pietra della Luna", Paolo Lucchesi è anche il creatore di MAC (Mistery Adventure Creator), uno strumento di creazione per le avventure testuali rivolto ai meno esperti della programmazione. Ulteriori informazioni potete trovarle all’indirizzo http://www.paololucchesi.it/).
  2. Cliccate qui per scaricare il listato di questo esercizio (il file 4.07.inf).