IF ITALIA - Il sito Italiano sull'Interactive Fiction
IV. LA PROGRAMMAZIONE DI HUGO
IV.a. Variabili
Hugo supporta due tipi di variabili: globali e locali. Entrambi i tipi contengono un intero a 16 bit, così una variabile può memorizzare un valore semplice, il numero di un oggetto, un indirizzo del dizionario, un indirizzo di una routine, o qualunque altro tipo di dati standard di Hugo tramite un'assegnazione come:a = 1Le variabili globali sono visibili per tutto il programma. Debbono essere definite in maniera simile a quanto viene fatto per le proprietà e gli attributi come in
nextobj = parent(obj)
temp_word = "the"global <nome variabile globale>[ = <valore iniziale>]Le variabili locali, d'altra parte, sono riconosciute solo all'interno della routine in cui sono state definite. Vengono definite usandolocal <nome variabile locale>[ = <valore iniziale>]Le variabili globali devono avere un nome univoco, diverso da quello usato per altri oggetti: le variabili locali, invece, possono usare lo stesso nome usato per altre variabili locali in altre routine.In entrambi i casi, globali o locali, il valore iniziale di default è 0 se nessun valore viene fornito. Ad esempio,
global time_of_day = 1100è uguale a 1100 quando il programma viene eseguito, ed è visibile in ogni punto del programma ad ogni oggetto o routine. D'altra parte le variabililocal a, max = 100, tsono visibili solo nel blocco di codice in cui sono state definite, e sono inizializzate, rispettivamente, a 0, 100 e 0, ogni volta che quella sezione di codice (che sia una routine, una routine proprietà, un evento, ecc.) viene eseguita.Il compilatore definisce una serie di globali dell'interprete (engine globals): varibili globali referenziate direttamente dall'interprete, ma che possono essere utilizzate come ogni altra variabile globale. Sono:
object oggetto a cui si riferisce un'azioneLe globali object e xobject vengono impostate dall'interprete in base al comando digitato dal giocatore. La globale self è indefinita a meno che un oggetto sia stato referenziato (in una routine proprietà). In questo caso viene impostata con il numero dell'oggetto. La variabile player contiene il numero dell'oggetto che il giocatore sta controllando; la variabile verbroutine contiene l'indirizzo della routine specificata nella tavola della grammatica e corrispondente al comando inserito; la varibile endflag deve essere 0 a meno che non accada qualcosa che faccia terminare il gioco; e la variabile prompt contiene la parola del dizionario che appare all'inzio della riga di input.
xobject l'oggetto indiretto
self l'oggetto che punta a se stesso
words numero di parole nel comando
player l'oggetto giocatore
actor il giocatore, od un personaggio (per gli script)
verbroutine la routine specificata dal comando
endflag se non è falso (0), esegue la routine EndGame
prompt per l'input; il default è ">"
objects il numero totale di oggetti
system_status dopo certe operazioniLa variabile objects può essere impostata dal giocatore, ma senza alcun effetto utile. L'interprete la reimposterà al valore "vero" ogni volta che verrà referenziata. (Tutti i numeri di oggetto variano da 0 al valore di objects). La variabile system_status può essere letta (dopo un'operazione riguardante una risorsa o una chiamata di 'sistema'; controllate le sezioni corrispondenti per una spiegazione di queste funzioni) per controllare se si è verificato un errore. Consultate la sezione riguardante le "Risorse" per i possibili valori di ritorno.
(NOTA: Impostando endflag ad un valore diverso da 0 forza un'interruzione IMMEDIATA del ciclo di gioco. Le istruzioni che seguono l'assegnazione del valore ad endflag, anche se nella stessa funzione, non verranno eseguite; il controllo passa direttamente all'interprete che chiama la routine EndGame).
IV.b Costanti
Le costanti sono semplicemente delle etichette che identificano un valore non modificabile.constant NOME "John"(Fate caso alla mancanza di un segno di '=' tra, ad esempio, NOME e "John")
constant COGNOME "Smith"print COGNOME; ", "; NOMEvisualizza:Smith, JohnLe costanti possono essere, come ogni altro tipo di dato in Hugo, interi, voci del dizionario, numeri di oggetti, ecc.(Non è necessario assegnare un valore definito ad una costante se le costanti devono essere usate come una specie di flag o indicatore. Perciò,
constant QUESTO_RISULTATOavranno un valore differente tra di loro, così come da ogni altra costante definita senza uno specifico valore).
constant QUEL_RISULTATOA volte può essere utile numerare una serie di costanti in sequenza. Invece di definirle individualmente è possibile usare:
enumerate start = 1che assegna:
{
LUNEDI, MARTEDI, MERCOLEDI, GIOVEDI, VENERDI
}LUNEDI = 1, MARTEDI = 2, MERCOLEDI = 3, GIOVEDI = 4, VENERDI = 5Il valore start [inizio] è opzionale. Se viene omesso si assume 0. Inoltre è possible cambiare il valore corrente in ogni punto (questo riguarderà anche i valori seguenti).enumerateimposta: A = 0, B = 1, C = 5, D = 6, E = 7.
{
A, B, C = 5, D, E
}Infine è possibile modificare il passo della numerazione usando la parola chiave "step" seguita da "+x", "-x", "*x" o "/x", dove x è un valore intero. Per contare per due:
enumerate step *2imposta: A = 1, B = 2, C = 4, D = 8.
{
A = 1, B, C, D
}NOTA: la numerazione delle variabili globali è possibile usando lo specificatore 'globals', come in:
enumerate globalsAltrimenti lo specificatore "constants" viene considerato di default.
{
<globale1>, <globale2>...
}IV.c. Scrivere il testo
Il testo può essere stampato usando due metodi differenti. Il primo è l'utilizzo del comando 'print', la cui forma più semplice èprint "<stringa>"dove <stringa> rappresenta una serie di caratteri alfanumerici e simboli di punteggiatura.Il carattere di controllo barra inversa ("\") viene gestito in maniera speciale. Modifica il modo in cui il carattere che lo segue nella stringa viene trattato.
\" inserisce le doppie virgolette \\ inserisce il carattere di barra inversa \_ inserisce uno spazio, ignorando la giustificazione a sinistra per il resto della stringa \n inserisce un carattere di nuova riga Come normale, un singolo "\" alla fine di una riga segnala che la riga continua sulla successiva.
Esempi:
print "\"Salve!\""(Sebbene"Salve!"
print "Stampa una...\n...riga nuova"
Stampa una...
...riga nuovaprint "Uno\\Due\\Tre"
Uno\Due\Tre
print " Giustificato a sinistra"
print "\_ Non giustificato a sinistra"Giustificato a sinistra
Non giustificato a sinistraprint "Questa è una \
riga singola."Questa è una riga singola.
print "Questa è unaproduca lo stesso risultato, visto che l'interruzione di riga avviene tra doppi apici).
riga singola."NOTA: queste combinazioni di caratteri di controllo sono valide solo in stampa; non vengono trattate in maniera letterale, come, ad esempio, le espressioni che coinvolgono le voci del dizionario.
Dopo ognuno dei comandi print indicati sopra, viene stampata una riga nuova. Per evitarlo è necessario aggiungere un punto e virgola (;) alla fine dell'istruzione print.
print "Questa è una ";Le istruzioni print possono anche contenere dei tipi di dato, o una combinazione di tipi di dato e stringhe. Il comando
print "singola riga."Questa è una singola riga.
print "La "; object.name; " è chiusa."stamperà la parola che si trova all'indirizzo del dizionario specificato da object.name, così se object.name punta alla parola "scatola", l'output risultante sarà:La scatola è chiusa.Per rendere maiuscola la prima lettera della parola specificata, si usa il modificatore 'capital'.print "La "; capital object.name; " è chiusa."Per stampare il dato come un valore invece di indirizzare una voce di dizionario, si usa il modificatore 'number'. Ad esempio, se la variabile tempo contiene il numero 5,La Scatola è chiusa.
print "Restano ancora "; number tempo; " secondi."Se non fosse stato usato 'number', l'interprete avrebbe cercato di trovare una parola all'indirizzo 5 del dizionario, ed il risultato sarebbe stato una stampa errata.Restano ancora 5 secondi.
NOTA: Soprattutto per gli scopi del debug, il modificatore 'hex' stampa il dato come un numero esadecimale invece di un numero decimale. Se la variabile val contiene 127,
print number val; " è "; hex val; " in esadecimale."Un secondo modo per stampare il testo è quello di prenderlo dalla memoria del testo (text bank), da dove -- se non c'è abbastanza memoria -- le sezioni di testo sono caricate dal disco solo quando è richiesto dal programma. Questo metodo è stato adottato così che lunghi blocchi di testo -- come le descrizioni e la narrazione -- non consumano spazio prezioso se la memoria è limitata. Il comando consiste semplicemente in una stringa tra doppi apici senza nessuna istruzione che la precede.127 è 7F in esadecimale.
"Questa stringa verrà scritta sul disco."oQuesta stringa verrà scritta sul disco.
"Così questa ";Fate caso al fatto che un punto e virgola alla fine dell'istruzione continua ad evitare la stampa su una nuova riga. I caratteri di controllo nella stringa sono ancora utilizzabili con queste istruzioni di stampa, ma visto che ogni comando è una singola riga, i tipi di dato e gli altri modificatori non possono essere composti. Per questo
"ed anche questa."Così questa ed anche questa.
"\"Salve,\"" disse."scriverà"Salve," disse.Nella memoria di testo del file .HEX, ma"Restano ancora "; number tempo_rimasto; " secondi."è illegale.Il colore del testo può essere cambiato usando il comando 'color' (usabile anche secondo la sintassi Inglese "colour"). Il formato è
color <primopiano>[, <sfondo>[, <colore dell'input>]]dove il colore di sfondo non è obbligatorio. Se nessun colore di sfondo viene specificato, verrà usato quello corrente).Anche il colore dell'input non è obbligatorio -- specifica il colore usato per stampare i comandi del giocatore.
Il set standard di colori con i valori corrispondenti ed i nomi delle costanti è:
COLORE VALORE COSTANTE Nero 0 BLACK Blu 1 BLUE Verde 2 GREEN Ciano 3 CYAN Rosso 4 RED Magenta 5 MAGENTA Marrone 6 BROWN Bianco 7 WHITE Grigio scuro 8 DARK_GRAY Blu chiaro 9 LIGHT_BLUE Verde chiaro 10 LIGHT_GREEN Ciano chiaro 11 LIGHT_CYAN Rosso chiaro 12 LIGHT_RED Magenta chiaro 13 LIGHT_MAGENTA Giallo 14 YELLOW Bianco brill. 15 BRIGHT_WHITE Primo piano default 16 DEF_FOREGROUND Sfondo default 17 DEF_BACKGROUND Primo piano statusline default 18 DEF_SL_FOREGROUND Sfondo statusline default 19 DEF_SL_BACKGROUND Primo piano corr. 20 MATCH_FOREGROUND (Le costanti sono definite in HUGOLIB.H; quando si usa la libreria non è necessario riferirsi ai colori usando il loro valore numerico).
Ci si aspetta che, a parte il sistema, ogni colore venga stampato differentemente dagli altri. Comunque la pratica suggerisce che il bianco (talvolta il bianco brillante) venga usato per la stampa del testo. Blu e nero vengono di solito usati per lo sfondo.
Un testo magenta su uno sfondo ciano si ottiene con
color MAGENTA, CYANocolor 5, 3 !Se non si usa HUGOLIB.HUna riga corrente può essere riempita -- con spazi bianchi del colore corrente -- fino ad una specifica colonna (sostanzialmente un tabulatore) usando la struttura "print to..." come segue:print "Ora:"; to 40; "Data:"dove il valore che segue il 'to' non deve essere superiore alla lunghezza massima della riga indicata dalla variabile globale dell'interprete linelength.L'output risultante è qualcosa del tipo:
Ora: Data:Il testo può essere posizionato usando il comando 'locate'locate <colonna>, <riga>dovelocate 1, 1posiziona il testo in output all'angolo in alto a sinistra della finestra di testo corrente. Né <colonna> né <riga> devono superare i bordi della finestra corrente -- l'interprete le riduce automaticamente se necessario.IV.d. Altri caratteri di controllo
Come indicato prima quelli che seguono sono dei caratteri di controllo validi che possono essere racchiusi in una stringa:
\" doppi apici \\ una barra inversa \_ uno spazio forzato, ignorando l giustificazione a sinistra per il resto della stringa \n riga nuova Il prossimo insieme di caratteri definisce l'aspetto del testo impostando il grassetto, il corsivo, il proporzionale ed il sottolineato. Non tutti i computer e sistemi operativi sono in grado di fornire tutti i tipi di output; comunque l'interprete si occuperà di formattare in maniera corretta tutti i testi -- ad esempio, il testo stampato in maniera proporzionale apparirà corretto anche su un sistema con solo caratteri a spaziatura fissa, come l'MS-DOS (sebbene non verrà stampato con la spaziatura porporzionale).
\B attiva il grassetto (Bold) \b disattiva il grassetto \I attiva il corsivo (Italic) \i disattiva il corsivo \P attiva la stampa proporzionale \p disattiva la stampa proporzionale \U attiva il sottolineato (Underline) \u disattiva il sottolineato (Lo stile della stampa può anche essere modificato usando la routine Font di HUGOLIB.H. Le costanti di modifica dei caratteri possono essere combinate:
Font(BOLD_ON | ITALICS_ON | PROP_OFF)dove le costanti valide sono BOLD_ON, BOLD_OFF, ITALICS_ON, ITALICS_OFF, UNDERLINE_ON, UNDERLINE_OFF, PROP_ON, e PROP_OFF).I caratteri speciali possono essere stampati attraverso i caratteri di controllo. Questi caratteri sono quelli compresi nel set di caratteri Latin-1; se un sistema non è in grado di visualizzarli, stamperà gli equivalenti ASCII.
(Gli esempi seguenti, tra parentesi, possono non essere visualizzati correttamente su tutti i computer e stampanti).\` accento grave seguito da una lettera
es. "\`a" stampa una 'a' con accento grave (à)\' accento acuto seguito da una lettera
es. "\'E" stampa una 'E' con accento acuto (É)\~ tilde seguita da una lettera
es. "\~n" stampa una 'n' con una tilde (ñ)\^ accento circonflesso seguito da una lettera
es. "\^i" stampa una 'i' con accento circonflesso (î)\: umlaut seguito da una lettera
es. "\:u" stampa una 'u' con umlaut (ü)\, cedilla seguito da c o C
es. "\,c" stampa una 'c' con cedilla (ç)\< o \> virgolette (« »)
\! punto esclamativo inverso (¡)
\? punto interrogativo inverso (¿)
\ae ae legate (æ)
\AE AE legate (Æ)
\c simbolo del centesimo (¢)
\L simbolo della lira (£)
\Y Yen Giapponese (¥)
\- linea (-)\#xxx un qualunque carattere ASCII dove xxx è il codice ASCII a tre cifre del carattere che deve essere stampato
es. "\#065" stampa una 'A' (ASCII 65).Esempio: Mischiare gli stili del testo
! Routine di esempio che stampa diversi stili e coloriL'output sarà:#include "hugolib.h"
routine PrintingSample
{
print "Il testo pu\`o essere stampato
in \Bgrassetto\b, \Icorsivo\i,
\Usottolineato\u, o
\Pproporzionale\p."color RED ! o color 4
print "\nPronti. ";
color YELLOW ! color 14
print "Partenza. ";
color GREEN ! color 2
print "Via!"
}Il testo può essere stampato in grassetto, corsivo, sottolineato o proporzionale.con "grassetto", "corsivo", "sottolineato" e "proporzionale" stampati nel rispettivo stile. "Pronti", "Partenza" e "Via!" appariranno sulla stessa riga in tre colori differenti.Pronti. Partenza. Via!
Non tutti i computer sono in grado di stampare tutti gli stili. Le versioni base MS-DOS, ad esempio, usano i colori invece dei cambi di stile e non supportano la stampa proporzionale.
IV.e. Operatori ed assegnazioni
Hugo consente l'uso degli operatori matematici standard:+ addizioneI confronti sono operatori validi, restituendo vero o falso booleano (1 o 0) così che
- sottrazione
* moltiplicazione
/ divisione intera2 + (x = 1)valgono rispettivamente 3 e 5 se x è 1, e 2 e 4 se x è 2 o superiore.
5 - (x > 1)Operatori relazionali validi sono
= uguale aSono consentiti anche gli operatori logici ('and', 'or' e 'not').
~= diverso
< minore di
> maggiore di
<= minore o uguale
>= maggiore o uguale(x and y) or (a and b)Usando 'and' si ha true (1) se entrambi i valori sono diversi da zero.
(j + 5) and not ObjectIsLight(k)
Usando 'or' si ha true se uno dei due non è zero. 'not' vale true solo se il valore seguente è zero.1 and 1 = 1Inoltre sono forniti anche gli operatori binari:
1 and 0 = 0
5 and 3 = 1
0 and 9 = 0
0 and 169 and 1 = 0
1 and 12 and 1233 = 11 or 1 = 1
35 or 0 = 1
0 or 0 = 0not 0 = 1
not 1 = 0
not 8 = 01 and 7 or (14 and not 0) = 1
(0 or not 1) and 3 = 01 & 1 = 1 (and binario)(Una spiegazione dettagliata degli operatori binari è un po' oltre lo scopo di questo manuale; i programmatori potranno usare l'operatore '|' per combinare i parametri a mascheratura di bit per alcune funzioni della libreria come font e list-formats, ma solo gli utenti avanzati saranno in grado di usare gli operatori binari con ottimi risultati nella programmazione pratica).
1 & 0 = 0
1 | 0 = 1 (or binario)
1 | 1 = 1
~0 = -1 (not/inversione binaria)Qualunque tipo di dato di Hugo può comparire in una espressione, comprese le routine, attributi, proprietà, costanti e variabili. Nella valutazione delle espressioni vengono applicate le regole matematiche standard di precedenza negli operatori così che le espressioni tra parentesi vengono valutate prima, seguite da moltiplicazioni e divisioni, seguite da addizioni e sottrazioni.
Alcune combinazioni di esempio sono:
10 + object.size ! costante intera e proprietàLe espressioni possono essere valutate e assegnate sia ad una variabile che ad una proprietà.
object is openable + 1 ! test su un attributo e costante
FindLight(location) +a ! valore di ritorno e variabile
1 and object is light ! costante, test logico e attributo<variabile> = <espressione>In alcuni casi il compilatore può consentire l'uso di un'istruzione la cui parte sinistra dell'assegnazione non è modificabile. Ad esempio<oggetto>.<proprietà> [#<elemento>] = <espressione>
Funzione() = <espressione>o<oggetto>.#<proprietà> = <espressione>possono essere compilate, ma queste istruzioni generanno un errore di run-time nell'interprete.IV.f. Operatori efficienti
numero_di_oggetti = numero_di_oggetti + 1può essere codificato in maniera più semplice
if numero_di_oggetti > 10
{
print "Troppi oggetti!"
}if ++numero_di_oggetti > 10L'operatore '++' incrementa il valore della variabile seguente di uno prima di restituire il valore della stessa. Allo stesso modo si può far precedere una variabile da '--' per decrementarne il valore di uno prima di resituire il valore. Poiché questi operatori agiscono prima che il valore venga restituito vengono chiamati operatori di "pre incremento" e "pre decremento".
{
print "Troppi oggetti!"
}Se '++' o '--' vengono DOPO una variabile, il valore della variabile viene restituito e poi il valore viene incrementato o decrementato. In questo caso si parla di operatori di "post incremento" e "post decremento".
Ad esempio,
while ++i < 5 ! pre incrementostamperà:
{
print number i; " ";
}1 2 3 4Mawhile i++ < 5 ! post incrementostamperà:
{
print number i; " ";
}1 2 3 4 5Visto che nel primo esempio la variabile viene incrementata prima di leggerne il valore, mentre nel secondo è incrementata dopo la lettura.È anche possibile usare gli operatori '+=', '-=', '*=', '/=', '&=' e '|='. Possono essere usati anche per modificare una variabile mentre il suo valore viene controllato. Questi operatori, comunque, agiscono prima che il valore venga restituito.
x = 5Risultato:
y = 10
print "x = "; number x*=y; ", y = "; number yx = 50, y = 10Quando il compilatore interpreta una delle righe più sopra gli operatori efficienti hanno la precedenza rispetto a quelli normali (quelli a carattere singolo).Ad esempio,
x = y + ++zzviene compilato inx = y++ + zvisto che '++' viene interpretato prima. Per codificare correttamente questa riga con un pre incremento della variabile z invece di un post incremento di y:x = y + (++z)IV.g. Array e stringhe
Prima di questo paragrafo non si è parlato molto degli array.
Gli array sono un insieme di valori che condividono un nome comune, e dove gli elementi sono indicati tramite un numero. Gli array si definiscono conarray <nomearray> [<dimensione array>]dove <dimensione array> deve essere una costante numerica.Una definizione di array riserva un blocco di memoria di <dimensione array> parole a 16 bit, così che, ad esempio:
array prova_array[10]inizializza dieci parole a 16 bit per l'array.Bisogna tener presente che <dimensione array> determina la dimensione dell'array, NON il numero massimo di elementi. Il conteggio degli elementi comincia da 0, perciò array_prova, con 10 elementi, ha i membri numerati da 0 a 9. Cercando di accedere a array_prova[10] o superiore viene restituito un valore senza senso. (Cercando di assegnargli un valore si potrebbe avere la sovrascrittura di qualcosa di importante, come il successivo array).
Per prevenire queste letture/scritture fuori dai limiti dell'array è possibile leggere la lunghezza di un array con:
array[]senza nessun elemento specificato. Usando l'esempio di prima,print number array_prova[]ritorna "10".Gli elementi di un array possono essere assegnati più di uno alla volta, come in
<nomearray> = <elemento1>, <elemento2>, ...dove <elemento1> e <elemento2> possono essere espressioni o valori singoli.Gli elementi non devono essere tutti dello stesso tipo, così che
array_prova[0] = (19+5)*x, "Salve!", FindLight(location)è perfettamente legale (sebbene non sia, probabilmente, molto utile).
Più comune è un uso del tiponomi[0] = "Ned", "Sue", "Bob", "Maria"oarray_prova[2]) = 5, 4, 3, 2, 1L'array può essere usato conprint nomi[0]; " e "; nomi[3]oNed e Maria
b = array_prova[3] + array_prova[5]che imposta a variabile b a 4 + 2, o 6.Visto che lo spazio degli array viene allocato staticamente dal compilatore, tutti gli array vanno dichiarati a livello globale. Gli array locali sono illegali, così come lo sono array interi passati come paramentri. Comunque gli elementi singoli di un array sono parametri validi.
È possibile passare l'indirizzo di un array come parametro, così che la routine possa accedere agli elementi dell'array tramire il modificatore 'array'. Ad esempio, se elementi è un array che contiene:
elementi[0] = "mele"La routine:
elementi[1] = "arance"
elementi[2] = "calzini"routine Test(v)può essere chiamata usando
{
print array v[2]
}Test(elementi)per produrre in output "calzini", sebbene v sia un parametro (cioè una variabile locale), e non un array. La riga "print array v[2]" dice all'inteprete di considerare v come un indirizzo di un array, non come un valore in sé.È possibile usare anche gli array di stringhe, e Hugo prevede un modo per memorizzare una voce di dizionario in un array come una serie di caratteri usando il comando 'string':
string(<indirizzo array>, <voce diz.>, <max lungh.>)(<max lungh.> è necessario perché l'inteprete non ha modo di sapere quali sono i limiti dell'array).Ad esempio,
string(a, word[1], 10)memorizzerà fino a 10 caratteri da word[1] in a.NOTA: Nell'esempio precedente ci si aspetta che a abbia almeno 11 elementi, visto che 'string' memorizza un terminatore a 0 o carattere nullo dopo la stringa.
Ad esempio,
x = string(a, word[1], 10)memorizza fino a 10 caratteri di word[1] nell'array a, e restituisce la lunghezza della stringa memorizzata nella variabile x.(Le variabili dell'interprete 'parse$' e 'serial$' possono essere usate al posto delle voci di dizionario; vedere la sezione più avanti sulle "ROUTINE DI CONGIUNZIONE: ParseError" per una descrizione).
Nella libreria sono definite le funzioni StringCopy, StringEqual, StringLength e StringPrint, che sono estremamente utili quando si usano gli array di stringhe.
StringCopy copia un array di stringhe in un altro array.
StringCopy(<nuovo array>, <vecchio array>[, <lungh.>])Ad esempio,StringCopy(a, b)copia il contenuto di b in a, mentreStringCopy(a, b, 5)copia solo fino a 5 caratteri di b in a.x = StringEqual(<stringa1>, <stringa2>)StringEqual restituisce true solo se i due array di stringhe indicati sono identici. StringCompare restituisce 1 se <stringa1> è alfabeticamente più grande di <stringa2>, -1 se <stringa1> è inferiore di <stringa2>, e 0 se le due stringhe sono identiche.
x = StringCompare(<stringa1>, <stringa2>)StringLength restituisce la lunghezza di un array di stringhe, come in:
lungh = StringLength(a)e StringPrint stampa un array di stringhe (od una parte).StringPrint(<ind. array>[, <inizio>, <fine>])Ad esempio, se contiene "presto",StringPrint(a)stampa "presto", maStringPrint(a, 1, 4)stampa "res". (Il parametro <inizio> del primo esempio ha come valore predefinito 0, non 1 -- il primo elemento in un array è numerato 0).Un effetto collaterale interessante della possibilità di passare gli indirizzi degli array come parametri è che è possibile barare con l'indirizzo, così che, ad esempio,
StringCopy(a, b+2)copia b in a, cominciando dalla terza lettera di b (visto che la prima lettera di b è b[0]).Bisogna tenere a mente che gli array di stringhe e le voci del dizionario sono due animali completamente separati, e che confrontarli direttamente con StringCompare non è possibile. Cioè, mentre una voce di dizionario è un valore che rappresenta un indirizzo, un array di stringhe è una serie di valori ognuno dei quali rappresentante un carattere della stringa.
La libreria fornisce la funzione seguente per risolvere:
StringDictCompare(<array>, <voce dizionario>)che restituisce gli stessi valori (1, -1, 0) di StringCompare, a seconda del fatto che l'array di stringhe sia alfabeticamente superiore, inferiore o uguale alla voce di dizionario.(C'è un comando complementare a 'string', la funzione 'dict', che crea dinamicamente a runtime una nuova voce di dizionario. La sintassi è:
x = dict(<array>, <max lungh.>)dove i contenuti di <array> o parse$ vengono scritti nel dizionario, per un massimo di <max lungh.> caratteri, e l'indirizzo della nuova parola viene restituito.
x = dict(parse$, <max lungh.>)Comunque visto che questo richiede di estendere la lunghezza della tabella del dizionario nel file del gioco, è necessario prevederlo durante la compilazione. Inserendo
$MAXDICTEXTEND=<numero>all'inizio del codice sorgente scriverà un buffer di <numero> byte vuoti alla fine del dizionario.
(MAXDICTEXTEND è 0 per default).L'estensione dinamica del dizionario è usata, soprattutto, in situazioni dove il giocatore deve essere in grado di, ad esempio, dare un nome ad un oggetto, e poi riferirsi a quell'oggetto con il nuovo nome. In questo caso, le nuove parole devono esistere nel dizionario, e devono essere scritte usando 'dict'. Comunque, una linea guida per i programmatori è che dovrebbe esserci un limite al numero di nuove parole che il giocatore può creare, in modo che la lunghezza totale delle nuove voci non superi mai <numero>, tenendo a mente che la lunghezza di una voce di dizionario è pari al numero di caratteri più uno (il byte che rappresenta la lunghezza). In pratica la parola "test" richiede 5 byte.)
Esempio: Usare le stringhe
#include "hugolib.h"Il cui output sarà:array s1[32]
array s2[10]
array s3[10]routine ProvaStringhe
{
local a, lungha = "Questa è una stringa di prova."
lungh = string(s1, a, 35)
string(s2, "Mela", 9)
string(s3, "Pomodoro", 9)print "a = \""; a; "\""
print "(Indirizzo dizionario: "; number a; ")"
print "s1 contiene \""; StringPrint(s1); "\""
print "(Indirizzo array: "; number s1;
print ", lungh. = "; number lungh; ")"
print "s2 \`e \""; StringPrint(s2);
print "\", s3 \`e \""; StringPrint(s3); "\"""\nStringCompare(s1, s2) = ";
print number StringCompare(s1, s2)
"StringCompare(s1, s3) = ";
print number StringCompare(s1, s2)
}a = "Questa è una stringa di prova."Come è evidente una voce di dizionario non deve necessariamente essere una parola singola; qualunque parte di un testo che può essere trattata come valore può essere inserita nella tabella del dizionario.
(Indirizzo dizionario: 1005)
s1 contiene "Questa è una stringa di prova."
(Indirizzo array: 1637, lungh. = 30)
s2 "Mela", s3 "Sedano"StringCompare(s1, s2) = 1
StringCompare(s1, s3) = -1L'argomento 35 nella prima chiamata alla funzione 'string' consente di copiare fino a 35 caratteri di a in s1, ma visto che la lunghezza di a è di soli 30 caratteri, solo 31 valori (compreso lo 0 terminale) vengono copiati, e la lunghezza della stringa s1 è restituita in lungh.
Visto che "M(ela)" è alfabeticamente inferiore di "Q(esta...)", confrontando le due si ottiene -1. Come "S(edano)" è alfabeticamente superiore di "Q(esta...)" e StringCompare restituisce 1.
IV.h. Compilazione condizionale e flusso del programma
Il flusso del programma può essere controllato usando una varietà di costrutti, ognuno dei quali è costruito attorno ad un'espressione che valuta il falso [false] (zero) ed il non-falso (non-zero).La più semplice tra questi è l'istruzione 'if' [se].
if <espressione>NOTA: Le parentesi graffe non sono necessarie se il blocco di codice è una riga singola. Inoltre il blocco di codice condizionale può cominciare (ed anche finire) sulla stessa riga dell'istruzione 'if' a condizione che vengano usate le parentesi graffe.
{...blocco di codice condizionale...}if <espressione>Se le parentesi non venogno usate per una riga singola, il compilatore le inserisce automaticamente, sebbene una cura speciale deve essere tenuta quando si costruiscono blocchi di codice che nidificano diverse condizioni su singola riga.
...riga singola...if <espressione> {...blocco di codice condizionale...}
Mentre
if <espressione1>può essere interpretata in maniera corretta,
if <espressione2>
...blocco di codice condizionale...if <espressione1>non lo sarà.
for (...<espressione2>...)
if <espressione3>
...blocco di codice condizionale...(Tecnicamente parlando, il compilatore sbaglierà nell'individuazione della fine del ciclo del costrutto 'for' visto che il blocco di codice condizionale al suo interno si aspetta di finire con l'espressione 'for'. Ad ogni ciclo l'espressione 'for' non differenzia correttamente la fine del ciclo condizionale. Il risultato potrebbe essere un overflow dello stack dell'interprete perché l'interprete nidificherà continuamente l'esecuzione di cicli 'for' ricorsivi fino a che non finirà lo spazio sullo stack).
Il modo corretto di strutturare la stessa sezione di codice dovrebbe essere:
if <espressione1>NOTA: Il consiglio è quello di usare le parentesi graffe per chiarificare la struttura del codice ogni volta che si usano costrutti così complessi. Questo deve essere applicato in maniera particolare quando si mischiano espressioni 'if', 'for', 'while' e 'do-while', specialmente quando sono coinvolte chiamate ricorsive a funzioni. Sebbene il risultato possa apparire come voluto, il metodo per ottenerlo è scorretto, ed ogni esecuzione di tale costrutto potrebbe mandare in errore lo stack.
{
for (...<espressione2>...)
{
if <espressione3>
...blocco di codice condizionale...
}
}Usi più elaborati di 'if' coinvolgono l'uso di 'elseif' [altrimenti-se] ed 'else' [altrimenti].
if <espressione1>In questo caso l'interprete valuta ciascuna espressione fino a che trova quella vera ed allora la esegue. Poi il controllo passa alla prossima istruzione non if/elseif/else che segue il costrutto condizionale. Se nessuna espressione vera è stata trovata, il blocco di codice di default viene eseguito. Se, ad esempio, <espressione1> genera un valore non falso, allora nessuna delle espressioni seguenti viene valutata.
...primo blocco di codice condizionale...
elseif <espressione2>
...secondo blocco di codice condizionale...
elseif <espressione2>
...terzo blocco di codice condizionale...
...
else
...blocco di codice di default...Naturalmente, non tutte e tre ('if', 'elseif' e 'else') devono essere usate tutte le volte, e combinazioni semplici di "if-elseif" e "if-else" sono perfettamente valide.
In alcuni casi, l'istruzione 'if' potrebbe non andar bene per la chiarezza, ed il costrutto "select-case" [seleziona-caso] potrebbe essere più appropriato. La forma generale è:
select <var>In questo caso l'interprete esegue rapidamente una valutazione che è, essenzialmente
case <valore1>[, <valore2>, ...]
...primo blocco di codice condizionale...
case <valore3>[, <valore4>, ...]
...secondo blocco di codice condizionale...
...
case else
...blocco di codice di default...if <var> = <valore1> [or <var> = <valore2> ...]Non ci sono limiti al numero di valori (separati da virgole) che possono apparire su una riga che segue il 'case'. Si applicano le stesse regole della 'if' per racchiudere i blocchi di codice su più righe tra parentesi graffe (così per tutti gli altri tipi di blocchi condizionali).NOTA: I case non proseguono al successivo case. Bisogna pensare ai case che seguono il primo come a delle 'elseif' piuttosto che delle 'if'; una volta che un case a vero viene trovato, i seguenti sono ignorati.
I cicli possono essere codificati usando 'while' [mentre] e "do-while" [fai-mentre].
while <espressione>Entrambi eseguono il blocco di codice condizionale mentre <espressione> è vera (true). Si presume che in qualche modo il blocco di codice alteri l'<espressione> così che ad un certo punto diventa falsa (false); altrimenti il ciclo viene eseguito senza fine.
...blocco di codice condizionale...do
...blocco di codice condizionale...
while <espressione>while x <= 10L'unica differenza tra i due è che se <espressione> è falsa dall'esterno, il blocco di codice 'while' non viene eseguito. Il blocco di codice "do-while" viene eseguito almeno una volta anche se <espressione> è falsa dall'esterno.
x = x + 1do
{x = x + 1
print "x vale "; number x}
while x <= 10Il ciclo più complesso usa l'istruzione 'for' [per].
for (<assegnazione>; <espressione>; <modificatore>)Ad esempio:
...blocco di codice condizionale...for (i=1; i<=15; i=i+1)Prima di tutto l'interprete esegue l'assegnazione "i = 1".
print "i vale"; number i
Poi esegue l'istruzione print. Successivamente controlla se l'espressione vale true [vero] (se i è minore od uguale a 15). In questo caso esegue l'istruzione print e l'assegnazione del modificatore che incrementa i. Continua il ciclo fino a quando l'espressione vale false [falso].Non tutti gli elementi del costrutto 'for' sono necessari. Ad esempio l'assegnazione può essere omessa, come in
for (; i<=15; i=i+1)e l'interprete userà il valore esistente di i.Con
for (i=1;;i=i+1)Il ciclo viene eseguito senza fine, a meno che qualche altro mezzo di uscita viene fornito.L'espressione del modificatore non deve per forza essere un'espressione.
Potrebbe essere, ad esempio, una routine che modifica una variabile globale, che viene controllata nel ciclo 'for'.(Un secondo formato del ciclo 'for' è:
for <var> in <oggetto>che cicla attraverso tutti i figli di <oggetto> (se ne ha), impostando la variabile <var> con il numero dell'oggetto di ogni figlio in sequenza, così che
...blocco di codice condizionale...for i in valigiastampa i nomi di tutti gli oggetti presenti nell'oggetto valigia).
print i.nameIl modo più semplice di visualizzare la prima forma di un ciclo 'for' di Hugo, è che
for (<assegnazione>; <espressione>; <modificatore>)si traduce nell'equivalente di
...blocco di codice condizionale...<assegnazione>che a turno si traduce nell'equivalente di
[while] <espressione>
{
...blocco di codice condizionale...
<modificatore>
}<assegnazione>(D'altra parte questo non è un modo particolarmente facile di visualizzare qualunque cosa, e nella sua debolezza, forse giustifica l'esistenza di cicli 'while', "do-while" e 'for' non minacciosi).
:<etichetta1>
[if] <espressione>
{
...blocco di codice condizionale...
<modificatore>
jump <etichetta1>
}La conoscenza di come Hugo spezzi in una serie di nodi 'if' e 'jump' le istruzioni di ciclo comporta una facilità nell'analisi del flusso del programma usando Hugo Debugger (si veda l'Appendice E).
Come risulta ovvio dall'illustrazione fatta sopra (forse confusamente), Hugo supporta i comandi 'jump' [salta] e le etichette. Un'etichetta è semplicemente un identificatore specificato dall'utente preceduto dai due punti (':') all'inizio di una riga. Il nome dell'etichetta deve avere un identificatore univoco all'interno del programma. (Bisogna avere una certa cura nell'utilizzo dell'istruzione 'jump' -- generalmente è molto meglio usare delle alternative, visto che esiste la possibilità di sovraccaricare lo stack dell'interprete quando non si usano costruttori di clici standard).
È anche importante riconoscere -- particolarmente con le istruzioni 'select' e 'while' o "do-while" -- che l'espressione viene valutata tutte le volte che il ciclo viene eseguito, o, nel caso dell'istruzione 'select', per ogni case corrispondente. Il significato di questo è evidente nell'esempio seguente
select test.prop_routinedove prop_routine restituisce un valore da 1 a 3. La routine proprietà verrà eseguita per 3 volte distinte, una per ogni istruzione 'case'. Se prop_routine ha qualche altro effetto, come la modifica di una variabile globale o la stampa di un output, allora questo avverrà per 3 volte.
case 1
{...}
case 2
{...}
case 3
{...}Se questo effetto non è accettabile si può provare con
local test_val ! imposta una variabile localecosì che test.prop_routine viene chiamata una sola volta.
test_val = test.prop_routine ! e le assegna un valore
select test_val
case 1
{...}
...Un caso simile potrebbe essere
select random(3)si potrebbe avere qualcosa tipo:
case 1: {...}
case 2: {...}
case 3: {...}if random(3) = 1: {...}In altre parole un valore casuale differente potrebbe essere valutato tutte le volte. Una scelta migliore sarebbe
elseif random(3) = 2: {...}
elseif random(3) = 3: {...}local bUna parola chiave finale è importante nel flusso di un programma, e questa è 'break'. In qualunque punto di un ciclo potrebbe essere necessario uscirne immediatamente (e forse prematuramente). 'break' passa il controllo all'istruzione che segue il ciclo attuale.
b = random(3)
select b
case 1: {...}
...Nell'esempio
dol'istruzione 'break' causa la terminazione del ciclo 'while' <espressione2>, anche se <espressione2> è vera. Comunque il ciclo "do-while" <espressione1> continua ad essere eseguito.
{
while <espressione2>
{
...
if <espressione3>
break
...
}
...
}
while <espressione1>È stato detto prima che le righe che terminano con 'and' o 'or' continuano alla riga successiva nel caso di lunghe espressioni condizionali. Una seconda utile funzionalità è la capacità di usare una virgola per separare le opzioni in una espressione condizionale. Con il risultato che
if parola[1] = "uno", "due", "tre"sono tradotte in
while oggetto is open, not locked
if scatola not in salotto, garage
if a~=1, 2, 3if parola[1]="uno" or parola[1]="due" or parola[1]="tre"Fate caso al fatto che con confronto '=' o 'in', una virgola corrisponde ad un confronto in 'or'. Con '~=' od un confronto di un attributo, il risultato è un confronto in 'and'.
while oggetto is open and oggetto is not locked
if scatola not in salotto and scatola non in garage
if a ~= 1 and a ~= 2 and a ~= 3
©2000 Simone Zanella e ©2000 IF Italia. E' vietata la riproduzione.