Appendice B - Come funziona Idra


 

indice L'idea di Idra

Per molto tempo la principale tecnica narrativa su computer è stata quella del gioco di avventura basato su comandi di testo, introdotto negli anni '70 da "Adventure" e reso popolare soprattutto dalla Infocom. In questi giochi il lettore è libero di scrivere ciò che vuole e il programma risponde a tono:
Cosa devo fare? Attacca il drago
 
Solleticato dalla tua lancia, il drago starnutisce riducendoti in cenere.
Dopo anni di relativo abbandono, vi è un certo ritorno di interesse per questo genere di giochi, limitato tuttavia a un relativamente ristretto numero di appassionati. L'impegno richiesto e la necessità di scrivere parecchio alla tastiera lo rendono infatti poco attraente per chi sia abituato a comandare il computer a base di clic (ciò è naturalmente una semplificazione, ma non è questo il luogo per un'analisi approfondita).

All'estremo opposto troviamo i libri-gioco di carta: una struttura inflessibile a base di scelte preconfezionate, che tuttavia pare gradita da molti lettori (d'altra parte un libro può essere appassionante anche senza scelte, nella veste classica di racconto immutabile, da leggersi dalla prima all'ultima pagina). Un tipico libro-gioco presenta alternative di questo tipo:

- Se vuoi attaccare il drago, vai al paragrafo 83.
- Se cerchi di parlare col drago, vai al paragrafo 132.
- Se ti nascondi dietro una colonna, vai al paragrafo 12.
Idra è sostanzialmente basato su questa seconda impostazione, ma aggiunge una flessibilità assai superiore rispetto a quella offerta dalla stampa su carta: sia le scelte disponibili che il contenuto stesso di una pagina possono infatti dipendere dal comportamento precedente del lettore.

Ho progettato Idra anche per togliermi un dubbio: i giochi di avventura basati su testo (cioè senza grafica) sono poco popolari perché la gente non ha voglia di leggere, o semplicemente perché non ha voglia di scrivere? Ho deciso di provare a togliere questa seconda necessità per vedere quale possa essere il gradimento dei lettori.
 

indice Perché JavaScript?

Anche se col solo HTML delle pagine Web sarebbe possibile scrivere una primitiva forma di racconto a scelte, si tratterebbe comunque di un'opera poco flessibile, essendo basata su scelte fisse; per fare qualcosa di più occorre fare ricorso a un linguaggio di programamzione.

L'esperienza dei miei giochi di avventura scritti negli anni '80, strettamente legati al linguaggio Basic nella versione Microsoft, mi ha insegnato a diffidare degli strumenti prodotti da una sola azienda. Per Idra volevo garantire la massima portabilità su diversi sistemi, l'indipendenza da strumenti di un dato produttore e, possibilmente, una ragionevole durata nel tempo.

Linguaggi dinamici come il Perl o il Tcl/Tk sono molto interessanti per questo genere di applicazioni, ma richiedono che il lettore abbia installato il relativo interprete; è inoltre difficile garantirne il corretto funzionamento con diverse versioni e diversi sistemi operativi.

Java ha il vantaggio della disponibilità di un interprete nei principali browser, ma trovo il linguaggio alquanto pesante e burocratico sia per il programmatore (cioè io) che, soprattutto, per l'autore dei racconti. Inoltre, cosa importante, Java non è dinamico: codice e dati sono rigidamente separati; ciò significa che l'autore sarebbe obbligato a compilare il programma a ogni modifica del comportamento del gioco.

Certo, con un po' di lavoro (non poco) si potrebbe definire in Java un completo linguaggio su misura che l'autore possa usare senza bisogno di ricompilazione. A questo punto, però, tanto varrebbe farlo in C per poter distribuire direttamente un programma eseguibile, almeno nelle versioni per i più diffusi sistemi operativi, coè Windows e Linux. Ciò eliminerebbe indubbiamente molti problemi.

Nell'estate del 1999 definii quindi a grandi linee un linguaggio dinamico e iniziai a scrivere le librerie C che ne avrebbero formato i mattoni portanti. Mi fermai quando lessi le specifiche di JavaScript e mi resi conto che era quasi identico a ciò che avevo in mente (almeno nella struttura di base). Inoltre esisteva uno standard indipendente (ECMA-262).

JavaScript richiede un interprete, ma esso è contenuto nei principali browser: avevo quindi quasi gratis un linguaggio dinamico e i vantaggi di HTML. C'era però un prezzo da pagare: l'impossibilità di accedere ai file e l'obbligo di usare i cookie per salvare la situazione di gioco. Decisi di accettare questi limiti e riprogettai di conseguenza l'intero sistema, scegliendo una versione stabile di JavaScript (la 1.1) per avere la massima compatibilità.
 

indice Struttura di Idra

La scelta di usare il linguaggio stesso (cioè JavaScript) per definire il contenuto delle pagine e le operazioni sulle variabili di gioco semplifica sia il programma che il lavoro dell'autore. L'alternativa, consistente nel definire un 'linguaggio autore', avrebbe comportato molto lavoro per un guadagno quantomeno dubbio; inoltre la precedente esperienza col Modulo Base mi aveva convinto della validità di un'impostazione ibrida di questo genere.

Ciasuna pagina del racconto diventa quindi una funzione JavaScript che disegna la pagina in un apposito frame, con la semplice aggiunta del meccanismo per passare da una pagina all'altra. I semplici link HTML non bastano, perché un clic su una scelta può anche comportare l'esecuzione di una serie di istruzioni.

Con riferimento al codice nel file idra.js, le funzioni rinvio() e scelta() ricordano quindi nell'array scelte[] l'azione desiderata (salto a una pagina, o istruzioni da eseguire) e predispongono un link HTML che, in risposta a un clic, chiami la funzione eseguiScelta() passandole il numero dell'azione corrispondente. Tralasciando un problema che vedremo tra breve, un clic su una scelta ha come effetto l'esecuzione di:

eseguiScelta(4)
La eseguiScelta() consulta scelte[4] che contiene l'azione richiesta e, con l'aiuto della esegui(), ne esamina il tipo: se è una pagina la presenta usando la consueta vai(), altrimenti la considera codice da eseguire con la funzione eval() tipica dei linguaggi dinamici, che interpreta le istruzioni contenute in una stringa, ad esempio:
eval("lire += 1000; vai(Edicola)")
L'unica complicazione in questo semplice ma efficiente meccanismo è causata dal pulsante "Indietro" del browser: se infatti il lettore potesse tornare indietro a piacimento, sarebbe impossibile garantire il corretto funzionamento del racconto-gioco (si dovrebbe memorizzare a ogni passo lo stato delle variabili per poterlo in seguito ripristinare, cosa impossibile a causa dei limiti di spazio nei salvataggi su disco).

Per evitare che il tutto possa finire in uno stato imprevisto a causa di queste operazioni, a ciascuna presentazione di pagina è assegnato un identificatore (idPagina), un semplice numero progressivo che viene anche scritto nei link dalle funzioni rinvio() e scelta(); in questo modo:

eseguiScelta(23:4)
In caso di clic, la eseguiScelta() verifica per prima cosa che l'identificatore del link su cui il lettore ha fatto clic (23 nell'esempio) coincida con quello della pagina corrente. Se ciò non avviene, significa che la pagina rappresentata sul video non è quella creata da Idra, tpicamente perché il lettore ha premuto "Indietro" sul browser, per cui viene presentato un messaggio di errore e poi viene chiamata la funzione ridisegna() per mostrare nuovamente al lettore la pagina corretta.
 

indice Salvataggio e ripristino

La situazione da registrare su disco consiste nei valori di tutte le variabili di gioco, ossia le proprietà della variabile v, più naturalmente la pagina corrente e lo stato del generatore di numeri pseudocasuali; quest'ultimo dato è necessario per evitare che la situazione possa cambiare salvando e ricaricando il gioco (addirittura la pagina potrebbe presentare scelte diverse, se esse dipendessero da un numero estratto a caso).

Per rendere ripristinabile lo stato del generatore pseudocasuale, ho dovuto sostituire quello di JavaScript (Math.random()) con la funzione caso() che impiega il semplice algoritmo di molti sistemi Unix; essa è a sua volta chiamata dalla dado() messa a disposizione dell'autore.

La creaStringaStato() ha il compito di 'impacchettare' tutte le informazioni, incluso il nome della pagina corrente, in un'unica stringa che riassume lo stato corrente del racconto-gioco; eventuali stringhe contenute nelle variabili vengono messe tra virgolette, mentre le virgolette contenute nelle stringhe sono sostituite dalla sequenza \" per evitare interferenze. Il risultato è una stringa di questo tipo:

v.inv=1;v.lupi=16;v.capr=2;v.uova=0;v._qui_="P11";v._caso_=271568720
La funzione registraCookie() codifica ulteriormente la stringa con la escape() per evitare la presenza di caratteri non ammessi, come ad esempio i segni di interpunzione; vi aggiunge poi il nome e la data di scadenza e salva il tutto in un cookie, se le circostanze lo permettono. Il cookie conterrà una stringa del genere:
v.inv%3D0%3Bv.lupi%3D0%3Bv.capr%3D2%3Bv.uova%3D0%3Bv._qui_%3D%22P11%22%3Bv._caso_%3D271568720%3B
Vi è qui una certa inefficienza dovuta alla codifica, che potrebbe eliminata con un'elaborazione preliminare per togliere i v. e i simboli (= ;); questa organizzazione della stringa ha comunque il vantaggio di poter ripristinare lo stato del sistema con una semplice eval(), come fa appunto la ripristinaStato().

Infine, nel caso di problemi con i cookie (che possono ad esempio essere disabilitati per scelta del lettore) Idra salva lo stato nella variabile situazioneSalvata: ciò non consente di conservarlo se si esce dal browser, ma lo si può comunque recuperare nell'ambito della stessa sessione, cioè se non si è usciti dal racconto-gioco. È ad esempio possibile salvare prima di fare una scelta rischiosa, per poi riprendere dallo stesso punto nel caso che sia andata male.
 

indice Lo stato precedente

Durante i collaudi emerse un serio problema: in certe condizioni il codice di una pagina poteva accidentalmente essere rieseguito, ad esempio aumentando nuovamente i punti se la pagina conteneva un'istruzione del tipo:
v.punti += 15
Ciò accadeva ad esempio nel caso di un salvataggio della situazione: la pagina corrente era infatti già stata presentata, e quindi le variabili erano state cambiate di conseguenza; al ripristino doveva però essere mostrata nuovamente, per cui la sua funzione era eseguita una seconda volta, con i possibili effetti collaterali indesiderati di cui sopra.

Per evitare questo problema, la funzione vai() salva l'intero stato nella variabile statoPrecedente prima di mostrare una pagina, in modo da conservare la situazione com'era prima che la pagina fosse presentata. Salvando questa situazione precedente anziché quella attuale, il ridisegno della pagina al momento del ripristino rimetterà le cose esattamente come stavano.

La funzione ridisegna() fa uso appunto dello statoPrecedente per mostrare una pagina senza eseguirne il codice; in realtà essa fa un passo indietro (usa lo stato prima che la pagina fosse disegnata) e uno avanti (disegna la pagina): l'effetto è quello di non alterare né i valori delle variabili né quello degli eventuali tiri di dado.

Dato che la funzione mostra(), a differenza della vai(), non salva lo stato precedente, è sempre possibile mostrare delle pagine temporanee e poi ridisegnare la pagina corrente usando appunto ridisegna() senza rischiare alcun effetto collaterale, purché ovviamente le stesse pagine temporanee non abbiano cambiato il valore di qualche variabile di gioco.
 

indice Il debugger

Il debugger non è particolarmente complesso: fa uso di una form per mostrare il campo di input e i pulsanti di comando; la stringa scritta nel campo di input viene eseguita con la solita funzione eval().

Ho solo dovuto risolvere qualche difficoltà di ordine tecnico, in particolare la tendenza di alcuni browser ad andare in crash in determinate condizioni; ho rilevato sperimentalmente che la suddivisione dell'esecuzione in due parti (eseguiDebug() ed eseguiDebug2()) collegate fra loro con un timer sembra aver eliminato l'inconveniente.

Avrei voluto controllare l'attivazione del debugger con una combinazione di tasti, ma le incompatibilità tra i diversi browser hanno sconsigliato questa soluzione; per evitare di dover modificare il file del racconto (gioco.js) per attivare o disattivare il debugger, ho inserito un automatismo nel frame di controllo ctrl.html: se la pagina viene aperta dentro un frame di nome "Debug", biene mostrato il link corrispondente.
 

indice Possibili migliorie

In linea di massima preferirei non toccare Idra, se non per togliere errori o difetti: è infatti un meccanismo flessibile e pulito che non desidero appesantire con 'migliorie', tanto più che molte estensioni si possono facilmente realizzare scrivendo funzioni nel file del racconto (gioco.js), senza toccare idra.js.

Tuttavia vi sono alcuni punti che potrebbero essere perfezionati, e sui quali potrei un giorno o l'altro lavorare se mai dovessi rimettere mano al programma, ad esempio:

- La compressione dei dati di salvataggio per aumentare lo spazio disponibile.
- Un maggiore controllo di errore, specie sugli argomenti passati alle funzioni.
- L'introduzione di azioni a tempo, inteso come numero di mosse.
- L'utilizzo di immagini con zone attive (<img usemap>).
- L'eventuale aggiunta di una 'traccia' che consenta di sfogliare all'indietro.

Terrò naturalmente in considerazione anche le richieste degli autori che sceglieranno di usare Idra per i propri racconti-gioco; mi pare comunque che Idra offra già parecchie possibilità così com'è e vorrei vederle sfruttate almeno in parte prima di pensare a eventuali versioni successive.

Inoltre, caso mai trovassi il tempo, piacerebbe anche a me scrivere un racconto-gioco; è da un po' che ho un'idea in mente e, lo confesso, è anche per questo che ho scritto Idra.

 



 «--  Precedente 1 2 3 4 5 6 7 A B Indice Successivo  --»