La guida a Glulx Inform per autori di avventure testuali Inform versione 6.21(G0.35) http://www.eblong.com/zarf/glulxe/ traduzione di riktik http://ifitalia.oldgamesitalia.net http://www.riktik.com Nota di traduzione: se ravvisi inesattezze nella traduzione di questo documento contattami all'indirizzo riktik@tiscalinet.it *** A cosa serve questa guida? Se siete autori di avventure scritte con Inform, o possedete un poco di familiarità con questo linguaggio, e volete addentrarvi nel mondo di Glulx, allora il vstro primo passo dovrebbe essere leggere questo documento. Probabilmente non è il *solo* documento che dovreste leggere. Essendo questo, infatti, solo un tentativo di spiegare in poche parole la grafica, i suoni, o qualsiasi altro trucchetto reso possibile dalle Glk. Questa guida, in particolare, prova a rispondere alla prima domanda che potreste farvi: "Ho un gioco in Inform, e voglio compilarlo in Glulx invece che per la Z-machine. Cosa devo fare?" E, come bonus, toccheremo anche la risposta alla vostra seconda domanda "Quali trucchetti in più sono disponibili usando Glulx Inform al posto della Z-machine?" Ma sarà solo una toccata e fuga, uomo avvisato... Alcuni autori troveranno in questo documento tutto ciò di cui hanno bisogno. Se il vostro unico desiderio è scrivere un gioco in inform troppo corposo per una Z-machine, vi basterà leggere questa guida, compilare il codice in Glulx, e distribuire il gioco. * Cosa è questa roba chiamata Glulx? La spiegazione lunga, con ragionamenti, storia, aneddoti e giustificazioni filosofiche è disponibile sulla pagina web di Glulx (si veda URL in alto). Per dirla in poche parole, è una virtual machine. Esattamente allo stesso modo in cui la Z-machine è una virtual-machine. Potete scrivere un gioco in Inform, compilarlo per la Z-machine, e lanciarlo su un interprete per Z-machine; oppure compilarlo per Glulx e lanciarlo su un interprete Glulx. La differenza è che la Z-machine ha molte limitazioni nella sua progettazione che Glulx non ha, o meglio, ha in misura inferiore. * Allora è proprio semplice, vero? Il compilatore Glulx Inform è progettato per compilare lo stesso linguaggio standard di Inform. Se il vstro codice sorgente è compilato correttamente sotto Inform 6.21, allora dovrebbe essere tranquillamente compilabile in Glulx Inform, senza quasi nessun cambiamento. La parte restante di questo documento è dedicata proprio alle differnze tra "quasi nessun cambianto" e "nessun cambiamento". *** Ciò che Qualsiasi Autore di Giochi Dovrebbe Sapere * Introduzione Per cominciare a scrivere un gioco in Glulx Inform, si ha bisogno di: Il compilatore Glulx Inform (o bipiattaforma) La libreria bipiattaforma Inform La libreria italiana adattata a glulx Un interprete Glulx per il vostro computer Ossia la stessa quantità di cose che servirebbero per scrivere un gioco in Z-code -- compilatore, librerie, interprete Z-machine. Il compilatore è scaricabile dalla pagina web di Glulx. Funziona esattamente allo stesso modo del compilatore originale di Inform (Inform Z-Code). L'unica differenza è il tipo di file che genera. (Al momento, ho disponibili due compilatori. Uno genera solo file Glulx; l'altro può generare sia file Glulx che Z-code, a seconda che si usi il parametro -G o meno. E' pertanto preferibile il compilatore bipiattaforma.) La libreria bipiattaforma è anch'essa disponibile sulla mia pagina web. E' una versione bipiattaforma della versione 6/10 della libreria di Graham -- così, può essere usata per compilare sia in Z-code, sia in Glulx. In Z-code essa funziona esattamente come la libreria 6/10. In Glulx, essa compie lo stesso lavoro, ma diversi pezzi di codice sono implementati differentemente, tenendo conto delle differenza tra le due VM. Anche l'interprete lo trovate sempre li. Potete scaricarvi il codice sorgente, o anche gli eseguibili per Mac, DOS e Windows. (Se scaricate il sorgente, avrete bisogno per compilarlo delle librerie Glk. Se invece optate per gli eseguibili, le librerie Glk sono già inserite al loro interno.) Al momento (agosto 2003) non sono state distribuite librerie italiane ufficiali per Glulx. Tommaso Caldarola http://digilander.libero.it/tommyzombie/atschizo/schizo.html ha però adattato le librerie di Giovanni Riccardi http://www.composizioni.net/inform/ alla nuova virtual machine. Scrivere un gioco con questi strumenti è quasi lo stessa cosa che scriverne uno con gli strumenti di Inform in Z-code. Naturalmente bisogna fare attenzione ad alcuni dettagli: Se si vuole che il gioco rimanga (ancora) compatible con il compilatore di Inform per Z-code di Graham, si dovrà inserire un piccolo blocco di codice di compatibilità all'inizio del programma. Se si prende la lunghezza di una proprietà, usando l'espressione (obj.#prop)/2, si dovrebbe cambiarla con (obj.#prop)/WORDSIZE. Se si scrive una propria procedura DrawStatusLine(), usando l'assembly Z-code, si dovrà riscriverla usando le opportune chiamate alla libreria Glk. (Vedi la libreria bipiattaforma per il codice di esempio.) Se si usano le istruzioni "style", si dovrà riscriverle usando una chiamata Glk. Alcune funzioni nuove della libreria sono disponibili, ma solo quando si compila per Glulx. La seguente sezione le spiega più dettagliatamente. Siete liberi di saltare qualsiasi cosa di cui non abbiate bisogno. (il file sorgente Advent.inf è compilabile in Glulx senza alcun cambiamento.) * Scrivere codice bipiattaforma L'ideale, naturalmente, è scrivere un codice sorgente di Inform che possa essere compilato su entrambe le piattaforme. Non tutti potrebbero volerlo fare -- se il vostro gioco è troppo grande per girare su una Z-machine, non vi è ragione di rendere il codice sorgente bipiattaforma. Ma, in nome della chiarezza, questo documento assumerà che si intende realizzare un gioco per entrambe le VM. Il compilatore predefinisce due costanti che sono piuttosto utili. Quando si compila per la Z-machine, esso (effettivamente) definisce: Constant TARGET_ZCODE; Constant WORDSIZE 2; Quando si compila per Glulx, invece definisce: Constant TARGET_GLULX; Constant WORDSIZE 4; Ciò significa che si può usare #ifdef TARGET_ZCODE o #ifdef TARGET_GLULX per definire pezzi di codice che sono appropriati solo per una singola piattaforma. (Le librerie adattate di Inform usano ripetutamente questa strategia). (La costante WORDSIZE definisce il numero di byte in una parola. Vedi la prossima sezione.) C'è un inconveniente temporaneo: il compilatore per Inform 6.21 realizzato da Graham (per Z-code) al momento non definisce ancora queste costanti. Future versioni probabilmente lo faranno. Nel frattempo, per far uso delle librerie 6/10 adattate con il compilatore di Graham, si deve inserire il seguente codice all'inizio del sorgente (prima di qualsiasi istruzione Include): ! Istruzioni necessarie per compilare con l'attuale compilatore Inform 6.21 di Graham. #ifndef WORDSIZE Constant TARGET_ZCODE; Constant WORDSIZE 2; #endif; Questo codice *non* è necessario quando si compila un gioco Z-code con il mio compilatore bipiattaforma. Il compilatore bipiattaforma definisce WORDSIZE e l'appropriata costante TARGET_, in entrambe le piattaforme. * Grandezza delle parole La principale differenza tra Glulx e la Z-machine è che le parole sono lunghe quattro byte invece che due. Tutte le variabili sono valori a 32-bit, lo stack contiene valori a 32-bit, le proprietà sono valori a 32-bit. E così via. Nella maggior parte della programmazione in Inform, non ci si deve preoccupare di questo cambiamento. Per esempio, se si ha una array Array list --> 10; ---lo Z-code Inform alloca 10 parole --ossia, venti bytes -- e si può accedere a questi valori come list-->0 fino a list-->9. Se si compila lo stesso codice in Glulx Inform, il compilatore allocherà ancora una volta 10 parole -- quaranta byte -- e si potrà ancora accedere ai valori come list-->0 fino a list-->9. Ogni cosa funzionerà alla stessa maniera, con l'eccezione che i variabili potranno contenere valori più grandi di 65535. La tabella degli array riporta anche un valore di quattro-byte, e la prima parola di quattro-byte è la lunghezza dell'array. Stringhe e -> array, e la -> notation, si rifersiscono ancora a singoli byte. Non vi dovrebbe essere alcuna necessità di modificare il proprio codice qui. Vi è un caso importante in cui *dovrete* modificare il vostro codice: l'operatore .#. L'espressione obj.#prop restituisce la lunghezza della proprietà in byte. Dal momento che la maggior parte delle proprietà contiene parole, piuttosto che byte, è spesso comune avere un codice di questo tipo: len = (obj.#prop) / 2; for (i=0 : ii; Nei programmi Inform Glulx, è necessario dividere per 4 invece che per 2. Per rendere le cose più leggibili, si può usare la costante WORDSIZE. Così potreste rimpiazzare il codice precedente con questo: len = (obj.#prop) / WORDSIZE; for (i=0 : ii; Che sarà compilabile correttamente per entrambe le VM. In generale, dovreste cercare nel vostro codice l'operatore .# ; ogni volta che lo incontrate, trovate il "2" e sostituitelo con "WORDSIZE". * Istruzioni mancanti Alcune istruzioni di Inform sono specifiche per la Z-machine. Nel codice Glulx, esse dovrebbero essere sostituite con assebly Glulx o funzioni della libreria Glk. Queste istruzioni nella maggior parte dei giochi non sono usate; esse vengono utilizzate per lo più nelle librerie, la libreria 6/10 adattata (bipiattaforma) le modifica correttamente. Se provate ad usare una qualsiasi di queste istruzioni in Glulx, otterrete un errore di compilazione. Style: questa istruzione dovrebbe essere sostituita con la funzione delle Glk per cambiare l'aspetto dello stile. Vedi la sezione "Stili di testo". E' possibile che in future versioni del compilatore Glulx queste istruzioni siano implementate con "best-guess Glk style calls". Comunque, Glk fornisce più possibilità rispetto ai soliti grassetto/corsivo/altezza-fissa bold/italic/fixed-width imposte dalla Z-machine. Vale quindi la pena inserire esplicite chiamate Glk. save, restore: Sono precedure più complicate in Glulx rispetto allo Z-code. Non possono essere implementate senza utilizzare variabili e funzioni della libreria. Se volete qualcosa del genere, modificate o copiate le routine della libreria SaveSub e RestoreSub. read: Allo stesso modo, leggere una linea di testo in Glulx coinvolge la libreria; il compilatore non può generare da solo codice indipendente per farlo. Vedi la routine KeyboardPrimitive della libreria. * Thingie References Nella Z-machine, le strighe e le funzioni sono indirizzi "concentrati"; le parole del dizionario sono indirizzi normali; e gli oggetti del gioco sono rappresentati come numeri in sequenza dall'1 fino al #ultimo_oggetto. Questi range si sovrappongono. Pertanto una stringa, una parola del dizionario ed un oggetto potrebbero essere rappresentati dallo stesso valore. In Glulx, invece, sono tutti rappresentati da indirizzi normali. Pertanto due cose differenti avranno sempre valori differenti. In più, il primo byte trovato nell'indirizzo è un valore identificativo, che dice quale tipo di contenuto è rappresentato dall'indirizzo: E0...FF: Stringhe. E0: Non codificato, null-terminated strings. E1: Stringhe compresse. E2...FF: Riservato per future espansioni dei modelli di stringhe. C0...DF: Funzioni. C0: Stack-argument functions. C1: Local-argument functions. C2...DF: Riservato per future espansioni del modello delle funzioni. 80...BF: Riservato per usi futuri della VM. 40...7F: Riservato per l'uso della libreria di Inform. 70: Oggetti (e classi). 60: Parole del dizionario. 01...3F: Disponibile per l'uso dal codice del gioco. 00: Riservato per "nessun oggetto". Ora che avete visto questo sistema, potete dimenticarlo. Le funzioni metaclass e ZRegion prevedono il tipo di oggetto, e restituiscono gli stessi valori come sotto lo Z-code Inform. Un piccolo dettaglio: nello Z-code Inform, si può stampare una stringa compressa da quasiasi parte della RAM con la linea: print (address) word; Diciannove o venti timeout, ciò è usato per stampare le parole del dizionario. (Il valore di una parola del dizionario è un indirizzo RAM, e una parola del dizionario Z-Code inizia con una stringa compressa.) Questo si può ancora fare in Glulx Inform, ma *solo* per stampare parole del dizionario. (Le parole del dizionario in Glulx Inform non sono compresse, e iniziano con il byte 60 invece che con E0 o E1.) Se si vuole stampare una stringa generica, si usi il metodo abituale, ossia print (string). Un altro dettaglio: dal momento che gli oggetti non sono più sequenze di integer, non si possono scrivere cicli come for (obj=rock : obj0) { 5: ! evtype_Arrange DrawStatusLine(); 2: ! evtype_CharInput if (gg_event-->1 == gg_mainwin) done = true; } } #endif; ! TARGET_ Notate che la gg_mainwin è una variabile della libreria che contiene il numero identificativo della finestra, e gg_event è una array di quattro parole che viene usata dalla libreria per catturare gli eventi Glk. Essa è usata in KeyboardPrimitive, ma può essere usata anche a parte. Infatti, una versione più simpatica di questo codice è presente nella libreria, come KeyCharPrimitive(). Si veda dopo. Come probabilmente avrete notato, non è estremamente facile da leggere. Se pensate di fare molte chiamate alla Glk, potreste usare la libreria di Katre "infglk.h". Che definisce le funzioni contenitore e le costanti. Così ad esempio, potreste chiamare glk_set_style(style_Preformatted); ed ottenere lo stesso effetto che si ottiene con l'istruzione glk($0086, 2); ! set_style La libreria non usa questo trucco, per questione di semplicità, ma voi potete se lo preferite. * Spieghiamo la DrawStatusLine() L'ostacolo più duro per gli autori che decidono di passare da Inform a Glulx è scrivere una procedura DrawStatusLine(). Nello Z-code Inform, costituisce un gran pezzo di codice assembly, e tutti solitamente si limitano a riarragiarne uno precedente che funziona -- al massimo dando qualche sguardo alle specifiche della Z-machine. Sotto Glulx, è un gran pezzo di codice in chiamate alle funzioni della Glk, ma potete riarrangiarla alla stessa maniera. Ecco la procedura completa della DrawStatusLine() della libreria bipiattaforma. (Naturalmente, la libreria contiene due versioni -- Glulx e Z-code -- con #ifdefs per compilare quella giusta a seconda dei casi.) [ DrawStatusLine width height posa posb; ! Se non abbiamo una finestra status, non dobbiamo provare a ridisegnarla. if (gg_statuswin == 0) return; ! Se il giocatore non è nella locazione, non dobbiamo continuare a provare. if (location == nothing || parent(player) == nothing) return; glk($002F, gg_statuswin); ! set_window StatusLineHeight(gg_statuswin_size); glk($0025, gg_statuswin, gg_arguments, gg_arguments+4); ! window_get_size width = gg_arguments-->0; height = gg_arguments-->1; posa = width-26; posb = width-13; glk($002A, gg_statuswin); ! window_clear glk($002B, gg_statuswin, 1, 0); ! window_move_cursor if (location == thedark) { print (name) location; } else { FindVisibilityLevels(); if (visibility_ceiling == location) print (name) location; else print (The) visibility_ceiling; } if (width > 66) { glk($002B, gg_statuswin, posa-1, 0); ! window_move_cursor print (string) SCORE__TX, sline1; glk($002B, gg_statuswin, posb-1, 0); ! window_move_cursor print (string) MOVES__TX, sline2; } if (width > 53 && width <= 66) { glk($002B, gg_statuswin, posb-1, 0); ! window_move_cursor print sline1, "/", sline2; } glk($002F, gg_mainwin); ! set_window ]; Andiamo a spiegare il codice riga per riga. [ DrawStatusLine width height posa posb; Una normale funzione in Inform, con quattro variabili locali. ! Se non abbiamo una finestra status, non dobbiamo provare a ridisegnarla. if (gg_statuswin == 0) return; gg_statuswin è la variabile globale della libreria che contiene il riferimento alle finestra status. Alcune librerie non supportano una finestra status; se non ve ne è una, gg_statuswin contiene zero. E quindi, non c'è nulla da fare qui e si può operare un return immediatamente. ! Se il giocatore non è nella locazione, non dobbiamo continuare a provare. if (location == nothing || parent(player) == nothing) return; Questo caso non capiterà in molti giochi, ma è meglio prevederlo. (un modo in cui potrebbe verificarsi è se si pone una domanda YesOrNo() nella procedura Initialise().) glk($002F, gg_statuswin); ! set_window Imposta la corrente finestra di output. Tutto ciò che verrà stampato dopo questa istruzione andrà nella finestra status. Ancora una volta, notate la forma generale delle chiamate Glk. Il primo argomento di glk() dice quale funzione Glk viene richiamata; $002F è glk_set_window(). Segue il resto degli argomenti. StatusLineHeight(gg_statuswin_size); StatusLineHeight() è una funzione della libreria che semplicemente imposta la altezza della status line ad un determinato valore. E' meglio ricordare la grandezza corrente, e lasciare che la finestra la decida da sola se non si necessita di cambiamenti. (La variabile globale gg_statuswin_size di default è posta ad 1, e la libreria non la cambia in nessun punto.) glk($0025, gg_statuswin, gg_arguments, gg_arguments+4); ! window_get_size width = gg_arguments-->0; height = gg_arguments-->1; Questo codice richiama la glk_window_get_size(), per misurare la grandezza della finestra status. Gli ultimi due argomenti dovrebbero essere indirizzi nella memoria; La larghezza e la altezza sono scritte li. gg_arguments è una array della libreria disponibile solo a questo scopo. E' lunga otto parole, ma in questo caso abbiamo bisogno solo delle due prime posizioni. (La seconda posizione è data come gg_arguments+4, poichè una parola è lunga quattro byte in Glulx Inform.) Una volta che la chiamata è stata fatta, metteremo i due valori in due variabili locali. posa = width-26; posb = width-13; La posizione orrizzontale degli indicatori del punteggio e del conto dei turni di gioco. Questa linea appare sia nello Z-code che nelle versioni Glulx della DrawStatusLine(). glk($002A, gg_statuswin); ! window_clear Richiama la routine glk_window_clear() per pulire la finestra corrente. glk($002B, gg_statuswin, 1, 0); ! window_move_cursor Sposta il cursore nella posizione (1,0) -- che corrisponde al secondo carattere della prima linea. La posizione in alto a sinistra nella finestra Glk è infatti numerata come (0,0). Il che è differente dalle finestre in Z-code, dove la posizione in alto a sinistra corrisponde a (1,1). Altra differenza, solo come importanza, è che la glk_window_move_cursor() richiede due coordinate nell'ordine (X, Y). Lo Z-code @set_cursor opcode usa invece (Y, X). if (location == thedark) { print (name) location; } else { FindVisibilityLevels(); if (visibility_ceiling == location) print (name) location; else print (The) visibility_ceiling; } Questo è identico al codice Z. if (width > 66) { glk($002B, gg_statuswin, posa-1, 0); ! window_move_cursor print (string) SCORE__TX, sline1; glk($002B, gg_statuswin, posb-1, 0); ! window_move_cursor print (string) MOVES__TX, sline2; } if (width > 53 && width <= 66) { glk($002B, gg_statuswin, posb-1, 0); ! window_move_cursor print sline1, "/", sline2; } Stampa il punteggio e muove. Questo codice funziona allo stesso modo della versione Z-code. glk($002F, gg_mainwin); ! set_window ]; Imposta la finestra corrente alla precedente nella story window, e return. La sola differenza che potreste notare è che la versione Z-code verifica ((0->1)&2 == 0). Questo è un piccolo header della Z-machine, che nei giochi V3 è usato per dire all'interprete di disegnare una status line con il "Tempo" invece che con il "Puteggio / Turni". La routine DrawStatusLine() Z-code emula questo comportamento. Naturalmente i giochi V3 sono rari, e Glulx non ha alcun piccolo header. Così la versione Glulx delle routine tralascia completamente questa idea. Se volete mostrare il tempo, rimpiazzate la DrawStatusLine e scrivete il vostro codice. Le spiegazioni qui esposte dovrebbero bastare a iniziare. Ancora una volta, se non volete perder tempo il poco tempo che rimane della vostra breve vita andando a cercare le costanti nelle tabelle, potete usare le librerie "infglk.h". * Se si scrive una estensione alla Libreria di Inform Non tutti gli autori di giochi vorranno scrivere codice sorgente in Inform tenendo conto di entrambre le piattaforme. Molti saranno interessati a creare un gioco in Glulx, e imposteranno il loro codice unicamente per esso. In ogni caso, una libreria supplementare *dovrebbe* essere bipiattaforma, poichè potrebbe essere usata in giochi che sfruttano sia lo Z-code che Glulx. Potrebbe anche essere usata sia con il compilatore di Graham (solo per Z-code), sia con i miei compilatori. Quindi, dovreste probabilmente includere una hack di portabilità all'inizio del sorgente della libreria: ! Ciò è necessario per compilare con l'attuale compilatore Inform 6.21 di Graham. #ifndef WORDSIZE Constant TARGET_ZCODE; Constant WORDSIZE 2; #endif; Dopo di che si può usare WORDSIZE nel proprio codice, ed inserire le codizioni #ifdef TARGET_... , se necessario. Nota: usate questo pezzo di codice esattamente come è dato. Non tralasciate la linea TARGET_ZCODE , anche se la libreria supplementare non ne fa uso. La spiegazione è lasciata come esercizio. *** Cose disponibili solo in Glulx * Nuove funzioni della Libreria KeyCharPrimitive(); Questa routine aspetta la pressione di un singolo tasto, e restituisce il carattere premuto (da 0 a 255, o uno dei codici chiave speciali delle Glk). Potete anche chiamare questa routine usando uno o due argomenti, come in questo caso: KeyCharPrimitive(win, nostat); Se win è diverso da zero, il carattere immesso va alla finestra Glk specificata (invece di gg_mainwin, finestra di default.) Se nostat è diverso da zero l'evento riarrangiamento della finestra è restituito immediatamente come valore 80000000 (invece del comportamento di default, che consiste nel chiamare la DrawStatusLine() ed aspettare.) StatusLineHeight(); Questa routine cambia l'altezza della status line, come potreste aspettarvi. La routine standard DrawStatusLine() la richiama ad ogni turno, il che non è un male, dal momento che la StatusLineHeight() è piuttosto utile. Se si rimpiazza la DrawStatusLine(), conviene mantenere questa convenzione. (Le routine del menu della libreria sono in sintonia con la status line, e è dato alla DrawStatusLine() il compito di resettarle dopo che il menu a terminato il suo lavoro.) DecimalNumber(num); Questa stampa num come numero decimale. E' di fatto identica a: print num; ...ed è così che viene implementata. Ma potrebbe essere utile unitamente alle seguenti funzioni: PrintAnything(obj, ...); Questa stamperà ogni cosa conosciuta dalla libreria. Gestisce stringhe, funzioni (con argomenti opzionali), oggetti, proprietà degli oggetti (con argomenti opzionali), e parole del dizionario. Chiamare: E' equivalente a: ------- ---------------- PrintAnything() PrintAnything(0) PrintAnything("string"); print (string) "string"; PrintAnything('word') print (address) 'word'; PrintAnything(obj) print (name) obj; PrintAnything(obj, prop) obj.prop(); PrintAnything(obj, prop, args...) obj.prop(args...); PrintAnything(func) func(); PrintAnything(func, args...) func(args...); Argomenti extra dopo una stringa o una parola del dizionario sono sicuramente ignorati. Il (primo) argomento che si passa alla routine è sempre interpretato come a riferimento ad una cosa, non come un integer. Ciò spiega il perchè nessuna dell forme precedentemente mostrate stampa una integer. Naturalmente, si può ottenere lo stesso effetto chiamando PrintAnything(DecimalNumber, num); ...che è il momento in cui torna utile la funzione DecimalNumber(). Si può anche, naturalmente, usare altre funzioni della libreria, e fare trucchi tipo PrintAnything(EnglishNumber, num); PrintAnything(DefArt, obj); Nessuno di questi sembra molto utile. Dopo tutto, ci sono già dei modi per stampare tutte queste cose. Ma PrintAnything() è fondamentale per implementare la seguente funzione: PrintAnyToArray(array, arraylen, obj, ...); Questa funzione lavora allo stesso modo, con l'eccezione che invece di stampare a schermo, l'output è trasferito all'array nominata. I primi due argomenti devono essere l'indirizzo della array e la sua massima lunghezza. Fino ad essa i caratteri verranno scritti nell'array; quasiasi extra sarà silenziosamente scartato. Ciò significa che non dovrete preoccuparvi di eventuali sovrascritture nell'array. La funzione PrintAnyToArray() restituisce il numero di caratteri generati. (Che potrebbero essere di più della lunghezza dell'array. Rappresenta l'intero testo che viene dato in output, non il numero limite scritto nell'array.) E' più sicuro annidare le fuzioni PrintAnyToArray(). Cioè, potete richiamare PrintAnyToArray(func), dove func() è una funzione che richiama a sua volta PrintAnyToArray(). (Naturalmente, se entrambe dovessero cercare di scrivere la *stessa array*, il caos sarebbe assicurato.) E' consentito che arraylen sia pari a zero (nel qual caso l'array è ignorata, e può essere zero tranquillamente.) Ciò scarta *tutto* l'output, e semplicemente restituisce il numero di caratteri generati. Potete usare questo artifizio per trovare la lunghezza di qualsiasi cosa -- anche di una chiamata ad una funzione. * Nuovi Entry Points Un entry point è una funzione che si può inserire o meno nel proprio codice; la libreria la richiamerà solo se è presente, e la ignorerà nel caso opposto. (Vedi l'inform DM) La libreria bipiattaforma possiede alcuni entry point che aiutano l'autore nella scrittura di interfacce più complicate del solito -- giochi con suoni, disegni, finestre supplementari, ed altri simpatici trucchetti offerti dalla Glk. Se state scrivendo solo un gioco in stile Infocom standard, potete ignorare questa sezione. HandleGlkEvent(ev, context, abortres) Questa entry point è chiamata ogni volta che accade un evento Glk. L'evento potrebbe indicare qualunque cosa, una linea di input dal giocatore, il ridimensionamento di una finestra o il ridisegno di una immagine, un rintocco dell'orologio, un click del mouse e così via. La libreria gestisce tutti questi eventi necessari per un normale gioco in stile Infocom. E' necessario prevedere una funzione solo se si vuole aggiungere qualche funzionalità extra. L'argomento ev è una array di quattro parole che descrive l'evento. ev-->0 è il tipo di evento; ev-->1 è la finestra coinvolta (se rilevante); ed ev-->2 e ev-->3 sono informazioni extra. L'argomento context è 0 se l'evento accade durante la linea di input (comandi normali, YesOrNo(), o qualche altro uso della funzione della libreria KeyboardPrimitive(); 1 indica che l'evento è accaduto durante la pressione di un carattere input (quasiasi uso della funzione della libreria KeyCharPrimitive(). L'argomento abortres è usato solo se si vuole cancellare l'input del giocatore e forzare un particolare risultato; si veda sotto. Se restituite 2 dal HandleGlkEvent(), l'input del giocatore sarà immediatamente abortito. E' richiesto un po' di codice in più: * Se l'evento è un input carattere (context == 1), dovete richiamare la funzione Glk cancel_char_event function, e quindi impostare abortres-->0 al carattere che volete sia restituito. Quindi restituite 2; KeyCharPrimitive() finirà e restituirà il carattere, come se il giocatore lo avesse premuto. * Se l'evento è una linea di input (context == 0), dovete richiamare la funzione Glk cancel_line_event. (Potete passare un argomento all'array per controllare quando il giocatore ha premuto il tasto.) Quindi, inserire la lunghezza dell'input perchè restituisca abortres-->0. Se è diversa da zero, scrivere il carattere input in sequenza nell'array di partenza abortres->WORDSIZE, sino a (ma non includendolo) abortres->(WORDSIZE+len). Non eccedete i 256 caratteri. Quindi restituite 2; KeyboardPrimitive() terminerà e restituirà la linea. Se restituite -1 dalla funzione HandleGlkEvent(), l'input del giocatore continuerà anche dopo aver premuto un tasto (per l'input carattere) o dopo il tasto invio (per l'input linea). (Non so se è utile, ma potrebbe esserlo.) You must re-request input by calling request_char_input or request_line_input. Ogni altro valore restituito dalla funzione HandleGlkEvent() (una normale restituzione, rfalse, o rtrue) sicuramente non condizionerà l'input del giocatore. InitGlkWindow(winrock) Questa entry point è richiamta dalla libreria quando imposta la finestra standard: la finestra story, la finestra status, e (se usate caselle in evidenza) altre finestre in evidenza. Le prime due finestre sono create alla partenza del gioco (prima della funzione Initialise()). Il terzo tipo di finestre sono create e distrutte quando è necessario. E' chiamata in cinque phases: * La libreria richiama InitGlkWindow(0). Che sopravviene proprio all'inizio dell'esecuzione, anche prima della routine Initialise(). Potete impostare qualsiasi situazione che vogliate. (naturalmente, ricordate che le finestre story e status potrebbero già esistere -- ad esempio, se il giocatore ha semplicemente dato il comando "ricomincia".) Questo è un buon momento per impostare gg_statuswin_size ad un valore diverso da 1. Restituire 0 per procedere con l'impostazione standard delle finestre della libreria, o 1 se avete creato per conto vostro tutte le finestre. * La libreria richiama InitGlkWindow(GG_MAINWIN_ROCK), prima di creare la finestra story. Questo è un buon momento per impostare gli stili di testo per la finestra story. Restituite 0 per lasciare che la libreria crei la finestra; restituite 1 se avete creato per conto vostro una finestra e l'avete posta in gg_mainwin. * La libreria richiama InitGlkWindow(GG_STATUSWIN_ROCK), prima di creare la finestra status. Ancora una volta, restituite 0 per lasciare che la libreria lo faccia per voi; restituite 1 se avete creato una vostra finestra e l'avete posta in gg_statuswin. * La libreria richiama InitGlkWindow(1). Che è la fine dell'impostazione delle finestre; Potete cogliere questa opportunità per aprire altre finestre. (Altrimenti potete farlo nella vostra routine initialise(). Non cambia molto.) * La libreria richiama InitGlkWindow(GG_QUOTEWIN_ROCK), prima di creare la finestra quote box. Cio non accade durante l'inizializzazione del gioco; la finestra quote box è creata durante il gioco, quando stampate un quote, e distrutta al turno successivo. Come al solito, restituite 1 per indicare che avete creato una finestra in gg_quotewin. (Il numero desiderato di linee per la finestra può essere trovato in gg_arguments-->0.) In ogni modo gestiate l'inizializzazione delle finestre, ricordate che la libreria richiede una gg_mainwin. Se non ne create una, e non lasciate che la libreria lo faccia per voi, il gioco verrà terminato. Viceversa, la finestra status e quote sono opzionali; la libreria può tranquillamente funzionare senza di esse. IdentifyGlkObject(phase, type, ref, rock) Questa entry point è richiamate dalla libreria per lasciare che sappiate quale oggetto Glk esiste. Dovrete inserire questa funzione nel caso in cui decidiate di creare una qualsiasi finestra, filerefs, file stream, o canale sonoro oltre quelli standard della libreria. (Ciò si rende necessario dal momento che dopo un comando di caricamento, di reinizio o di undo le vostre variabili globali che contengono gli oggetti Glk potrebbero essere inesatte.) Questa viene richiamata in tre phase: * La libreria richiama la funzione IdentifyGlkObject() con phase==0. Qui dovreste impostare tutti i riferimenti agli oggetti Glk a zero. * La libreria richiama la funzione IdentifyGlkObject() con phase==1. Sopravviene una volta per ogni finestra, stream, e fileref che la libreria non riconosce. (La libreria gestisce le due finestre standard, e i file e gli stream che hanno a che fare con i comandi di salvataggio, copia e registrazione. Dovrete quindi unicamente combattere con gli oggetti che create.) Dovreste impostare qualsiasi riferimento in modo appropriato all'oggetto. Per ogni oggetto: il tipo sarà 0, 1, 2 per finestre, stream, fileref rispettivamente; ref sarà il riferimento all'oggetto; e rock sarà la pietra dell'oggetto, dalla quale potrete riconoscierlo. * La libreria richiama la funzione IdentifyGlkObject() con phase==2. Che sopravviene una volta, dopo tutte le altre funzioni, e che vi da la possibilità di riconoscere gli oggetti che non sono ne finestre, ne stream ne filerefs. Se non create nessun altro oggetto, potreste ignorarla. Dovreste però cogliere l'occasione di aggiornare tutti gli oggetti Glk allo stato del gioco che fosse cominciato o caricato. (Per esempio, ridisegnare le finestre, o impostare la giusta musica di sottofondo.) * Stack-Argument Functions Di default, gli argomenti delle funzioni operano in Glulx allo stesso modo che in Z-code. Quando chiamate una funzione, gli argomenti che passate sono scritti nelle variabili locali della funzione stessa, in ordine. Se passate troppi argomenti, quelli in più saranno scartati; se ne passate pochi, le variabili locali non usate assumeranno valore zero. In ogni caso, la VM Glulx supporta un secondo tipo di funzioni. Potete definire una funzione di questo tipo dando "_vararg_count" come nome del primo argomento della funzione. Ad esempio: [ StackFunc _vararg_count ix pos len; ! ...code... ]; Se fate questo, gli argomenti della funzione *non* verranno scritti nelle variabili locali. Essi saranno inseriti nello stack; dovrete usare l'assembly di Glulx per tirarli fuori. Tutte le variabili locali saranno inizializzate a zero, con l'eccezione di _vararg_count, che (come potreste immaginare) contiene il numero di argomenti che vengono passati. Notate che _vararg_count è una variabile locale normale, all'infuori del suo valore iniziale. Potrete assegnargli un valore, incrementarla o decrementarla, usarla come espressione, e così via. Le funzioni stack-argument sono molto utili se si vuole una funzione con argomenti variabili, o se volete scrivere una funzione contenitore che passa i suoi argomenti ad una ulteriore funzione. (I lettori più attenti potrebbero notare che questa non è esattamente la struttura che le specifiche della Glulx VM descrive. Una funzione C0-type comincia con un valore extra sullo stack, che specifica il numero di argomenti che lo seguono sullo stack. Naturalmente, ciò è nascosto ai vostri occhi dal compilatore. Quando scrivete una funzione _vararg_count, il compilatore genera automaticamente il codice per far uscire questo valore dallo stack e inserirlo nella variabile locale _vararg_count.) * Lunghezza limitata print_to_array La metaclass String di Inform ha un metodo interno print_to_array. str = "Questa è una stringa."; len = str.print_to_array(arr); ...scriverà il contenuto della stringa nell'array, a cominciare dal byte 2. La prima parola (arr-->0) conterrà il numero di caratteri scritti, come farà len. Ciò funziona alla stessa maniera in Glulx Inform, con la solita eccezione che la stringa comincia a 4 byte, così che c'è una locazione per i quattro-byte arr-->0. Comunque, esiste anche una forma estesa in Glulx. len = str.print_to_array(arr, 80); ...scriverà non più di 76 caratteri nell'array. Se arr è una array di 80-byte, potrete star sicuri che essa non sarà sovrascritta. (Non provatela con il secondo argomento inferiore a 4.) Il valore scritto in arr-->0, e il valore restituito, *non* sono limitati al numero di caratteri scritti. essi rappresentano il numero di caratteri nella stringa completa. Ciò significa che len = str.print_to_array(arr, 4); ...è un lungo ma perfettamente valido modo di trovare la lunghezza di una stringa. (e in questo caso, arr ha bisogno di essere lungo 4 byte.) * Migliori variabili di stampa Z-code Inform supporta 32 variabili di stampa, @00 to @31, che si possono includere in una stringa e quindi impostare con una istruzione. string num "value"; In Glulx, questo limite è stato elevato a 64. (Eventualmente, il limite potrebbe essere oggetto essere una opzione di linea di comando di inform. Potrebbe, infatti, essere facilmente elevato a 100, o anche di più se la sintassi delle stringhe di inform venisse cambiata in modo da permettere tre digitazioni dopo il segno @.) Inoltre, in Glulx potete impostare queste variabili per ogni stringa o valore di funzione. Se usate una funzione, la funzione dovrà essere chiamata senza argomenti e il risultato sarà scartato; dovreste perciò stampare l'output desiderato all'interno della funzione. Un valore stringa sarà semplicemente stampato. In Glulx, diversamente dallo Z-code, una variabile stringa stampabile può contenere al suo interno codici @.., permettendo la ricorsione. Potete annidarli tanto profondamente quanto volete. Naturalmente è una cattiva idea causare una ricorsione infinita. Ad esempio, string 3 "This is a @03!"; print "What is @03?"; ...provocherà certamente un crash dell'interprete. *** Trucchi di cui probabilmente non avrete bisogno Se volete definire una vostra classe di oggetti, potete farlo. (Solitamente non è necessario - è più facile rappresentare le proprie carabattole come oggetti di inform -- ma è possibile.) Definite un qualsiasi blocco di memoria il cui primo byte è in un range 01 to 3F. Poi passate il suo indirizzo come ogni altro riferimento. Le funzioni della libreria come metaclass e PrintAnything non lo capiranno, ma se prendete un riferimento, potete con una certa attendibilità dire la differenza tra le tue cose e le cose della libreria esaminando il primo byte.