Zork, CP/M, le adventure Infocom e la personalizzazione del terminale

Source: https://archive.org/serve/ZorkI_r75_earlier_4amCrack/

Zork è il nome di una delle text adventure più famose della storia, rilasciata da Infocom per la prima volta nel 1977. Non la prima, che fu Colossal Cave Adventure (sempre nel 1977), ma di sicuro un nome che evoca in modo iconico questa categoria di giochi. In comune col capostipite prima citato ha l’essere stata sviluppata in prima battuta per il mainframe PDP-10.

Infocom ha distribuito successivamente decine di altre text adventure (ricordiamo Hitchhiker’s guide to the galaxy) basate sullo stesso modello. A differenza però del capostipite Colossal Cave Adventure che fu scritto in FORTRAN, tutte le text adventure di Infocom sono state in un linguaggio specifico per descrivere il mondo all’interno del quale si muove il giocatore di una adventure: oggi si direbbe un DSL (Domain Specific Language).

Linguaggio ZIL e Z-Machine multipiattaforma

Questo linguaggio fu denominato ZIL (Zork Implementation Language), è un linguaggio dichiarativo e ha una struttura simile a quella del LISP, e a partire dal sorgente viene compilato (tramite compilatori appositi, per esempio ZILF) in una sorta di bytecode, pronto a sua volta a essere eseguito da una virtual machine, che viene detta in questo caso Z-Machine, una sorta di ScummVM ante-litteram.

Ciò significa che il file eseguibile che viene lanciato per far partire il gioco non contiene il gioco in sé, bensì è un’implementazione della Z-Machine, che legge e interpreta il file compilato a partire dal sorgente ZIL.

Tale scelta lungimirante ha permesso a Infocom di rilasciare i suoi titoli su praticamente qualunque piattaforma esistente (negli anni, dalla versione PDP-11, a quella per CP/M, fino ad Amiga), in quanto bastava fare il porting della sola implementazione di Z-Machine per la specifica piattaforma, mentre i file compilati dai sorgenti ZIL dei giochi erano gli stessi ovunque.

In questo video del 1986 della BBC vediamo i dipendenti Infocom all’opera, nella produzione delle decine di titoli di text-adventure, che parlano del loro lavoro:

 

La versione CP/M

Su quasi tutte le piattaforme, Zork (più in generale, qualsiasi text-adventure di Infocom, in virtù di quanto detto prima) presenta un’interfaccia utente caratterizzata da una status bar in alto, spesso in reverse, in cui è indicato il luogo in cui ci si trova e il punteggio attualmente raggiunto. Tutto il resto del testo che viene prodotto subisce uno scrolling verso l’alto, tranne la status bar, che rimane lì, a darci queste indicazioni (come nell’immagine di introduzione a questo articolo).

Ecco però cosa succede quando lanciamo la versione CP/M (sistema usato: l’emulatore YAZE su Linux):

Non c’è una status bar e inoltre il word wrap taglia le righe ben prima degli 80 caratteri, di cui ogni linea è composta (come se fossero composte da 64 caratteri): c’è un motivo ben preciso.

CP/M, per sua natura, nasce per interagire con l’utente attraverso un terminale (fisico o emulato). Per visualizzare una status bar occorre controllare il cursore, portarlo in alto, passare alla modalità reverse, scrivere la status bar, riportare il cursore in basso e proseguire con la scrittura della descrizione del luogo. Per controllare il cursore occorrono particolari sequenze di caratteri dette sequenze di escape, e ogni modello di terminale ne possiede di proprie e incompatibili con altri modelli. Per esempio la sequenza di escape per portare il cursore in alto in un terminale compatibile con lo standard ADM31 è completamente diversa rispetto a quella usata in un terminale compatibile con lo standard VT100.

Per questo motivo, Infocom ha scelto di mantenere di default al minimo l’uso dei caratteri di controllo, limitandosi al ritorno a capo e al backspace, comuni in tutti i terminali, e ipotizzando anche una lunghezza delle linee dello schermo di 64 caratteri, per adattarsi ai terminali più “poveri” che non arrivano a 80 caratteri.

“Patchare” la Z-Machine per CP/M

Quelli di Infocom non sono affatto stati sprovveduti: non a caso nel paragrafo sopra ho evidenziato in grassetto le parole “di default” riferito al limitarsi ai caratteri di controllo di base (CR/LF e BACKSPACE). All’interno del file eseguibile .COM infatti ci sono zone ben definite pronte a essere personalizzate e sovrascritte per implementare determinate azioni sullo schermo di un determinato tipo di terminale.

Discutendo in due thread su Facebook (il primo sul gruppo di ZIL e il secondo su Vintage Computer Club) ho scoperto grazie agli utenti Ethan Dicks e Frode van der Meeren, il seguente link alla struttura del file ZORK.COM (e di qualsiasi altra text adventure di Infocom per CP/M):

Non solo, sempre dagli utenti sopra citati ho scoperto l’esistenza in Rete di un archivio .zip contenente un programma scritto in assembly per Intel 8080 pronto da personalizzare e assemblare, che altro non è che un “applicatore di patch” che automatizza la manipolazione sopra indicata proprio per il file ZORK.COM (e gli altri). Si trova a questi link:

All’interno di tale pacchetto, estraiamo dalla cartella ZORK i file chiamati:

  • 2DEFAULT.ASM
  • 2ADM3.ASM

Rappresentano i file per patchare ZORK2.COM rispettivamente con le impostazioni di default (2DEFAULT.ASM) e con le sequenze di escape per i terminali compatibili con lo standard ADM-3x (2ADM3.ASM).

Scrivere una patch per VT100

Partendo dal sorgente “2ADM3.ASM” scritto per il terminale ADM-3x, modifichiamolo (chiamandolo per esempio Z1VT100.ASM) per ottenere una patch per i terminali compatibili con lo standard VT100, implementato tra l’altro da tutti gli emulatori di terminale sui sistemi Unix-like (e da “PuTTY”, per esempio). I file di patch hanno una struttura molto precisa: non bisogna andare con le modifiche oltre la parte marcata col commento:

;The setup parameters end at this point.  Nothing below this point
;should be changed for any reason.

Le parti del sorgente invece da modificare e personalizzare sono quelle indicate dalle seguenti label:

CPMCPL: numero caratteri per riga

Il terminale VT100 prevede 80 caratteri per riga, da cui sottraiamo 1 per evitare problemi di “ritorni a capo non desiderati”, quindi utilizziamo il valore 79. La keyword “DB” significa “define byte”:

    CPMCPL: DB 79

CPMLPP: numero righe meno la status bar

Rappresenta il numero di righe dello schermo esclusa la status bar che vogliamo ottenere. Il VT100 prevede in totale 24 righe, meno uno fa quindi 23:

     CPMLPP: DB 23

CPMFN: nome del file

Rappresenta il nome del file eseguibile da patchare, senza l’estensione “.COM”. Deve essere tassativamente lungo 8 caratteri, nel caso il nome sia meno lungo, bisogna “riempire” con degli spazi (indicati con 20H in esadecimale). Nel caso volessimo patchare il file chiamato ZORK1.COM avremo:

     CPMFN: DB 'ZORK1',20H,20H,20H

CPMCLF: aggiunta line feed

Valore booleano pari a 1 in caso in cui a ogni carattere CR (carriage return) debba essere aggiunto un LF (line feed). Sotto CP/M funziona esattamente così, quindi deve essere pari a 1:

     CPMCLF: DB 1

CPMINV: delta da aggiungere per “reverse”

Valore da aggiungere al codice ASCII dei caratteri da stampare quando il carattere va stampato in reverse. Nel caso del VT100 il reverse non funziona in questo modo, c’è una sequenza apposita che vedremo successivamente, quindi poniamo a ZERO questa costante:

     CPMINV: DB 0

TINIT: sequenza di inizializzazione del terminale

Sequenza per posizionare il cursore in posizione “home” (in alto a sinistra sullo schermo). Sui terminali dove è possibile, stabilire inoltre una regione di scrolling che includa tutto lo schermo tranne la prima riga in alto, destinata a contenere la status bar. Inoltre, ripulire lo schermo. (tradotto pari-pari dal sorgente ASM).

Il primo valore deve rappresentare la lunghezza totale della sequenza, fino a un massimo di 32 byte.

Nel terminale VT100 tutto ciò è possibile con questa sequenza, che andremo a commentare in dettaglio (1Bh rappresenta ESC):

     TINIT: DB 24  ; 24 = lunghezza seq.
            DB 1Bh,'c',1Bh,'[H',1Bh,'[2J',1Bh,'[2r',1Bh,'[?6l',1Bh,'[3;1H'
  • ESC c = inizializzazione dello schermo
  • ESC [ H = posizione detta ‘HOME’, in alto a sinistra, del cursore
  • ESC [ 2J = cancellazione dello schermo
  • ESC [ 2r = setta la scrolling region dalla seconda riga fino alla fine
  • ESC [ ?6l = rende la numerazione delle righe indipendente dalla scrolling region (coordinate assolute)
  • ESC [ 3;1H = posiziona il cursore alla terza riga, prima colonna (coordinate assolute)

TRESET: ripristino del terminale alla fine del gioco

Per esempio, se è stata impostata una scrolling region va disattivata (quindi “estesa a tutto schermo”). Non è desiderabile cancellare lo schermo (traduzione dei commenti del sorgente ASM).

Il primo valore, come prima, rappresenta la lunghezza totale della sequenza, e non deve superare i 32 byte. Lo realizziamo così:

     TRESET: DB 7  ; 7 = lunghezza seq.
             DB 1Bh,'7',1Bh,'[r',1Bh,'8'
  • ESC 7 = memorizza l’attuale posizione del cursore
  • ESC [ r = disattiva la scrolling region, usando l’intero schermo
  • ESC 8 = ripristina la posizione del cursore precendentemente memorizzata

BLINE: inizio della status line

Sequenza per muovere il cursore alla posizione ‘HOME’ (in alto a sinistra) dello schermo. Se possibile, attivare la modalità reverse. Immediatamente dopo verrà scritta a schermo la status bar.

Il primo valore come nei casi precedenti rappresenta la lunghezza in byte della sequenza e non deve superare i 32 byte. Ecco il caso del VT100 con una piccola variante, cioè memorizziamo preventivamente la posizione del cursore prima di portarlo in HOME:

     BLINE: DB 9  ; 9 = lunghezza seq.
            DB 1Bh,'7',1Bh,'[H',1Bh,'[7m'
  • ESC 7 = memorizza l’attuale posizione del cursore
  • ESC [ H = posizione detta ‘HOME’, in alto a sinistra, del cursore
  • ESC [ 7m = attiva la modalità reverse

ELINE: fine della status line

Sequenza per muovere il cursore all’ultima riga dello schermo, eventualmente disattivare la modalità reverse se è stata attivata prima. Primo valore = lunghezza della sequenza.

Nel caso di VT100, dato che prima (nella BLINE) abbiamo memorizzato la posizione del cursore da cui eravamo partiti prima di scrivere la status bar, possiamo scegliere semplicemente di ripristinarla invece di portare il cursore all’ultima riga:

     ELINE: DB 7  ; 7 = lunghezza seq.
            DB 1Bh,'8',1Bh,'[27m'
  • ESC 8 = ripristina la posizione del cursore precendentemente memorizzata
  • ESC [ 27m = disattiva la modalità reverse.

Compilare e applicare la patch

Siamo a questo punto pronti a produrre l’eseguibile della nostra nuova patch. Se stiamo utilizzando l’emulatore YAZE abbiamo già tutti gli strumenti necessari già pronti, che sono due:

  • MAC.COM (Macro Assembler), che produce un file di testo contenente i valori esadecimali, risultato dell’assembler e corrispondenti al codice macchina Intel 8080
  • HEXCOM.COM, che produce un file binario corrispondente ai valori esadecimali contenuti nel file di testo precedentemente prodotto

Lanciamoli quindi in sequenza:

     A>MAC Z1VT100.ASM
     A>HEXCOM Z1VT100.HEX

Sarà a questo punto pronta la patch, che come indicato prima (nella label CPMFN), andrà a modificare il file chiamato ZORK1.COM. Lanciamo la patch con:

     A>Z1VT100

A questo punto lanciamo il nostro amato Zork, il risultato sarà questo:

Eccolo quindi con lo schermo ben pulito, con la sua status bar indicante luogo e punteggio, e con il word wrap che utilizza l’intera riga di terminale. Forse è stato faticoso ma ne è valsa la pena! 🙂

Non mi resta da augurarvi buon divertimento e attenti al GRUE!

Riferimenti esterni

 


																			

Have your say