BreakIn (20-Liner BASIC Breakout per ZX Spectrum)

Read in English

BreakIn è un “clone” del gioco Breakout per ZX Spectrum, realizzato in 20 linee, costituite da un massimo di 80 caratteri, di Sinclair BASIC. Ho sviluppato BreakIn in occasione del Breakout Basic Challenge, organizzato dall’Associazione Culturale RetroProgramming Italia 8 bit e oltre.

Schermata del gioco BreakIn

Scopo del gioco è abbattere il muro di mattoncini colorati posto nella parte superiore dello schermo, facendovi rimbalzare contro la “palla” , evitando che questa cada oltre il limite inferiore dello schermo. La palla deve essere colpita con la barra azzurra in basso, che può essere mossa a destra o sinistra mediante la pressione dei tasti z e x.

Puoi giocare a BreakIn direttamente sul tuo browser sul mio sito (è richiesta comunque la presenza della tastiera sul tuo device), oppure giocarlo e scaricarlo (incluso il codice sorgente) dalla pagina del progetto su itch.io.

Implementazione

Per l’implementazione mi sono ispirato (ma senza spiare il codice! 😉) all’approccio descritto in Attribute Smash!, gioco in lizza per la ZX Spectrum BASIC Jam, ospitata un paio di anni fa su itch.io e a cui avevo partecipato con baSnake.

L’idea è quella di generare l’immagine scrivendo direttamente nell’area di memoria dedicata agli attributi dello schermo, la cui organizzazione è decisamente più lineare rispetto al display file, mediante POKE. La memoria video dello Spectrum è infatti organizzata in due sezioni:

  • Display file (inizio al byte: 16384, lunghezza: 6144 byte), che memorizza l’immagine “monocromatica” – in un formato più comodo al computer che all’utilizzatore – a piena risoluzione 256x192;
  • Attributi (inizio: 22528, lunghezza: 768), che definisce i colori di “inchiostro” e “carta” per ciascun quadrato di 8×8 pixel.

Partendo dalla situazione in cui il display file è interamente inizializzato a 0 e tutti gli attributi sono impostati allo stesso valore, è possibile “accendere” un quadrato di 8×8 pixel modificando il colore della “carta” nell’attributo corrispondente; ciò permette di lavorare a bassa risoluzione (32×22), ma con velocità superiore rispetto all’uso di PRINT AT o istruzioni grafiche. Partendo da questo presupposto, mi sono reso conto che potevo avvalermi della funzione PEEK per rilevare la presenza o meno di mattoncini nelle celle adiacenti a quelle della palla e quindi per determinare le collisioni.

Per approfondimenti riguardo l’organizzazione della memoria video dello Spectrum, si vedano: L Break Into Program: Screen Memory Layout e il capitolo 24 del manuale dello ZX Spectrum.

Utilizzo in BreakIn dell’area di memoria dedicata agli attributi

Descrizione dettagliata del programma

Variabili utilizzate

A$= input (“z”=sinistra, “x”=destra)
A = indirizzo di memoria nell’area degli attributi corrispondente al “pixel” più a sinistra della barra
B = indirizzo di memoria nell’area degli attributi corrispondente alla palla
X = coordinata X palla
Y = coordinata Y palla
V = velocità X palla (-1=sinistra; 1=destra; 0=movimento solo verticale)
W = velocità Y palla (1=basso; -1=alto)
R = numero di mattoncini rimanenti
D = utilizzata per determinare collisione tra barra e palla (in base a differenza tra A e B)
T = utilizzata per identificare i mattoncini colpiti in caso di collisioni con la palla
U = utilizzata per determinare collisioni tra palla e mattoncini (variabile di supporto introdotta per ridurre la lunghezza della linea 9 di programma)
Z = utilizzata per determinare eventuale ulteriore collisione tra palla e mattoncini
H = collisione tra palla e mattoncino (1=no collisione, 0=collisione)
I, J = iteratori
L = vite
S = punteggio

Funzionamento del codice

Inizializzazioni vite e punteggio e chiamata alle routine di inizializzazione:

1 BORDER 1:INK 0:PAPER 0:CLS:LET L=6:LET S=0:GO SUB 42:GO SUB 30

Loop principale:
Lettura tasto premuto, reset flag collisione palla/mattoncini, controllo se gioco completato:

2 LET A$=INKEY$:LET H=1:IF R=0 THEN BORDER 4:PAUSE 0:GO TO 1

Controllo tasto input e riposizionamento della barra:

3 IF A$="z" AND A>23200 THEN LET A=A-1:POKE A,40:POKE A+3,0:GO TO 5
4 IF A$="x" AND A<23229 THEN POKE A+3,40:POKE A,0:LET A=A+1

Controllo se la palla è colpita dalla barra ed eventuale rimbalzo. D rappresenta la “distanza” tra la pallina e la barra: se la pallina si trova sulla riga superiore rispetto alla barra e la distanza orizzontale tra la pallina e il centro della barra è < 3, significa che la pallina sta per essere colpita. La velocità orizzontale della pallina cambia in base al punto di impatto sulla barra:

5 LET D=B-A+31:IF ABS D<3 THEN LET W=-1:LET V=SGN D:BEEP .03,12

Controllo se la palla rimbalza contro i bordi dello schermo (Y=0, X=0, X=31) e aggiornamento di velocità orizzontale e verticale. L’istruzione LET W=(Y=0)+(Y<>0)*W equivale a IF Y=0 THEN LET W=1; è meno leggibile, ma è un espediente che ho escogitato (ho scoperto l’acqua calda 😊) per avere due IF differenti sulla medesima linea, risparmiando una riga di codice:

6 LET W=(Y=0)+(Y<>0)*W:IF X=0 OR X=31 THEN LET V=-V:GO TO 10

Controllo collisione verticale (mattoncino sopra o sotto la palla) e chiamata alla routine di collisione.
B+W*32 è l’indirizzo dell’attributo che identifica la “cella” sopra o sotto la pallina (in base al segno di W), per cui se PEEK U <> 0, significa che la cella è occupata e quindi si è verificata una collisione, pertanto si inverte la direzione verticale (segno di W). Se la nuova posizione della pallina sarà occupata da un altro mattoncino (Z), allora si invertirà anche la direzione orizzontale (segno di V):

7 LET U=B+W*32:IF PEEK U THEN LET T=U:LET W=-W:GO SUB 40:IF Z THEN LET V=-V

Controllo collisione orizzontale (mattoncino a destra o sinistra della palla) e chiamata alla routine di collisione.
Il controllo è effettuato solo se non si è già verifcata una collisione verticale e se la velocità orizzontale V è <> 0. B+V è l’indirizzo dell’attributo che identifica la “cella” a destra o sinistra della pallina (in base al segno di V), per cui se PEEK B+V <> 0, significa che la cella è occupata e quindi si è verificata una collisione, per cui si inverte la direzione orizzontale (segno di V). Se la nuova posizione della pallina sarà occupata da un altro mattoncino (Z), allora si invertirà anche la direzione verticale (segno di W):

8 IF H*V*PEEK(B+V) THEN LET T=B+2*V:LET V=-V:GO SUB 40:IF Z THEN LET W=-W

Controllo collisione diagonale e chiamata alla routine di collisione. U = B+W*32+V è l’indirizzo dell’attributo che identifica la “cella” in diagonale (in base al segno di V e W). Gestito in modo analogo alla collisione verticale:

9 LET U=U+V:IF H*PEEK U THEN LET T=U:LET W=-W:GO SUB 40:IF Z THEN LET V=-V

Riposizionamento e aggiornamento palla:

10 POKE B,0:LET X=X+V:LET Y=Y+W:LET B=B+V+32*W:POKE B,56

Controllo se palla al fondo dello schermo e decremento vite / game over:

11 IF Y=21 THEN LET L=L-1:BORDER 2:BEEP .5,-23:IF L=0 THEN PAUSE 0:GO TO 1
12 IF Y=21 THEN PAUSE 25:BORDER 1:GO SUB 42:POKE B,0:GO SUB 32

20 GO TO 2

Routine di inizializzazione:
Posizionamento iniziale barra, posizionamento e disegno 7 file di mattoncini:

30 LET A=23214:LET R=0:FOR I=1 TO 7:FOR J=22562+32*I TO 22589+32*I
31 POKE J,I*8:LET R=R+1:NEXT J:NEXT I:LET V=1:LET W=1:GO SUB 42

Routine di riposizionamento e disegno palla in base a posizione barra:

32 LET B=A-31:LET X=A-23200+1:LET Y=20:POKE B, 56

Disegno barra:

33 FOR I=0 TO 2:POKE A+I,40:NEXT I:RETURN

Routine di gestione delle collisioni palla/mattoncini.
Gestione collisione: cancella i mattoncini colpiti, decrementa il numero di mattoncini rimanenti R.
La collisione tra palla e un mattoncino fa sì che siano cancellati anche anche gli eventuali mattoncini adiacenti, per un massimo di 3 mattocini coinvolti. R è decrementato per ogni mattoncino cancellato, infatti -SGN(PEEK I) assumerà valore -1 se l’indirizzo I è occupato da un mattoncino, 0 altrimenti:

40 FOR I=T-1 TO T+1:LET R=R-SGN(PEEK I):POKE I,0:NEXT I:BEEP .02,23

Incrementa punteggio, setta flag collisione, determina se ulteriore collisione con la cella verso cui la palla si sta muovendo:

41 LET S=S+1:LET H=0:LET Z=PEEK(B+W*32+V)

Routine di aggiornamento punteggio e vite; stampa sullo stream #1 in modo da non interferire con l’area di gioco:

42 PRINT #1;AT 1,0; "Score: ";S,"Lives: ";L-1:RETURN

Video registrato durante lo sviluppo di BreakIn (versione non definitiva)

Codice completo del programma

1 BORDER 1:INK 0:PAPER 0:CLS:LET L=6:LET S=0:GO SUB 42:GO SUB 30
2 LET A$=INKEY$:LET H=1:IF R=0 THEN BORDER 4:PAUSE 0:GO TO 1
3 IF A$="z" AND A>23200 THEN LET A=A-1:POKE A,40:POKE A+3,0:GO TO 5
4 IF A$="x" AND A<23229 THEN POKE A+3,40:POKE A,0:LET A=A+1
5 LET D=B-A+31:IF ABS D<3 THEN LET W=-1:LET V=SGN D:BEEP .03,12
6 LET W=(Y=0)+(Y<>0)*W:IF X=0 OR X=31 THEN LET V=-V:GO TO 10
7 LET U=B+W*32:IF PEEK U THEN LET T=U:LET W=-W:GO SUB 40:IF Z THEN LET V=-V
8 IF H*V*PEEK(B+V) THEN LET T=B+2*V:LET V=-V:GO SUB 40:IF Z THEN LET W=-W
9 LET U=U+V:IF H*PEEK U THEN LET T=U:LET W=-W:GO SUB 40:IF Z THEN LET V=-V
10 POKE B,0:LET X=X+V:LET Y=Y+W:LET B=B+V+32*W:POKE B,56
11 IF Y=21 THEN LET L=L-1:BORDER 2:BEEP .5,-23:IF L=0 THEN PAUSE 0:GO TO 1
12 IF Y=21 THEN PAUSE 25:BORDER 1:GO SUB 42:POKE B,0:GO SUB 32
20 GO TO 2: REM *** BreakIn 20-Liner Breakout by Marco Varesio 2019 ***
30 LET A=23214:LET R=0:FOR I=1 TO 7:FOR J=22562+32*I TO 22589+32*I
31 POKE J,I*8:LET R=R+1:NEXT J:NEXT I:LET V=1:LET W=1:GO SUB 42
32 LET B=A-31:LET X=A-23200+1:LET Y=20:POKE B, 56
33 FOR I=0 TO 2:POKE A+I,40:NEXT I:RETURN
40 FOR I=T-1 TO T+1:LET R=R-SGN(PEEK I):POKE I,0:NEXT I:BEEP .02,23
41 LET S=S+1:LET H=0:LET Z=PEEK(B+W*32+V)
42 PRINT #1;AT 1,0; "Score: ";S,"Lives: ";L-1:RETURN

Buon divertimento!

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.