Come funziona Campo Minato

Campo minato e' uno dei piu' semplici ed interessanti che ci sia a mio parere.
Ci vogliono circa 10 minuti per realizzarne un clone da console, si puo' imparare molto da un gioco cosi' banale.

Fino ad ora abbiamo visto solo esempi grafici e interattivi che sfruttavano le librerie SDL per l'input e la grafica, ora facciamo un passo indietro.

Prima di tutto, perche' voler scrivere l'ennesimo clone di campo minato?
Vi do 3 buone ragioni:
  1. Molti giochi 2D usano mappe basate sulle matrici, questo puo' essere un ottimo esempio per farsi le ossa ed avere un accenno di mappe tile based (anche se qui ancora non le si usera').
  2. Il gioco sfrutta un algoritmo detto floodfill, secondo me e' un bene conoscerlo e saperlo utilizzare a dovere, puo' sempre tornare utile.
  3. Male non vi fa, credo che sia un motivo piu' che valido...
La nostra grafica sara' del semplice testo in console, e l'interazione con l'utente normali richieste di valori.
Si potra' notare una similitudine con quello che e' stato presentato negli articoli precendenti, ad esempio anche qui ci sara' il ciclo principale di gioco ed un buffer grafico (seppur testuale).
La parte piu' importante di tutto, sara' comunque la gestione del nostro campo di gioco e i vari algoritmi.

Iniziamo definendo quali header includere nel nostro programma, definiamo qualche costante e qualche variabile come sempre.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define SIZE 8 //Dimensione del campo di gioco per lato
#define MAX_MINES 10 //Numero massimo di mine

int map[SIZE][SIZE], i, j, x, y, mines, minefree;
char playfield[SIZE][SIZE];
Tutto dovrebbe essere abbastanza chiaro, spendo solo due parole riguardo le variabili attualmente definite.
Le due matrici, map e playfield, tengono nota dello stato del campo di gioco, la prima della logica interna, la seconda come semplice trasposizione grafica per il giocatore (semplice buffer grafico volendo).
Le variabili mines e minefree, tengono conto rispettivamente del numero di mine presenti nel campo e del numero di celle liberate.

Il gioco e' effettivamente quello che da a vedere, una grossa matrice, dove in ogni cella vi e' salvato un valore.
Zero in caso di assenza di mine, quindi cella vuota, da 1 a 4 in caso di mine nei paraggi e 9 in caso di mina presente come si puo' vedere nell'immagine.

Il primo passo effettuato dal programma e' quello di valorizzare l'intera matrice con soli 0, successivamente si piazzano in maniera casuale 10 mine.
Si incrementa di 1 il valore delle celle attorno alle mine, poi si chiede al giocatore di dare delle coordinate, partiamo comunque a spiegare dall'azione di numerazione delle celle.
int increase()
{
for (i=0; i < SIZE; i++)
for (j=0; j < SIZE; j++)
if (map[i][j]==9)
{
//Lato sinistro
if((j-1)>=0)
{
//Su sinistra
if (map[i-1][j-1]!=9) map[i-1][j-1]++;
//Sinistra
if (map[i][j-1]!=9) map[i][j-1]++;
//Giu' sinistra
if (map[i+1][j-1]!=9) map[i+1][j-1]++;
}

//Lato destro
if((j+1) < SIZE )
{
//Su destra
if (map[i-1][j+1]!=9)
map[i-1][j+1]++;
//destra
if (map[i][j+1]!=9)
map[i][j+1]++;
//giu' destra
if (map[i+1][j+1]!=9)
map[i+1][j+1]++;
}

//Lati superiori e inferiori
//Su
if (map[i-1][j]!=9)
map[i-1][j]++;//down
if (map[i+1][j]!=9)
map[i+1][j]++;
}
}
La funzione semplicemente, controlla le 8 celle che circondano quella interessata, sempre che queste siano nei limiti della matrice.

Al giocatore ovviamente, si mostra del testo alternativo a questi valori, per comodita', salvato in una matrice apposita di tipo char, questa matrice la si puo' intendere come un buffer grafico volendo dove ad ogni pixel corrisponde un carattere alfanumerico.
 // Disegnamo il campo di gioco
void draw()
{
printf(" 0 1 2 3 4 5 6 7\n +---+---+---+---+---+---+");
for(i=0; i < SIZE; i++)
{
printf("\n%d ", i);
for(j=0; j < SIZE; j++)
printf(" %c ", playfield[i][j]);
}
printf("\n +---+---+---+---+---+---+\n");
}
Qui abbiamo due cicli for annidati, il primo si occupa di leggere e scrivere le righe, il secondo di leggere e scrivere le colonne, non credo ci sia molto da dire al riguardo.

Proseguendo, il programma legge una coppia di coordinate dall'utente, controlla alla relativa posizione in matrice il valore contenuto ed esegue le operazioni di routine.
 // Chiediamo le coordinate al giocatore
int coord()
{
printf("Insert the X ordinate: ");
scanf("%d", &x);
printf("Insert the Y ordinate: ");
scanf("%d", &y);

if(x>=SIZE || y>=SIZE)
{
printf("Error! Please insert coordinates between 0 and %d\n", SIZE);
coord();
}
}
La routine, consiste nel mostrare tutte le celle libere continue fino a che queste non siano maggiori di 0, l'algoritmo qui usato e' il gia' citato floodfill, "a macchia d'olio".
// Controlliamo cosa contiene una cella
int check(int xcoord, int ycoord)
{
if (xcoord >= 0 && ycoord >= 0 && xcoord < SIZE && ycoord < SIZE)
{
if(map[xcoord][ycoord] == 0)
{
playfield[xcoord][ycoord]='_';
map[xcoord][ycoord] = 10;
minefree--;
check(xcoord+1, ycoord);
check(xcoord, ycoord+1);
check(xcoord-1, ycoord);
check(xcoord, ycoord-1);
}

if(map[xcoord][ycoord] == 1)
{
map[xcoord][ycoord] = 11;
playfield[xcoord][ycoord]='1';
minefree--;
}

if(map[xcoord][ycoord] == 2)
{
map[xcoord][ycoord] = 12;
playfield[xcoord][ycoord]='2';
minefree--;
}

if(map[xcoord][ycoord] == 3)
{
map[xcoord][ycoord] = 13;
playfield[xcoord][ycoord]='3';
minefree--;
}

if(map[xcoord][ycoord] == 4)
{
playfield[xcoord][ycoord]='4';
map[xcoord][ycoord] = 14;
minefree--;
}

if(map[xcoord][ycoord] == 9)
{
playfield[xcoord][ycoord]='*';
draw();
printf("\nGame Over! :(\n");
exit(0);
}

if(minefree==0)
{
draw();
printf("\nYou Win! :)\n");
exit(0);
}
}
return 1;
}
Questo algoritmo anche se non molto complesso, necessita due parole di spiegazione.
Alla funzione vengono passate due coordinate, si controlla che siano contenute nei limiti della matrice in quanto la funzione viene richiamata ricorsivamente, ovvero da se stessa.

A seconda del valore presente nella matrice alla coordinate scelte dal giocatore, si assegna un valore testuale alla matrice di supporto playfield.
Nel caso di valore Zero, ovvero cella vuota, si richiama la funzione check() sommando di 1 le coordinate una volta ciascuna, proseguendo in lungo e il largo la ricerca.

Dato che le coordinate sono tipi semplici, la funzione ad ogni nuova chiamata di se stessa ricevera' solo una copia del valore, una volta terminata la sua esecuzione parallela, tornando al punto in cui e' stata richiamata si riavranno le coordinate di partenza date dal giocatore.
Il giro termina quando si trova un valore diverso da Zero, nel frattempo si calcola anche il numero di celle liberate e si controlla l'eventuale vittoria del giocatore nel caso tutte le celle libere siano state scoperte.

Completiamo il tutto definendo la nostra funzione main()
int main(int argv, char argc[])
{
for(i=0; i < SIZE; i++)
for(j=0; j < SIZE; j++)
{
map[i][j] = 0;
playfield[i][j]='#';
}

// Insemianiamo il generatore di numeri random
srand(time(NULL));

i=0;
// Controlliamo che ci siano 8 mine per per partita
while (mines!=MAX_MINES)
{
if(map[rand() % SIZE + 1][rand() % SIZE + 1]!=9)
{
map[rand() % SIZE + 1][rand() % SIZE + 1]=9;
mines++;
}
}

increase();

//Contiamo il numero di spazi liberi
for(i=0; i < SIZE; i++)
for(j=0; j < SIZE; j++)
if(map[i][j] !=9)
minefree++;

printf("IndieFanMines!\n");
scanf("%c", NULL);

//Main game loop
while(1)
{
draw();
coord();
check(x,y);
}

return 0;
}
La prima operazione e' quella di azzerare tutti i valori della matrice, quella successiva di assicurarsi che ci siano circa 8 mine nel campo di gioco, tutte piazzate in maniera casuale.
Si effettua l'incremento dei valori come gia' discusso prima e si entra nel classico main game loop.

Come si puo' vedere, questo gioco mantiene sempre la stessa struttura gia' vista negli esempi degli altri articoli, un sistema spartano di input, un primordiale buffer per la grafica ed un classico main game loop.
Questo semplice esempio per quanto stupido e banale, aiuta a comprendere la base su cui i la maggioranza dei giochi 2D si regge, le matrici sono un elemento fondamentale per la realizzazione dei livelli in molti giochi 2D.
Si pensi a Zelda o Mario, la loro struttura e' data tasselli di 16x16 pixel solitamente, la cui posizione e' dettata da mappe alfanumeriche analoghe a questa, dove ogni valore corrisponde ad un tiles specifico.
Si avra' modo di affrontare il discorso a tempo debito, per chi volesse puo' trovare questo semplice esempio nella sezione Console Series del Pix3lworkshop.

Nessun commento :

Posta un commento

Related Posts Plugin for WordPress, Blogger...