Un semplice gioco di Snake

Lo abbiamo visto ovunque, sui cellulari, in regalo con i detersivi, persino nei decoder per digitale terrestre!

Snake e' un giochino tanto scemo quanto elegante allo stesso tempo, realizzarlo non richiede nemmeno troppo lavoro.
Per chi non lo conoscesse (ne dubito fortemente), si tratta di un semplice giochino in cui un serpente muovendosi a intervalli regolari per lo schermo, deve cercare di crescere raggiungendo delle mele piazzate casualmente nel campo di gioco, senza toccare i bordi dello schermo ne se stesso.
Al giocatore viene semplicemente chiesto di dare la direzione di movimento al serpente, cercando di entrare in sincronia con il suo movimento perpetuo, niente di particolare.

Avendo a mente quanto appena detto, possiamo partire dalla solita base per i nostri giochi ed implementare l'intero meccanismo di gioco.

Per iniziare, definiamo qualche struttura ed includiamo alcuni header con funzioni necessarie al tutto.

#include
#include
#include

#define SCREEN_WIDTH 300
#define SCREEN_HEIGHT 350

#define ALL_DOTS 900
#define DOT_SIZE 10

//Global variables
int repeat, i, quit=0, t2;
SDL_Event event;
SDL_Surface *screen;
SDL_Rect rect;
Uint8 *keystate; // keyboard state

struct _Game
{
int player_score;
} Game;

struct _Snake
{
int x[ALL_DOTS], y[ALL_DOTS], xInc, yInc, lenght;
} Snake;

struct _Apple
{
int x, y;
} Apple;


Il concetto su cui si basa questo esempio, e' quello di tenere traccia in appositi vettori, di tutte le posizioni occupate da ogni singola porzione del nostro serpentello, tutte le posizioni X occupate, e anche quelle Y.
Tutto cio' viene tenuto in conto nella struttura _Snake, che oltre a cio' terra' conto anche delle nuove direzioni da intraprendere e della lunghezza complessiva del serpente.

Impostiamo i valori iniziali del nostro serpente e della prima mela da raccogliere, in una funzione apposita come segue:

int initGame()
{
Snake.lenght = 3;
int z;

for (z = 0; z < Snake.lenght; z++) { Snake.x[z] = 50 - z*DOT_SIZE; Snake.y[z] = 50; } Snake.xInc = DOT_SIZE; placeApple(); }

Decidiamo la lunghezza di partenza del nostro serpente (3 blocchi), successivamente assegniamo ad ognuno di essi le relative posizioni di partenza.

Ciclando dal primo verso l'ultimo, ogni elemento sara' posizionato alla coordinata X specificata, meno la loro lunghezza di blocco, in questo caso 10 pixel.
Le cordinate Y per ogni singolo blocco saranno le stesse, per avere il corpo perfettamente allineato.

Snake.x [0] = 50 - 0 * 10 = 50;
Snake.x [1] = 50 - 1 * 10 = 40;
Snake.x [2] = 50 - 1 * 10 = 30;

Snake.y [0] = 50;
Snake.y [1] = 50;
Snake.y [2] = 50;

Infine impostiamo la direzione iniziale di movimento e piazziamo una mela sullo schermo, con la funzione placeApple().

void placeApple()
{
int r;

r = rand()%30;
Apple.x = r*DOT_SIZE;
r = rand()%30;
Apple.y = r*DOT_SIZE;

int z;
for (z = Snake.lenght; z > 0; z--)
{
if (Apple.x == Snake.x[z] && Apple.y == Snake.y[z])
{
placeApple();
break;
}
}
}

La funzione placeApple(), si occupa di generare una posizione casuale per l'oggetto Apple sullo schermo, e di controllare che tale posizione non sia gia' occupata dal corpo del nostro serpente, in tal caso richiama se stessa per generare una nuova posizione.

Il movimento del serpente e' automatico e perpetuo in una direzione ben specifica, dalla funzione getInput(), diamo al giocatore la possibilita' di decidere in quale direzione far svoltare il serpente e nel frattempo ad intervalli regolari ne incrementiamo la posizione sullo schermo.

int getInput()
{
while(SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT ||
(event.type == SDL_KEYDOWN &&
event.key.keysym.sym == SDLK_ESCAPE)) quit = 1;
}

// Grab a keystate snapshot
keystate = SDL_GetKeyState( NULL );

if (keystate[SDLK_LEFT] && Snake.xInc <=0) { Snake.xInc = -DOT_SIZE; Snake.yInc = 0; } if (keystate[SDLK_RIGHT] && Snake.xInc >=0) {
Snake.xInc = DOT_SIZE;
Snake.yInc = 0;
}

if (keystate[SDLK_UP] && Snake.yInc <=0) { Snake.yInc = -DOT_SIZE; Snake.xInc = 0; } if (keystate[SDLK_DOWN] && Snake.yInc >=0) {
Snake.yInc = DOT_SIZE;
Snake.xInc = 0;
}

if(t2 + 200 < SDL_GetTicks()) //wait SDL_Getticks rich last_tick value { move(); t2 = SDL_GetTicks(); } }

Alla pressione di un tasto direzionale, controlliamo se la direzione che si vuol intraprendere non sia opposta a quella attuale, per fare in modo che il nostro verme possa solo avanzare e mai indietreggiare.
Una volta accertato cio', assegniamo alla variabile di incremento apposita un valore pari alla distanza da percorrere nel campo di gioco.
Questo valore per evitare di avere disguidi grafici, deve essere pari alla larghezza di un singolo pezzo del corpo.

Infine, proprio come abbiamo visto gia' nel tutorial su Tetris, a intervalli regolari di circa 1 secondo, richiamiamo la funzione move(), responsabile dell'effettivo movimento del nostro serpentello sullo schermo.


void move()
{
int z;
for (z = Snake.lenght; z > 0; z--)
{
Snake.x[z] = Snake.x[(z - 1)];
Snake.y[z] = Snake.y[(z - 1)];

if ((z > 4) && (Snake.x[0] == Snake.x[z]) && (Snake.y[0] == Snake.y[z]))
quit=1;
}

Snake.x[0] += Snake.xInc;
Snake.y[0] += Snake.yInc;

if ((Snake.x[0] == Apple.x) && (Snake.y[0] == Apple.y)) {
Snake.lenght++;
placeApple();
}


if (Snake.y[0] + DOT_SIZE > SCREEN_HEIGHT ||
Snake.y[0] < 0 || Snake.x[0] + DOT_SIZE > SCREEN_WIDTH ||
Snake.x[0] < 0) {quit = 1;} }

Nella funzione move(), aggiorniamo la posizione di ogni singolo pezzo del corpo del nostro serpente, con la posizione del pezzo di corpo successiva, partendo dalla coda verso la testa.


Snake.x[3] = Snake.x[2] => Snake.x[3]=0 Snake.x[2]=30
Snake.x[2] = Snake.x[1] => Snake.x[2]=30 Snake.x[1]=40
Snake.x[1] = Snake.x[0] => Snake.x[1]=40 Snake.x[0]=50

Snake.x[3] = Snake.x[2] => Snake.x[3]=30 Snake.x[2]=40
Snake.x[2] = Snake.x[1] => Snake.x[2]=40 Snake.x[1]=50
Snake.x[1] = Snake.x[0] => Snake.x[1]=50 Snake.x[0]=60


Ad esempio, alla partenza i valori delle posizioni occupate dal serpente saranno, 0 30 40 50, 30 e' la coda.
Al primo ciclo, il valore 0, sara' sovrascritto dal valore 30, ovvero Snake.x[pos3] = Snake.x[pos3-1], e cosi' via tutti gli altri valori, 30 40 50 60.
Ogni singolo elemento del corpo vedra' la sua posizione aggiornata con il valore di chi lo precede, dando la sensazione che i blocchi si seguano come le pecore.
Tutto questo viene fatto sia per le posizioni X che per quelle Y, e nel frattempo si controlla anche che il serpente non tocchi se stesso.

Successivamente nella funzione, vengono incrementate le posizioni X e Y della testa del nostro serpente, che avra' sempre indice 0, in base al valore di incremento impostato nella funzione getInput().

Viene verificata una eventuale collisione con la mela nel campo di gioco, nel caso in cui vi sia stata collisione tra serpente e mela, si aumenta la lunghezza di quest'ultimo e si genera una nuova mela.

Controlliamo anche che non ci si sia spostati oltre i limiti del campo di gioco, cio' risulterebbe in un game over istantaneo.

Il gioco ora e' pronto, manca solo la grafica, disegniamo con l'aiuto della solita funzione DrawRect(), una grafica molto basilare e semplice, che per questo gioco si adatta alla grande!

void draw()
{
int z;
for (z = 0; z < Snake.lenght; z++) DrawRect(Snake.x[z], Snake.y[z], DOT_SIZE, DOT_SIZE, 0xffffff); DrawRect(Apple.x, Apple.y, DOT_SIZE, DOT_SIZE, 0xff0000); }

Un semplice ciclo, disegna un blocco delle dimensioni di 10x10 pixel alle posizioni salvate negli appositi vettori, il tutto ora sempre avere piu' senso no?

Sulla base dei concetti di questo gioco, si potrebbero realizzare tante altre cose, come ad esempio un gioco stile Centipede, oppure personaggi che seguano il giocatore come si vede in molti giochi RPG, e tanti altri possibili utilizzi.

Trovate il sorgente completo nel repository ufficiale del blog, divertitevi e sperimentateci su!

Nessun commento :

Posta un commento

Related Posts Plugin for WordPress, Blogger...