MaN1cPuzzle (20-Liner BASIC Puzzle per ZX Spectrum)

Read in English

MaN1cPuzzle è un rompicapo per ZX Spectrum, realizzato in 20 righe di BASIC. Sostanzialmente, si tratta di una versione configurabile del Gioco del 15 (conosciuto anche come Magic 15 o 15 Puzzle), in cui l’area di gioco è costituita da M righe * N colonne, con 3<M<4 e 3<N<6. Ho sviluppato MaN1cPuzzle in occasione del Puzzle Challenge, organizzato da RetroProgramming Italia 8 bit e oltre – Associazione Culturale nel mese di Giugno 2019.

Rompicapo in versione standard (15 tessere) risolto!

Partendo da una situazione iniziale, in cui le M*N-1 tessere numerate sono disposte in ordine crescente da destra verso sinistra e dall’alto verso il basso, con lo spazio vuoto in prossimità dell’ultima cella in basso a destra, il programma rimescola le tessere in modo (pseudo)casuale. Lo scopo del gioco è quindi riordinare le tessere in modo analogo alla configurazione iniziale, utilizzando il minor numero di mosse possibile. Le tessere possono scorrere orizzontalmente o verticalmente, sfruttando la presenza dello spazio vuoto.

Puoi giocare a MaN1cPuzzle sul tuo browser oppure scaricarlo per utilizzarlo su un vero ZX Spectrum o un emulatore dalla pagina itch.io del progetto:

Funzionamento del gioco

All’avvio, il programma disegna la griglia di gioco con le tessere disposte nella configurazione iniziale, dopodiché procede al rimescolamento delle stesse; nella parte inferiore dello schermo è visualizzata la percentuale di completamento del mescolamento (“Shuffling %”).

Griglia 3×3: rimescolamento in corso…

A mescolamento completato, il giocatore può iniziare a spostare le tessere, avvalendosi dei tasti freccia o cursore. NOTA: nelle versioni di ZX Spectrum senza tasti cursore o di emulatori che non li supportano, è possibile ottenere lo stesso effetto mediante pressione simultanea del tasto CAPS SHIFT (solitamente emulato con lo SHIFT) e di un tasto numerico da 5 a 8, come illustrato nella seguente tabella:

Tasto frecciaCombinazione corrispondente
CAPS SHIFT + 5
CAPS SHIFT + 6
CAPS SHIFT + 7
CAPS SHIFT + 8

Il programma tiene conto delle mosse del giocatore (“Moves”). Alla risoluzione del rompicapo, mostrerà il messaggio “SOLVED 🙂” e sarà possibile cominciare una nuova partita premendo un tasto qualsiasi.

Tweaking e parametrizzazione

Il comportamento del programma può essere modificato in base all’inizializzazione di alcune variabili, in particolare:

  • numero di righe della griglia di gioco: assegnare un valore differente a M, compreso tra 3 e 4, alla linea 1 (esempio: LET M=4);
  • numero di colonne della griglia di gioco: assegnare un valore differente a N, compreso tra 3 e 6, alla linea 1 (esempio: LET N=4);
  • numero di spostamenti delle tessere effettuati durante il mescolamento iniziale: assegnare un valore differente a S alla linea 11 (esempio: LET S=100). Un valore minore di S comporterà un mescolamento iniziale ridotto e quindi tendenzialmente una modalità di gioco più semplice e adatta per gli utenti alle prime armi col rompicapo.
Griglia 4×6: sembra pittosto difficile!

E’ possibile accedere al codice sorgente del programma in ogni momento, interrompendo l’esecuzione con la pressione di BREAK o CAPS SHIFT + SPACE. Dopo le modifiche, il programma può essere avviato normalmente col comando RUN.

Descrizione dettagliata del programma

Variabili

Mdefinisce il numero di righe della griglia di gioco
Ndefinisce il numero di colonne della griglia di gioco
Fmatrice M*N che memorizza lo stato della griglia di gioco
I,Jiteratori per esplorare la griglia di gioco
X,Yposizione (riga, colonna) dello spazio vuoto nella griglia di gioco; inizialmente utilizzati come iteratori di supporto per il disegno della griglia
Vnumero corrente di spostamenti delle tessere effettuati
Sdefinisce il numero di spostamenti effettuati nella fase di mescolamento
Kdirezione dello spostamento tessera (8: sinistra; 9: destra; 10: basso; 11: alto)
Tvariabile temporanea, utilizzata per determinare se il rompicapo è risolto e inizialmente per disegnare le griglie a scacchiera, con i colori alternati

Descrizione del codice

Inizializzazioni preliminari e disegno della griglia di gioco

Inizializzazione della dimensione della griglia di gioco F (M righe * N colonne) e dei colori. Definizione della funzione e(x) che dato un numero x, restituisce 1 se x è pari oppure 0 se x è dispari. Questa funzione sarà utilizzata nella lina seguente per colorare le celle in modo alternato, a scacchiera.

1 LET M=4:LET N=4:DIM F(M,N):BORDER 0:INK 0:PAPER 0:DEF FN e(x)=INT(x/2)=x/2

Cancellazione schermo e disegno della griglia di gioco di M*N celle. Ogni cella è un quadrato di 5*5 caratteri, colorata di azzurro o verde in base alla somma degli indici (I:riga; J:colonna), che può essere pari o dispari. La colorazione è effettuata impostando mediante POKE il valore della carta (sfondo) nel byte corrispondente all’attributo di ciascuno dei 5*5 caratteri che costituiscono la cella. L’indirizzo di memoria 22528 corrisponde agli attributi del primo carattere della prima linea; 22529 al secondo carattere della prima linea e così via per tutti i 32 caratteri della prima linea; l’indirizzo 22560 corrisponde agli attributi del primo carattere della seconda linea, etc. Il colore della carta (4:verde; 5:azzurro) è specificato nei bit 5,4,3 del byte che rappresenta l’attributo del carattere, per cui considerando lo shift di 3 bit a sinistra, occorrerà impostare l’attributo a 40 per l’azzurro e 32 per il verde. La variabile T è valorizzata al risultato di e(I+J), quindi 1 se I+J è pari, 0 altrimenti.

2 CLS:FOR I=0 TO M-1:FOR J=0 TO N-1:LET T=FN e((I+J)):FOR X=I*5 TO I*5+4
3 FOR Y=J*5 TO J*5+4:POKE 22528+X*32+Y,32+T*8:NEXT Y:NEXT X:NEXT J:NEXT I

Inizio partita: inizializzazione, mescolamento e stampa dei valori della griglia

Ciclo di inizializzazione dei valori della griglia di gioco F (M righe * N colonne) alla configurazione iniziale (tessere in ordine crescente da destra a sinistra, dall’alto al basso); assegna 0 alla posizione corrispondente al vuoto:

10 FOR I=1 TO M:FOR J=1 TO N:LET F(I,J)=(I-1)*N+J:NEXT J:NEXT I:LET F(M,N)=0

Inizializzazione del numero di movimenti casuali S da effettuare in fase di mescolamento; inizializzazione di X e Y a riga e colonna della posizione vuota; inizializzazione del contatore di mosse V a 0; richiama la routine per disegnare a video la situazione attuale delle tessere e stampa il messaggio di rimescolamento in corso:

11 LET S=100:LET X=M:LET Y=N:LET V=0:GO SUB 30:PRINT #1;AT 0,0;"Shuffling% "

Ciclo di rimescolamento: per V che va da 0 a S, determina casualmente la direzione K del prossimo spostamento (8: sinistra; 9: destra; 10: basso; 11: alto); richiama la routine di spostamento tessera e aggiorna la percentuale di completamento del mescolamento. GO TO 13-(V<S) è una “scorciatoia” per IF V<S THEN GO TO 12 ELSE GO TO 13:

12 LET K=8+INT(RND*4):GO SUB 40:PRINT #1;AT 0,11;INT(V*100/S): GO TO 13-(V<S)

Rimescolamento completato: reimposta il numero di mose a 0, stampa i valori della griglia rimescolata e stampa il messaggio con nome e autore:

13 LET V=0:GO SUB 30:PRINT #1;AT 1,0;"MaN1cPuzzle by Marco's Retrobits"

Loop principale

Ciclo principale del gioco: legge il tasto correntemente premuto, che deve corrispondere a uno spostamento valido (8: sinistra; 9: destra; 10: basso; 11: alto) e inizializza il generatore pseudocasuale:

14 LET K=CODE(INKEY$):RANDOMIZE:IF K<8 OR K>11 THEN GO TO 14

Stampa il messaggio di movimento tessera in corso; richiama la routine di spostamento tessera, dopodiché richiama la routine di stampa a video della situazione attuale. Inizializza la variabile T, che sarà utilizzata per determinare se il gioco è stato risolto, a 0:

15 PRINT #1;AT 0,0;"Moving...";TAB 31:GO SUB 40:GO SUB 30: LET T=0

Determina se il gioco è stato risolto. Si cicla per ogni cella su tutte le righe e colonne della griglia di gioco; se la cella contiene il valore della tessera attesa, T (che inizialmente vale 0) è incrementato di 1. Alla fine del ciclo, se tutte le tessere sono al loro posto, T avrà valore N*M-1. Questa condizione, unitamente al fatto che la cella vuota deve trovarsi in posizione M,N, determina la risoluzione del rompicapo. Se il rompicapo è risolto, si attende la pressione di un tasto (“PAUSE 0”) e si comincia una nuova partita:

16 FOR I=1 TO M:FOR J=1 TO N:LET T=T+(F(I,J)=(I-1)*N+J):NEXT J:NEXT I
17 IF F(M,N)=0 AND T=M*N-1 THEN PRINT #1;AT 0,0;"SOLVED :) ":PAUSE 0:GO TO 10

Se il rompicapo non è risolto, torna all’inizio del loop alla linea 14 per la prossima mossa:

18 GO TO 14

Routine di stampa a video della situazione attuale della griglia di gioco

Cicla su tutti i valori della griglia F e aggiorna a video il numero di ciascuna tessera nella cella correntemente occupata. La colorazione dello sfondo azzurro o verde è gestita in modo analogo a quanto già descritto. La condizione IF F(I,J) THEN PRINT F(I,J) fa sì che il valore 0, che corrisponde alla posizione vuota, non sia stampato. Dopo aver stampato tutti i numeri delle tessere, si stampa il conteggio degli spostamenti effettuati e si emette un segnale sonoro:

30 FOR I=1 TO M:FOR J=1 TO N:PAPER 4+FN e((I+J)):PRINT AT (I-1)*5+2,(J-1)*5+2;
31 PRINT " ";AT (I-1)*5+2,(J-1)*5+2;:IF F(I,J) THEN PRINT F(I,J)
32 NEXT J:NEXT I:PRINT #1;AT 0,0;"Moves: ";V;TAB 31:BEEP .02,23:RETURN

Routine di spostamento tessera

Sia nel caso in cui la routine sia chiamata per un movimento casuale in fase di mescolamento che nel caso in cui sia chiamata per una mossa del giocatore, a K sarà stata assegnata la direzione (8: sinistra; 9: destra; 10: basso; 11: alto) dello spostamento da effettuare. Il movimento di una tessera a sinistra è possibile solo per la tessera che eventualmente si trova alla destra dello spazio vuoto e farà sì che tale tessera sia spostata nello spazio vuoto, lasciando così la posizione originale della tessera vuota. Il movimento a sinistra non è possibile se non ci sono tessere a destra della posizione vuota. Analogamente, lo spostamento a destra è possibile solo se c’è una tessera a sinistra della posizione vuota; lo spostamento in alto solo se c’è una tessera immediatamente sotto allo spazio vuoto e lo spostamento in basso solo se c’è una tessera immediatamente sopra lo spazio vuoto… beh, è stato più facile implementarlo che spiegarlo 😊
Se la mossa è possibile, viene attuata e il contatore di mosse V è incrementato di 1:

40 IF K=8 AND Y<N THEN LET F(X,Y)=F(X,Y+1):LET F(X,Y+1)=0:LET Y=Y+1:LET V=V+1
41 IF K=9 AND Y>1 THEN LET F(X,Y)=F(X,Y-1):LET F(X,Y-1)=0:LET Y=Y-1:LET V=V+1
42 IF K=10 AND X>1 THEN LET F(X,Y)=F(X-1,Y):LET F(X-1,Y)=0:LET X=X-1:LET V=V+1
43 IF K=11 AND X<M THEN LET F(X,Y)=F(X+1,Y):LET F(X+1,Y)=0:LET X=X+1:LET V=V+1

44 RETURN

Codice completo

1 LET M=4:LET N=4:DIM F(M,N):BORDER 0:INK 0:PAPER 0:DEF FN e(x)=INT(x/2)=x/2
2 CLS:FOR I=0 TO M-1:FOR J=0 TO N-1:LET T=FN e((I+J)):FOR X=I*5 TO I*5+4
3 FOR Y=J*5 TO J*5+4:POKE 22528+X*32+Y,32+T*8:NEXT Y:NEXT X:NEXT J:NEXT I
10 FOR I=1 TO M:FOR J=1 TO N:LET F(I,J)=(I-1)*N+J:NEXT J:NEXT I:LET F(M,N)=0
11 LET S=100:LET X=M:LET Y=N:LET V=0:GO SUB 30:PRINT #1;AT 0,0;"Shuffling% "
12 LET K=8+INT(RND*4):GO SUB 40:PRINT #1;AT 0,11;INT(V*100/S): GO TO 13-(V<S)
13 LET V=0:GO SUB 30:PRINT #1;AT 1,0;"MaN1cPuzzle by Marco's Retrobits"
14 LET K=CODE(INKEY$):RANDOMIZE:IF K<8 OR K>11 THEN GO TO 14
15 PRINT #1;AT 0,0;"Moving...";TAB 31:GO SUB 40:GO SUB 30: LET T=0
16 FOR I=1 TO M:FOR J=1 TO N:LET T=T+(F(I,J)=(I-1)*N+J):NEXT J:NEXT I
17 IF F(M,N)=0 AND T=M*N-1 THEN PRINT #1;AT 0,0;"SOLVED :) ":PAUSE 0:GO TO 10
18 GO TO 14:REM ** Tweak frame size: 2<M<4 2<N<6; shuffle moves: LET S=XXX **
30 FOR I=1 TO M:FOR J=1 TO N:PAPER 4+FN e((I+J)):PRINT AT (I-1)*5+2,(J-1)*5+2;
31 PRINT "  ";AT (I-1)*5+2,(J-1)*5+2;:IF F(I,J) THEN PRINT F(I,J)
32 NEXT J:NEXT I:PRINT #1;AT 0,0;"Moves:    ";V;TAB 31:BEEP .02,23:RETURN
40 IF K=8 AND Y<N THEN LET F(X,Y)=F(X,Y+1):LET F(X,Y+1)=0:LET Y=Y+1:LET V=V+1
41 IF K=9 AND Y>1 THEN LET F(X,Y)=F(X,Y-1):LET F(X,Y-1)=0:LET Y=Y-1:LET V=V+1
42 IF K=10 AND X>1 THEN LET F(X,Y)=F(X-1,Y):LET F(X-1,Y)=0:LET X=X-1:LET V=V+1
43 IF K=11 AND X<M THEN LET F(X,Y)=F(X+1,Y):LET F(X+1,Y)=0:LET X=X+1:LET V=V+1
44 RETURN:REM **** 20-Liner BASIC M*N-1 Puzzle game by Marco Varesio 2019 ****

Link utili

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.