Quante volte abbiamo tentanto il gioco del 15, la tavoletta con 16 caselline movibili con 15 numeri d 1 a 15 ed uno spazietto vuoto, il tutto da mettere in ordine consecutivo. Vediamo un gioco simile realizzato in C++, con vettori, funzioni e diverse finezza del nostro amato C++
Partiamo dalla struttura dei dati. Per realizzare la simil tavoletta, ci occorre una matrice 4×4 di interi. In questa matrice dobbiamo inserire i numeri da 1 a 15, mentre per simulare la casellina vuota che permette gli spostamenti, andiamo ad inserirci lo 0. Quindi in sostanza, dobbiamo staccare numeri casuali da 0 a 15, estremi inclusi. Per il resto cerchiamo passo passo di capire le funzioni che ci occorrono e come ci siamo arrivati.
Le prime due funzioni che ci occorrono sono la classica funzione per inizializzare le strutture dati e stamparle. In questo esercizio la cartella la supponiamo una matrice quadrata n x n con n=4. Il Assumiamo come parametro il passaggio (per riferimento di default) della matrice per la cartella, la dimensione la consideriamo completa, con N*N completamente utilizzati. L’idea che proponiamo è di creare cartelle con numeri casuali da 0 a 15, mentre con lo 0 zero simuliamo la casella vuota utilizzata per fare gli scambi. Per farlo, dobbiamo immaginare di estrarre un numero casuale e controllare che non sia già stato estratto e posizionato sulla nostra matrice. In pratica: scorriamo ogni posizione della matrice, stacchiamo un numero casuale, controlliamo se non c’era già e lo posizioniamo, altrimenti ne estraiamo un altro. Per verificare che il numero ci sia già, ricorriamo ad uno stratagemma: una variabile booleana, che potrebbe essere anche un semplice intero che vale 1 o 0, che viene messa in uno stato diverso da 0 se trova almeno un doppione. Se alla fine del controllo, non c’è doppione e la variabile ok è ancora 0, il numero è nuovo e posso posizionarlo.
void creaCartella(int _cartella[N][N])
{
int ok;
int numero =0 ;
for (int k=0; k < N; k++)
for (int z=0; z < N; z++)
{
ok = 1;
while (ok == 1)
{
numero = rand() % 16;
ok = 0;
for (int i=0; i < N; i++)
{
for (int j=0; j < N; j++)
{
if (_cartella[i][j] == numero)
ok = 1; //ne ho trovato almeno uno uguale
}
}
}
_cartella[k][z] = numero;
}
}
La funzione stampaCartella non ha bisogno di molti commenti. Al lettore meno esperto facciamo notare lo stratagemma cout << endl; tra un ciclo e l’altro per andare a capo ogni fine riga, mentre la dicitura cout<< setw(2) serve ad impostare uno spazio fisso di due caratteri per i numeri del gioco, in modo da visualizzare in modo adeguato la matrice con la sua forma pulita ed allineata.
void stampaCartella(int _cartella[N][N])
{
for (int i=0; i < N; i++)
{
for (int j=0; j < N; j ++)
{
cout << setw(2) ;
cout << _cartella[i][j] << " ";//ne ho trovato almeno uno uguale
}
cout << endl;
}
}
Finalmente possiamo addentrarci nel gioco. Abbiamo deciso di far inserire all’utente un numero da spostare. Inserito l’input, la funzione trovaNumero scorre la matrice e individua riga e colonna che ritorna sotto forma di puntatori al programma principale. La stessa funzione, per ogni spostamento, viene chiamata anche per individuare la casella vuota, o meglio, che qui simuliamo con lo zero. Questa è la parte più ostica forse per gli studenti poco avvezzi ai puntatori.
void trovaNumero(int _cartella[N][N], int _numero, int *x, int *y)
{
for (int i= 0; i< N; i++)
{
for (int j=0; j< N; j++)
{
if (_cartella[i][j] == _numero)
{
*x= i;
*y= j;
}
}
}
}
Se lo scambio è possibile, lo controlliamo con controllaScambio che semplicemente vede se il numero scelto dall’utente è in prossimità dello zero per effettuare lo scambio. Sono una serie di if che giocano sulle coordinate dei due numeri che devono essere vicine in senso orizzontale o verticale ma non in diagonale.
scambia è invece una funzioncina di utilità per scambiare il contenuto di due celle di una matrice. Non ha controlli. Si avvale, come sempre accade nello scambio di variabili, di una variabile di appoggio.
L’ultima funzione vincente occorre per verificare semplicemente se i numeri sono in ordine da 1 a 15. Per realizzarla basta scorrere col doppio ciclo ed un contatore incrementale. Se si trova almeno una difformità tra la casella e il contatore, non sono in ordine ancora e tocca richiedere un’altra mossa.
Listato completo
Il codice completo:
#include <iostream>
#include <time.h>
#include <stdlib.h>
#include <iomanip> // std::setw
using namespace std;
int const N = 4;
void creaCartella(int [N][N]);
void stampaCartella(int [N][N]);
void trovaNumero(int [N][N], int , int *, int *);
void scambia(int [N][N], int , int , int , int );
bool controllaScambio( int , int , int , int );
bool vincente(int [N][N]);
int main()
{
srand(time(NULL));
bool vinto = false;
char resa;
int cartella [N][N];
int numero_da_scambiare;
int x_da_scambiare, y_da_scambiare;
int x_zero, y_zero;
creaCartella(cartella);
stampaCartella(cartella);
while(!vinto)
{
//cominciamo gli scambi
cout << "-----------------------------" << endl;
cout << "Che numero vuoi scambiare? ";
cin >> numero_da_scambiare;
trovaNumero(cartella,numero_da_scambiare,&x_da_scambiare,&y_da_scambiare );
//cout <<"Coordinate "<<x_da_scambiare << " "<< y_da_scambiare <<endl;
trovaNumero(cartella,0,&x_zero,&y_zero );
//cout <<"Coordinate "<< x_zero << " " <<y_zero <<endl;
if (controllaScambio( x_da_scambiare,y_da_scambiare,x_zero,y_zero))
scambia(cartella, x_da_scambiare,y_da_scambiare,x_zero,y_zero);
else
cout << "Scambio non valido "<< endl;
stampaCartella(cartella);
cout << "Ti arrendi? Premi q altrimenti un tasto qualsiasi ";
cin >> resa;
if (resa == 'q')
{
vinto = true;
}
if (vincente(cartella))
{
cout << "Hai vinto!" << endl;
vinto = true;
}
}
return 0;
}
void creaCartella(int _cartella[N][N])
{
int ok;
int numero =0 ;
for (int k=0; k < N; k++)
for (int z=0; z < N; z++)
{
ok = 1;
while (ok == 1)
{
numero = rand() % 16;
ok = 0;
for (int i=0; i < N; i++)
{
for (int j=0; j < N; j++)
{
if (_cartella[i][j] == numero)
ok = 1; //ne ho trovato almeno uno uguale
}
}
}
_cartella[k][z] = numero;
}
}
void stampaCartella(int _cartella[N][N])
{
for (int i=0; i < N; i++)
{
for (int j=0; j < N; j ++)
{
cout << setw(2) ;
cout << _cartella[i][j] << " ";//ne ho trovato almeno uno uguale
}
cout << endl;
}
}
void trovaNumero(int _cartella[N][N], int _numero, int *x, int *y)
{
for (int i= 0; i< N; i++)
{
for (int j=0; j< N; j++)
{
if (_cartella[i][j] == _numero)
{
*x= i;
*y= j;
}
}
}
}
void scambia(int _cartella[N][N], int x, int y, int i, int j)
{
int tmp;
tmp = _cartella[x][y];
_cartella[x][y] = _cartella[i][j];
_cartella[i][j] = tmp;
}
bool controllaScambio( int x, int y, int i, int j)
{
//controlla sinistra
if (y < j && j-y != 1 && x==i)
return false;
//controllo destra
if (y > j && y-j != 1 && x==i)
return false;
//controllo sopra
if (x > i && x - i != 1 && y==j)
return false;
//controllo sotto
if (x < i && i-x != 1 && y==j)
return false;
//digonale
if (x != i && y != j)
return false;
return true;
}
bool vincente(int _cartella[N][N])
{
int contatore = 1;
for (int i =0; i < N; i++ )
for (int j=0; j < N; i++)
{
if (contatore != _cartella[i][j])
return false;
contatore++;
}
return true;
}
Potete trovare il codice completo anche su GitHub => https://github.com/alfredocentinaro/esercizi-cplusplus/tree/main/gioco-del-15
Ultima modifica 11 Maggio 2023