Gioco del serpente

Avviare l'emulatore 8086 e creare un nuovo progetto basato sul template COM.

Nell'editor si presenterà la seguente pagina:

#make_COM#

ORG     100h

Nella parte iniziale del segmento si inseriscono le dichiarazioni dei dati, quindi l'esecuzione deve iniziare dall'istruzione che è identificata con la label Inizio. Si deve notare che la dichiarazione EQU (Equates) definisce una costante, ovvero un'associazione tra un nome simbolico ed un valore che non occupa spazio di memoria, ma serve all'assemblatore per sostituire il valore nell'istruzione ogni volta che trova il nome simbolico associato:

JMP     Inizio
; ------ sezione dichiarazioni ------
Lunghezza  EQU     7

L'array Serpente contiene le coordinate (Riga e Colonna dello schermo) delle parti che compongono il serpente (dalla testa alla coda).

Il byte inferiore è la parte sinistra, il byte superiore è la testa - [testa, coda]

Serpente DW Lunghezza DUP(0)

coda    DW      ?

Codici esacimali dei caratteri corrispondenti ai tasti cursore:

Sinistra	EQU     4Bh
Destra   	EQU     4Dh
Su      	EQU     48h
Giu    		EQU     50h

Direzione di movimento del serpente:

Direzione DB      Destra

Attesa DW    0

Segmento CODICE.
La prima istruzione chiama la procedura che stampa le istruzioni, al ritorno il programma resta in attesa di premere un tasto

Inizio:

CALL Istruzioni

; Attesa che venga premuto un tasto:
MOV AH, 00h
INT 16h

Disegno del serpente

La funzione 1, specificata in AH, dell'interrupt 10h fissa la dimensione, in numero di righe, del cursore. La prima riga è specificata in AH e l'ultima riga è specificata in CL. Se il numero di riga inizia con 2 si nasconde il cursore:

	MOV     AH, 1
	MOV     CH, 2Bh
	MOV     CL, 0Bh
	INT     10h

Si giunge in questo punto dopo aver premuto un tasto.

La funzione 5, specificata in AH seleziona la pagina video (in modalità testo) specificata in AL:

gioco:

; === selezione della prima pagina video 
	MOV     AL, 0  ; Numero di Pagina.
	MOV     AH, 05h
	INT     10h

Nell'array Serpente sono contenute le coordinate dello schermo in cui mostrare le 7 parti che compongono il serpente. Le coordinate sono contenute in due byte separati che la funzione 02h dell'interrupt 10h riceve nei registri DL e DH

; === Mostra la testa:
	MOV     DX, Serpente[0]

; Posiziona il cursore alle coordinate DL,DH
	MOV     AH, 02h
	INT     10h

In quella posizione la funzione 09 dell'interrupt 10h stampa il carattere '*' contenuto in AL con l'attributo di colore specificato in BL:

	MOV     AL, '*'
	MOV     AH, 09h
	MOV     BL, 0Eh ; attributi.
	MOV     CX, 1   ; Numero di caratteri da stampare.
	INT     10h

Viene copiata la posizione occupata dalla coda del serpente perchè, dopo che le parti precedenti sono state fatte avanzare nella direzione determinata dal tasto cursore premuto, si dovrà cancellare dallo schermo il carattere corrispondente alla coda:

	MOV     AX, Serpente[Lunghezza * 2 - 2]
	MOV     coda, AX

	CALL    muoviSerpente

=== Elimina la coda dopo l'avanzamento:
	MOV     DX, coda

la funzione 02 dell'interrupt 10h porta il cursore nelle coordinate DL,DH e poi, richiamando la funzione 09 dell'interrupt 10h si cancella la coda.

	MOV     AH, 02h
	INT     10h

; in quella posizione stampa ' ':
	MOV     AL, ' '
	MOV     AH, 09h
	MOV     BL, 0Eh ; attributi.
	MOV     CX, 1   ; singolo carattere.
	INT     10h

Lettura della tastiera

La funzione 01 dell'interrupt 16h interroga il buffer della tastiera e restituisce un valore booleano, tramite la flag di Zero, per indicare se c'è un tasto. Se la flag Z=1 non è stato premuto nessun tasto.

leggiTasto:
; === riconoscimento del comando:
	MOV     AH, 01h
	INT     16h
	JZ      nessunTasto

Se invece è stato premuto un tasto la funzione 00 dell'interrupt 16h legge il tasto, eliminandolo dal buffer, e rende disponibile il suo codice nel registro AL. I tasti speciali (Tasti Funzione, Home, End, Canc, ecc..) sono formati da due byte, il primo è sempre il carattere NUL (0), il secondo è disponibile in AH:

	MOV     AH, 00h
	INT     16h

	CMP     AL, 1Bh    ; il tasto ESC termina il programma
	JE      fine  ;

Se si tratta di un tasto cursore, Il secondo valore del tasto premuto viene scritto nella variabile Direzione:

	MOV     Direzione, AH

Se non è stato premuto nessun tasto si ripete l'avanzamento del serpente nella direzione indicata nella variabile Direzione

nessunTasto:
; === ripetizione del ciclo:
	JMP     gioco

Se è stato premuto il tasto ESC si ripristina la visualizzazione del cursore e si esce dal programma:

fine:
; Mostra nuovamente il cursore:
	MOV     AH, 1
	MOV     CH, 0Bh
	MOV     CL, 0Bh
	INT     10h
	RET

La procedura muoviSerpente

Questa procedura crea l'animazione muovendo le parti che compongono il serpente un passo in avanti. L'ultimo elemento dell'array Serpente (la coda)] si cancella, mentre le parti intermedie vengono copiate una posizione in avanti nell'array

Si inizializza ES per puntare al segmento 40h, dove sono contenute informazioni di sistema, in particolare il numero di righe e il numero di colonne del video:

muoviSerpente PROC NEAR

	MOV     AX, 40h
	MOV     ES, AX

Il registro DI viene inizializzato con l'indice dell'array che contiene il penultimo elemento dell'array Serpente, mentre nel registro CX si scrive il numero di elementi del serpente:

	MOV   DI, Lunghezza * 2 - 2
	MOV   CX, Lunghezza-1

Mediante un ciclo si muovono tutte le parti del corpo un passo in avanti.

Ogni elemento viene portato nell'elemento successivo:

avanza:
	MOV   AX, Serpente[DI-2]
	MOV   Serpente[DI], AX
	SUB   DI, 2
	LOOP  avanza

Prima di stampare la testa del serpente si esamina la variabile Direzione:

	CMP     Direzione, Sinistra
	  JE    vaiSinistra
	CMP     Direzione, Destra
	  JE    vaiDestra
	CMP     Direzione, Su
	  JE    vaiSu
	CMP     Direzione, Giu
	  JE    vaiGiu
	JMP     fermaSerpente       ; nessuna direzione scelta.

Se il serpente procede verso sinistra si deve decrementare il numero di colonna, prima di modificare le coordinate della testa contenute nell'elemento 0 dell'array Serpente.

Se però il serpente esce fuori dallo schermo (colonna < 0) allora si fa rientrare il serpente dall'altro lato. Occorre però conoscere il numero di colonne dello schermo per modificare il numero di colonna in cui deve comparire la testa del serpente. Questa informazione è disponibile nella variabile di sistema che si trova all'offset 4Ah nel segmento 40h.

vaiSinistra:
  MOV   AL, byte ptr Serpente[0]
  DEC   AL
  MOV   byte ptr Serpente[0], AL
  CMP   AL, -1
  JNE   fermaSerpente       
  MOV   AL, ES:[4Ah]    ; numero colonna.
  DEC   AL
  MOV   byte ptr Serpente[0], AL  ; rientra a destra.
  JMP   fermaSerpente

Analogamente, se il serpente si muove verso destra si deve incrementare la coordinata di colonna della testa, ma quando raggiunge l'estremità destra dello schermo, si deve modificare la coordinata di colonna portandola a 0.

vaiDestra:
	MOV   AL, byte ptr Serpente[0]
	INC   AL
	MOV   byte ptr Serpente[0], AL
	CMP   AL, ES:[4Ah]    ; numero colonna.   
	JB    fermaSerpente
	MOV   byte ptr Serpente[0], 0   ; rientra a sinistra.
	JMP   fermaSerpente

Le stesse considerazioni valgono quando il serpente si muove in verticale. Se il serpente sale si decrementa la coordinata di riga, altrimenti la si incrermenta, e quando giunge ad una delle estremità dello schermo deve rientrare dall'altra estremità. In questo caso il numero di righe dello schermo è specificato nell'offset 84h del segmento 40h

vaiSu:
	MOV   AL, byte ptr Serpente[1]
	DEC   AL
	MOV   byte ptr Serpente[1], AL
	CMP   AL, -1
	JNE   fermaSerpente
	MOV   AL, ES:[84h]    ; numero di riga -1.
	MOV   byte ptr Serpente[1], AL  ; ritorna in basso.
	JMP   fermaSerpente

vaiGiu:
	MOV   AL, byte ptr Serpente[1]
	INC   AL
	MOV   byte ptr Serpente[1], AL
	CMP   AL, ES:[84h]    ; numero di riga -1.
	JBE   fermaSerpente
	MOV   byte ptr Serpente[1], 0   ; ritorna in alto.
	JMP   fermaSerpente

La pressione di un tasto diverso da un tasto cursore esce dalla procedura

fermaSerpente:
	RET
muoviSerpente ENDP

Stampa di una stringa ZERO TERMINATED

La seguente procedura stampa una stringa terminata con il carattere ZERO.

Istruzioni PROC NEAR
    push 	SI	; salva il contenuto del registro SI
    mov 	SI, offset CS:Help
    PUSH    AX      ; salva il contenuto del registro AX

carattSucc:
    MOV     AL, CS:[SI]
    INC     SI            ; indice del prossimo byte.
    CMP     AL, 0
    JZ      stampato        
    MOV     AH, 0Eh       ; funzione macchina da scrivere
    INT     10h
    JMP     carattSucc
stampato:
    pop 	SI	; ripristina i valori dei registri
    POP     AX
    RET
Istruzioni ENDP

Variabile Help

Help DB "==== ISTRUZIONI ====", 13, 10
DB "Impostare la casella", 13, 10
DB '"step delay" a "0" prima di', 13, 10
DB "eseguire il programma, altrimenti", 13, 10
DB "il serpente avanza lentamente.", 13, 10, 13, 10

DB "Il controllo del movimento del serpente", 13, 10
DB "avviene con i tasti cursore", 13, 10, 13, 10

DB "Qualsiasi altro tasto", 13, 10
DB "ferma il serpente.", 13, 10, 13, 10

DB "Il tasto ESC termina.", 13, 10
DB "====================", 13, 10, 13, 10
DB "Premere un tasto per iniziare...", 0
END

La direttiva END al termine del programma informa l'assemblatore che non ci sono altre istruzioni da tradurre.

Esecuzione del programma e uso

Dopo aver premuto il pulsante Compila ed Emula,prima di premere il pulsante RUN, impostare la casella "step delay" a "0".

Per controllare la direzione del movimento del serpente agire sui tasti cursore. La pressione di altri tasti ferma l'avanzamento del serpente

Premere ESC per terminare.

Esercizi:

Eseguire il programma e osservare nel codice assembler che le costanti non sono state memorizzate in nessuna locazione, ma in tutte le istruzioni che le riferiscono è stato sostituito il loro valore.

Dopo ciascuna chiamata di procedura osservare il valore dei registri SP e IP e leggere i valori depositati sullo stack.