Gioco del 15 in C++

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 4x4 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 alal 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, 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 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.

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;
}