6 - Programmazione evoluta


Le tecniche illustrate in questa sezione possono essere ignorate da chi desidera soltanto scrivere semplici racconti-gioco; esse consentono agli esperti di sfruttare più a fondo le possibilità offerte da Idra.

Una buona familiarità col linguaggio JavaScript è naturalmente consigliabile; sul Web e in libreria si trovano numerosi corsi, sia introduttivi che a livello più avanzato. Un esempio interessante è Voodoo's Introduction to JavaScript di Stefan Koch (come sempre, l'indirizzo è soggetto a possibili cambiamenti).

In ogni caso è bene non modificare il contenuto del file idra.js, intervenendo invece solo sul file gioco.js o su eventuali altri file aggiunti; si evita in questo modo il rischio di introdurre sottili malfunzionamenti, che possono non risultare evidenti a un primo collaudo.
 

indice Sfondo variabile

Abbiamo visto nel capitolo 4 che per cambiare le opzioni della pagina, ad esempio il colore di fondo, si deve scrivere una funzione di nome OpzioniPagina() che verrà chiamata automaticamente da Idra al momento opportuno:
// Opzioni della pagina: sfondo ciano pallido

function OpzioniPagina(pag) {
  return 'bgcolor="#ccffff"'
}
Un'impostazione così fatta vale però per tutte le pagine; volendo personalizzarla, ad esempio per fare in modo che le pagine Cantina() e Laboratorio() usino invece uno sfondo giallo, ci si può basare sul contenuto dell'argomento pag che corrisponde alla pagina da mostrare:
// Sfondo diverso a seconda della pagina:

function OpzioniPagina(pag) {
  if (pag == Cantina || pag == Laboratorio) {
    return 'bgcolor="#ffff00"'  //giallo
  } else {
    return 'bgcolor="#ccffff"'  //ciano chiaro
  }
}
In questo caso, se la pagina è una delle due indicate la funzione ritorna il comando HTML per mettere un fondo giallo, altrimenti ritorna il comando per mettere un fondo ciano chiaro. Da notare anche qui l'uso dei singoli apici intorno alle stringhe che contengono virgolette.

Con lo stesso sistema si potrebbe ritornare per altre pagine il comando per usare un disegno di fondo invece che un colore uniforme, come illustrato nel capitolo 4. Un buon programmatore potrebbe poi sfruttare gli array o altre tecniche per non dover specificare le pagine una per una, nel caso che ve ne siano molte con caratteristiche differenti.
 

indice Pagine e nomi di pagina

Lo stesso sistema sopra illustrato si può usare anche nelle funzioni Intestazione() e PiePagina(): anch'esse ricevono infatti come argomento la pagina corrente. È quindi possibile cambiare l'una o l'altra a seconda della pagina:
// Alla fine di ogni pagina tranne Copertina(): separatore e scritta piccola

function PiePagina(pag) {
  if (pag != Copertina) {
    testo("<hr>")
    testo('<font size="-2">Riuscirai a risolvere questo mistero?</font>')
  }
}
Una nota importante: l'argomento pag e i nomi di pagina come Copertina (indicati senza parentesi) si riferiscono a funzioni JavaScript, che non sono stringhe; non è quindi possibile, ad esempio, confrontarli con una stringa:
if (pag != "Copertina") {  //--- errore ---
  ...
}
Si può però ricavare una stringa contenente il nome di una funzione di pagina, usando l'apposita funzione nomePagina():
if (nomePagina(pag) != "Copertina") {  //valida
  ...
}
Nell'esempio mostrato il confronto è valido perché avviene fra due stringhe. Un possibile uso della funzione nomePagina() consiste nel mostrare il nome della pagina corrente a piè di pagina:
function PiePagina(pag) {
  testo("<hr>")
  testo("Siamo nella pagina ", nomePagina(pag), ".")
}
Ad esempio, al termine della pagina Valanga() verrebbe in questo caso rappresentato questo piè di pagina:



Siamo nella pagina Valanga.
 

Per evitare errori è importante ricordare questa distinzione tra la pagina, che è una funzione, e il suo nome che è invece una stringa. Quest'ultima non si usa spesso, ma a volte può essere assai utile, come vedremo tra breve.
 

indice Ricordare una pagina

A volte può essere utile prendere nota di una certa pagina per potervi ritornare in seguito; lo si può fare usando la funzione pagina() per conoscere la pagina corrente, salvandone il nome in una variabile di gioco:
v.segno1 = nomePagina(pagina())
È importante salvare il nome della pagina, usando appunto nomePagina(), e non la pagina direttamente, altrimenti ci sarebbero problemi in caso di salvataggio della situazione:
v.segno1 = pagina()  // --- Errore! ---
In qualunque momento successivo del gioco sarà possibile ritornare alla pagina indicata, eseguendo questa operazione:
vai(eval(v.segno1))
La funzione eval(), una delle più utili di JavaScript e dei linguaggi dinamici in generale (es. Lisp, Perl), è qui usata per ricavare la pagina (funzione) a partire dal suo nome (stringa) contenuto nella variabile v.segno1.
 

indice Pagine temporanee

Se una pagina viene presentata con la funzione mostra() anziché con vai(), non è necessario memorizzare la pagina di provenienza per poterci tornare: Idra la ricorderà automaticamente. Anzi, in realtà dal suo punto di vista sarà come se la pagina non fosse affatto cambiata. Per esempio, se diverse pagine contengono questa stessa riga:
scelta("Chiama aiuto via radio", "mostra(SOS)")
al lettore verrà proposta una normale scelta:


Chiama aiuto via radio
 

Facendo clic su questa scelta verrà eseguita l'istruzione JavaScript mostra(SOS) che richiamerà la pagina SOS(); quest'ultima potrebbe ad esempio contenere:

function SOS() {
  testo("<br>")
  titolo("Centro soccorso stellare")
  testo("Siamo spiacenti, l'assicurazione risulta non pagata.")
  continua("ridisegna()")
}
Il lettore vedrà quindi:


Centro soccorso stellare

Siamo spiacenti, l'assicurazione risulta non pagata.

Facendo clic su "Continua" verrà però eseguita la funzione ridisegna(): essa ridisegnerà la pagina corrente, cioè appunto quella dalla quale si proveniva. Lo stesso accade ad esempio se si salva la situazione: questa pagina SOS() è a tutti gli effetti una pagina fantasma che non sposta il 'segnalibro' dalla pagina corrente.

È importante usare ridisegna() invece che aggiorna() nelle pagine chiamate con mostra() perché aggiorna() esegue le istruzioni contenute nella pagina, mentre ridisegna() si limita a riproporla sul video com'era prima. In queste pagine temporanee è bene non cambiare il valore delle variabili di gioco.

La funzione mostra(), a differenza di vai(), non chiamaIntestazione()PiePagina(); per questo motivo la prima linea dell'esempio fa uno stacco iniziale con <br>. La stessa mostra() è anche usata da Idra stesso per presentare i propri messaggi, relativi ad esempio al salvataggio su disco.
 

indice Costanti

Una costante è una variabile... che non cambia, e quindi non ha bisogno di essere salvata e non necessita del prefisso v. che caratterizza le variabili di gioco. In Idra le costanti possono essere utili per risparmiare lavoro; si scrivono per convenzione con (almeno) l'iniziale maiuscola e si possono mettere, ad esempio, prima di tutte le funzioni:
FontRosso = '<font color="#ff0000">'
FontVerde = '<font color="#00ff00">'
FontBlu = '<font color="#0000ff">'
FineFont = "</font>"
Tutte queste costanti potranno poi essere usate con la funzione testo() come si fa con le normali stringhe da mostrare nella pagina:
testo("Ora ", FontRosso, "aspetta", FineFont)
testo(", poi ", FontBlu, "scatta", FineFont, "!")
Il risultato sarà lo stesso che se fossero stati scritti direttamente i corrispondenti comandi HTML:


Ora aspetta, poi scatta!
 
 

indice Funzioni

Per evitare di ripetere spesso una serie di istruzioni, si può definire una funzione che si occupa di quel lavoro. Per convenzione le funzioni definite dall'autore devono iniziare con una lettera maiuscola, per distinguerle da quelle di Idra e di JavaScript stesso.

Ad esempio, una funzione Tavola2() potrebbe essere usata per scrivere del testo in due riquadri indipendenti:

function Tavola2(t1, t2) {
  testo('<table align=center width="100%" border=1 cellpadding=10><tr>')
  testo("<td>", t1, "</td>")  //prima colonna
  testo("<td>", t2, "</td>")  //seconda colonna
  testo("<tr></table>")
}
Basterà in questo caso aggiungere, nella funzione che descrive una pagina, una chiamata alla nuova funzione Tavola2():
Tavola2("Io sto a sinistra", "E io sto a destra")
per ottenere questo risultato:

 
 
Io sto a sinistra. E io sto a destra.

 

Lo stesso sistema può essere usato per ottenere qualsiasi tipo di impaginazione complessa consentita dal linguaggio HTML, sempre ricordando di non usare caratteristiche specifiche di un determinato browser.

Ovviamente queste funzioni non hanno nulla a che fare con le funzioni che descrivono le pagine, tranne per il fatto che sono anch'esse "funzioni" dal punto di vista del linguaggio JavaScript: si tratta di sezioni di programma che vengono chiamate direttamente quando fa comodo usarle (mentre le pagine vanno chiamate solo attraverso le apposite vai() o mostra()).
 

indice Link dal frame di controllo

Una funzione può essere chiamata in conseguenza del clic su un link nella parte sinistra della finestra, cioè quella esterna alla pagina vera e propria (tecnicamente si tratta di un altro frame, cioè di un riquadro indipendente); ad esempio la funzione Informazioni() viene chiamata quando si fa clic sul link "Informazioni sul gioco", per effetto di questa riga nel file ctrl.html:
<a href="javascript:parent.ctrl.focus();Informazioni()">Informazioni sul gioco</a><br>&nbsp<br>
Normalmente non c'è bisogno di aggiungere ulteriori link, ma lo si può comunque fare scrivendo una nuova riga in ctrl.html:
<a href="javascript:parent.ctrl.focus();Punti()">Punteggio corrente</a><br>&nbsp<br>
Il nuovo link "Punteggio corrente" sarà sempre visibile, indipendentemente dalla pagina mostrata nella parte destra della finestra; un clic su di esso chiamerà la funzione Punti(), che però non è una pagina normale: essa deve a sua volta chiamare una pagina vera e propria per mezzo della funzione mostra():
function Punti() {
  mostra(PagPunti, 'bgcolor="#00cc99"') //Imposta il colore di fondo
}

function PagPunti() {
  titolo("Punteggio")
  testo("Sei arrivato a ben ", v.punti, " punti!")
  continua("ridisegna()")
}
Trattandosi di una pagina temporanea aperta con mostra(), essa viene chiusa con l'apposita funzione ridisegna() in modo da ripresentare la pagina corrente senza alcuna variazione; in effetti la funzione Informazioni(), già predisposta, viene chiamata esattamente in questo modo.

È importante che la funzione chiamata dal link (Punti() nell'esempio) non ritorni alcun valore, altrimenti un clic sul link stesso può causare l'uscita dal gioco. Volendo si può evitare la doppia funzione impiegando le apposite apriPagina() e chiudiPagina() per costruire una pagina al volo:

function Punti() {
  apriPagina('bgcolor="#00cc99"')
  titolo("Punteggio")
  testo("Sei arrivato a ben ", v.punti, " punti!")
  continua("ridisegna()")
  chiudiPagina()
}
Il risultato sarà identico a quello precedentemente illustrato, cioè a quello che si avrebbe impiegando la funzione mostra() per chiamare una pagina temporanea.
 

indice Variabili locali

All'interno delle funzioni si possono usare anche normali variabili (cioè non variabili di gioco precedute da v.), purché il loro valore non debba essere conservato al terminare della funzione stessa. Ad esempio, questa funzione scrive del testo per più volte consecutive, di seguito:
function RipetiTesto(volte, t1) {
  var i;
  for (i = 0; i < volte; i++) {
    testo(t1)
  }
}
L'istruzione var dichiara (cioè crea) la variabile i, che viene poi usata all'interno della funzione e smette di esistere quando quest'ultima termina. Chiamando la funzione RipetiTesto() questo modo:
RipetiTesto(8, ".oOOo.")
si otterrà di mostrare:


.oOOo..oOOo..oOOo..oOOo..oOOo..oOOo..oOOo..oOOo.
 

In questo caso si poteva semplificare la funzione usando lo stesso parametro volte per contare alla rovescia, ma in molti casi l'uso di variabili locali è assai utile o indispensabile.
 

indice Risparmiare variabili

A causa del meccanismo usato per salvare la situazione su disco, basato sui cookie (l'unica possibilità offerta da JavaScript), lo spazio disponibile per le variabili di gioco è limitato. Non è possibile indicare un numero preciso perché esso dipende da vari fattori, ma approssimativamente il totale disponibile per tutte le variabili (nome + contenuto) è attorno ai 2000 caratteri.

Nel caso di racconti lunghi o complessi è possibile che questo limite venga superato, causando un errore quando si chiede di salvare. Per evitarlo si possono adottare vari accorgimenti:

- Usare nomi corti, ad esempio v.anello invece che v.trovatoAnelloMagicoDelDragoVerdeDiGrottafumosa.
- Annullare il contenuto delle variabili non più necessarie, assegnando loro il valore speciale null:

v.trovatoPozzo = null
o, meglio ancora, eliminarle definitivamente con l'apposita istruzione delete:
delete v.trovatoPozzo
In questo caso non si dovranno dichiarare le variabili nella funzione Inizia(), per evitare che esse vengano ricreate quando si inizia una lettura e poi si riprende una situazione da disco: basterà invece crearle quando servono, semplicemente assegnando loro un valore:
v.trovatoPozzo=1
Non si deve qui usare la dichiarazione var (vista a proposito delle variabili locali) perché le variabili di gioco sono tecnicamente delle proprietà della variabile v e non delle variabili vere e proprie; è quindi sufficiente assegnare loro un valore per crearle.

Occorre però essere certi che la variabile desiderata venga effettivamente creata prima di essere utilizzata, altrimenti il suo valore è undefined (non definito); è facile causare errori se non si presta la massima attenzione, per cui questa soluzione è da adottare solo in caso estremo, se proprio non c'è più spazio disponibile.

Una tecnica meno rischiosa consiste nel riutilizzare per altro uso alcune variabili, specie quelle che servono solo per una o per alcune pagine; lo svantaggio consiste in questo caso nell'impossibilità di usare nomi significativi (es. v.trovatoPendolo), essendo invece costretti a ricorrere a nomi generici (es. v.temp3) dato che la stessa variabile verrà usata di volta in volta per scopi diversi.
 

indice File multipli

Se le funzioni JavaScript aggiuntive, cioè quelle che non descrivono pagine ma svolgono operazioni varie, dovessero essere molte e complesse, si può scriverle in un file a parte (ad esempio extra.js che andrà poi caricato aggiungendo nel file ctrl.html:
<!-- include le funzioni ausiliarie -->
<script language="JavaScript1.1" src="extra.js"></script>
La prima riga è un semplice commento e si può tralasciare, mentre la seconda indica all'interprete JavaScript di leggere le istruzioni contenute nel file extra.js.

Il posto migliore per mettere queste righe è subito prima di quelle che richiamano gioco.js, in modo che eventuali comandi diretti presenti in quest'ultimo possano farne uso (i comandi diretti sono istruzioni poste fuori dalle funzioni, che vengono eseguiti al caricamento del file):

<!-- include le funzioni ausiliarie -->
<script language="JavaScript1.1" src="extra.js"></script>
 
<!-- include il programma dell'avventura -->
<script language="JavaScript1.1" src="gioco.js"></script>
L'effetto sarà lo stesso che se le funzioni fossero state scritte all'inizio del file gioco.js; la suddivisione serve solo per comodità di lavoro.
 

indice Riferimenti al documento

Normalmente non è necessario fare riferimento al documento HTML che contiene le pagine create da Idra: ci pensa infatti la funzione testo(). Un programmatore esperto può comunque accedervi con:
parent.area.document
Se ad esempio, per mezzo di istruzioni HTML stampate con la funzione testo(), è stato creato nella pagina un form formRichiesta contenente il campo editabile campoNome, si può leggere il contenuto del campo e assegnarlo a una variabile di gioco in questo modo:
v.nome = parent.area.document.formRichiesta.campoNome.value
Il debugger fa uso appunto di questa tecnica. Fortunatamente si tratta di operazioni non necessarie per un impiego normale di Idra, ma che consentono di realizzare meccanismi anche piuttosto complessi.

 



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