Domanda Movimento Snake in c++

EExoduss

Utente Silver
22 Agosto 2014
69
17
5
79
Salve a tutti,
sto provando a codificare snake in c++ (più per esercizio che per altro).

Questo è il codice che ho scritto finora.

C++:
#include <iostream>
using namespace std;
#include <cstdlib>
#include <windows.h>
#include <conio.h>

const int  WIDTH = 50;
const int  HEIGHT = 20;
int x=15, y = 15;
int foodX=15, foodY=15;
int dir;


void Board()
{
    for (int j = 0; j < HEIGHT; j++)
    {
        for (int i = 0; i < WIDTH; i++)
        {

            if (i == 0 || i == WIDTH - 1 || j == 0 || j == HEIGHT - 1)
            {
                cout << "#";
            }
            else if (i == x && j == y)
                cout << "O";
            else if (i == foodX && j == foodY)
                cout << "X";
            else cout << " ";

        }
        cout << endl;
    }
}

void input()
{
    if (_kbhit())
    {
        //0 = sinistra     1 = sopra     2 = destra    3 = sotto

        switch (_getch())
        {
            case 'a':
                dir = 0;
                break;

            case 'w':
                dir = 1;
                break;

            case 'd':
                dir = 2;
                break;

            case 's':
                dir = 3;
                break;
        }

        switch (dir)
        {
            case 0:
                x--;

            case 1:
                y--;

            case 2:
                x++;

            case 3:
                y++;

        }
    }
}


int main()
{
    
    while(true)
    {
        
        Board();
        input();
        cout << "x= " << x << " " << "y=" << y << " " << "dir = " << dir;
        
        SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), {0, 0});
    }
    
}

Il problema consiste nel fatto che il movimento non è coerente con i tasti premuti.

In particolare:
se premo "a" non succede nulla (e dovrebbe ridurre la x di 1)
se premo "w" si ottiene x++ (ed invece dovrebbe essere y--)
se premo "d" si ottiene x++ e y++ (ed invece dovrebbe essere solo x++)
se premo "s" miracolosamente fa quello che deve fare: y++.

Qualcuno può spiegarmi perchè non funziona?
 
I problemi che hai riscontrato sono legati al fatto che nel secondo switch hai dimenticato i break;. Un altro problema è che dir è dichiarata come variabile globale, nel tuo switch non è previsto un caso di default, per cui per tutte le altre lettere il numero rimarrà quello precedente, ed anche una gran confusione qualora iniziassi a scrivere lettere maiuscole non gestite dallo switch (es. premo s, va sotto, premo A va sotto perché è l'ultimo valido premuto).
 
Perfetto grazie mille. Effettivamente si è risolto mettendo i dovuti break;

Se volessi esporre un altro problema di altra natura devo aprire un altro topic o posso chiedere qui?
 
Eccomi qui di nuovo.

Ho dei grandissimi problemi con il movimento della coda. Sostanzialmente mi mancano delle idee.

Posto il codice (un pochino diverso rispetto a quello precedente).

C++:
#include <iostream>
#include <cstdlib>
#include <windows.h>
#include <conio.h>
#include <cmath>

using namespace std;


bool Game = true;
const int  WIDTH = 25;
const int  HEIGHT = 25;
int x = 10, y = 10;
char Board[WIDTH][HEIGHT];
int foodX = 7, foodY = 3;
int dir = 0;
int TailX[1000], TailY[1000];
int lenght = 0;
int coordinate_precedenti_x, coordinate_precedenti_y;



void InitialBoard()
{
    for (int j = 0; j <= HEIGHT; j++)
    {
        for (int i = 0; i <= WIDTH; i++)
        {
            if (i == 0 || i == WIDTH || j == 0 || j == WIDTH)
                Board[i][j] = '#';
            else Board[i][j] = ' ';
        }
    }
}


void DrawBoard()
{
    for (int j = 0; j < HEIGHT + 1; j++)
    {
        for (int i = 0; i < WIDTH + 1; i++)
        {

            cout << Board[i][j];
        }
        cout << endl;
    }

}

void DrawSnake()
{
    for (int j = 1; j < HEIGHT; j++)
    {
        for (int i = 1; i < WIDTH; i++)
        {
            for (int k = 0; k < lenght; k++)
            {

                if (i == x && j == y)
                {
                    Board[i][j] = 'O';
                }

                else if (lenght > 0)
                {
                    if (i == TailX[k] && j == TailY[k])
                    {
                        Board[i][j] = 'o';
                    }
                }
                else Board[i][j] = ' ';
            }
        }
    }
}



void Movement()
{
    if (_kbhit())
    {
        //selettore direzione
        switch (_getch())
        {
        case 'a':
            dir = 0;
            break;

        case 'w':
            dir = 1;
            break;

        case 'd':
            dir = 2;
            break;

        case 's':
            dir = 3;
            break;
        }
    }

    coordinate_precedenti_x = x;
    coordinate_precedenti_y = y;

    //movimento
    switch (dir)
    {
    case 0:
        x--;
        break;

    case 1:
        y--;
        break;

    case 2:
        x++;
        break;

    case 3:
        y++;
        break;
    }


    //passaggio oltre confine
    if (x == 0)
        x = WIDTH - 1;
    if (x == WIDTH)
        x = 1;
    if (y == 0)
        y = HEIGHT - 1;
    if (y == HEIGHT)
        y = 1;

    TailX[0] = x;
    TailY[0] = y;
    

    //movimento della coda
    for (int k = 0; k < lenght; k++)
    {
        if (k == 0)
        {
            TailX[k] = coordinate_precedenti_x;
            TailY[k] = coordinate_precedenti_y;
        }
        TailX[k] = TailX[k - 1];
        TailY[k] = TailY[k - 1];
    }

}

void Spawnfood() //spawna gli obiettivi
{
    for (int j = 1; j < HEIGHT; j++)
    {
        for (int i = 1; i < WIDTH; i++)
        {
            if (i == foodX && j == foodY)
            {
                Board[i][j] = 'X';
            }
        }
    }
}

void Eventeat() //Quando il serpente colpisce l'obiettivo
{
    if (x == foodX && y == foodY)
    {
        foodX = (abs(rand() % WIDTH - 2));
        foodY = (abs(rand() % HEIGHT - 2));

        if (foodX == 0)
        {
            foodX = 1;
        }

        if (foodY == 0)
        {
            foodY = 1;
        }

        lenght++;
    }
}

void GameOver() //ferma il gioco
{
    if (_kbhit())
    {
        if (_getch() == '0')
            Game = false;
    }
}

int main()
{

    InitialBoard();
    
    while (Game == true)
    {

        DrawBoard();
        DrawSnake();
        Movement();
        Spawnfood();
        Eventeat();
        GameOver();

        cout << "FoodX = " << foodX << " " << "FoodY = " << foodY << endl;
        for (int k = 0; k < lenght; k++)
        {
            cout << TailX[k] << endl;
            cout << TailY[k] << endl;
        }

        SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), { 0, 0 });
        Sleep(50);
    }

    cout << "Game Over";
}

Come si può notare, invece dei cout << " " o cout << "#", ho preferito fare un array bidimensionale Board[i][j] i cui elementi mostrano cosa l'utente deve vedere in ogni punto della board.

In particolare, nella funzione DrawSnake() si può notare come questa disegni:
  1. Una "O" se siamo in corrispondenza della testa
  2. Una "o" se siamo in corrispondenza di uno degli elementi della coda
  3. Uno " " se non siamo da nessuna parte.
Ora, a me, runnando questo codice non mi esce assolutamente nulla. Ho notato che se metto 3 for uno dentro l'altro, l'output in console è sostanzialmente errato.

Comunque, la mia idea era quella di far prendere la posizione di un elemento della coda pari alla posizione dell'elemento che lo precede.

Ovvero:
Codice:
void Movement()

{

    if (_kbhit())

    {

        //selettore direzione

        switch (_getch())

        {

        case 'a':

            dir = 0;

            break;



        case 'w':

            dir = 1;

            break;



        case 'd':

            dir = 2;

            break;



        case 's':

            dir = 3;

            break;

        }

    }



    coordinate_precedenti_x = x;

    coordinate_precedenti_y = y;



    //movimento

    switch (dir)

    {

    case 0:

        x--;

        break;



    case 1:

        y--;

        break;



    case 2:

        x++;

        break;



    case 3:

        y++;

        break;

    }





    //passaggio oltre confine

    if (x == 0)

        x = WIDTH - 1;

    if (x == WIDTH)

        x = 1;

    if (y == 0)

        y = HEIGHT - 1;

    if (y == HEIGHT)

        y = 1;



    TailX[0] = x;

    TailY[0] = y;

    



    //movimento della coda

    for (int k = 0; k < lenght; k++)

    {

        if (k == 0)

        {

            TailX[k] = coordinate_precedenti_x;

            TailY[k] = coordinate_precedenti_y;

        }

        TailX[k] = TailX[k - 1];

        TailY[k] = TailY[k - 1];

    }



}



void Spawnfood() //spawna gli obiettivi

{

    for (int j = 1; j < HEIGHT; j++)

    {

        for (int i = 1; i < WIDTH; i++)

        {

            if (i == foodX && j == foodY)

            {

                Board[i][j] = 'X';

            }

        }

    }

}


Alla riga 51 e 53 ho impostato coordinate_precedenti_x = x; e coordinate_precedenti_y = y;.

Successivamente vengono cambiate le coordinate con lo switch che segue //movimento e successivamente ho messo le seguenti righe di codice:

Codice:
 TailX[0] = x;



    TailY[0] = y;



    







    //movimento della coda



    for (int k = 0; k < lenght; k++)



    {



        if (k == 0)



        {



            TailX[k] = coordinate_precedenti_x;



            TailY[k] = coordinate_precedenti_y;



        }



        TailX[k] = TailX[k - 1];



        TailY[k] = TailY[k - 1];



    }

Ovvero, se k==0 significa che il primo elemento della coda (TailX[0] e TailY[0]) prende le coordinate della testa PRIMA del movimento.
per gli altri casi, semplicemente l'elemento prende le coordinate dell'elemento che lo precede.

Ad ogni modo è tutto sbagliato perchè non funziona nulla....
 
Ciao, anche io qualche tempo fa mi sono cimentato nell'implementare lo snake (in C però, e non in C++, ma poco cambia comunque), ed effettivamente non è proprio semplicissima come cosa.

Premetto che non ho letto in modo approfondito il codice che hai postato, anche perché l'ultimo messaggio risale a giorni fa e tu stesso hai detto che è tutto sbagliato e non funziona, ma se ti può essere utile provo ad illustrarti il modo in cui ho impostato io il problema.

Il riquadro di gioco l'ho schematizzato come una matrice di interi di dimensione RxC. Tale matrice può assumere 3 valori diversi:
"0") sta ad indicare una casella contenente o il corpo del serpente o un qualsiasi altro ostacolo (tipo un bordo esterno non attraversabile o un ipotetico ostacolo interno). Se la testa del serpente invade una di queste celle, ovviamente scatta il "game over";
"1") sta ad indicare una casella "libera" che può essere occupata dalla testa del serpente;
"2") sta ad indicare una casella "libera" contenente però il "boccone" che il serpente dovrà mangiare.

Per tenere invece traccia della posizione del serpente ho utilizzato un array (chiamiamolo V) di COORD di dimensione sufficiente a contenere un serpente di lunghezza massima. All'interno di V (implementato come una sorta di array circolare), per tenere traccia della coda e della testa del serpente, utilizzo due variabili puntatore (chiamiamole rispettivamente p_CODA e p_TESTA).
Quando la testa del serpente si sposta in una nuova casella libera, per tenere traccia del movimento del serpente bisogna aggiornare p_TESTA e p_CODA:
- p_TESTA andrà a puntare all'elemento immediatamente successivo in V (elemento che dovrà ovviamente essere aggiornato con le nuove coordinate);
- p_CODA subirà una sorte diversa in base al fatto che la casella libera occupata dalla testa del serpente contenga o meno un "boccone": se non lo contiene, p_CODA subirà la stessa sorte di p_TESTA e andrà a puntare all'elemento immediatamente successivo in V (ma, a differenza di quanto avviene con p_TESTA, non c'è alcun bisogno di aggiornare le coordinate); se invece la nuova casella contiene un boccone, p_CODA va lasciato inalterato (il fatto che p_TESTA avanza e p_CODA no, sta infatti a rappresentare l'allungamento del corpo del serpente di un'unità).


Ecco una breve gif del gioco da me implementato:

Animation.gif


Non so quanto sia stata chiara la mia spiegazione, quindi se hai qualche dubbio chiedi pure!
 
  • Mi piace
Reazioni: DispatchCode
Premetto di non averlo ancora provato, non ho nemmeno compilato o guardato bene la logica che hai utilizzato; più tardi magari lo farò.

C:
  coordinate_precedenti_x = x;
  coordinate_precedenti_y = y;
//passaggio oltre confine
    if (x == 0)
        x = WIDTH - 1;

    if (x == WIDTH)
        x = 1;

    if (y == 0)
        y = HEIGHT - 1;

    if (y == HEIGHT)
        y = 1;

    TailX[0] = x;
    TailY[0] = y;

    //movimento della coda
    for (int k = 0; k < lenght; k++)
    {
        if (k == 0)
        {
            TailX[k] = coordinate_precedenti_x;
            TailY[k] = coordinate_precedenti_y;
        }
        TailX[k] = TailX[k - 1];
        TailY[k] = TailY[k - 1];
    }
}

Mi limito intanto a farti notare un paio di cose che mi sono balzate all'occhio prima leggendo da telefono.
La prima è che non inizializzi il seme, non ho visto nel codice srand(time(NULL));. Questo sicuro è una causa importante dei problemi che stai avendo.

Ovvero, se k==0 significa che il primo elemento della coda (TailX[0] e TailY[0]) prende le coordinate della testa PRIMA del movimento.
per gli altri casi, semplicemente l'elemento prende le coordinate dell'elemento che lo precede.

Ok, ma hai un problema ad esempio qui:
C:
    TailX[0] = x;
    TailY[0] = y;

nel primo ciclo entra subito nell'IF e ti andrà a sovrascrivere i valori assegnati in precedenza. Ti servono (x,y) oppure (coordinate_precedenti_x, coordinate_precedenti_y)?
Perchè se ti servono solo queste ultime, dovresti fare ad esempio:

C:
    TailX[0] = coordinate_precedenti_x;
    TailY[0] = coordinate_precedenti_y;

    //movimento della coda
    for (int k = 1; k < lenght; k++)
    {
        TailX[k] = TailX[k - 1];
        TailY[k] = TailY[k - 1];
    }

E qui arriviamo subito al secondo problema: sia il mio estratto elaborato qui sopra, sia il tuo, presentano un problema: nel primo ciclo vai a scrivere out of bounds, in una posizione che non fa parte di quell'array (k-1 = 0-1 = -1). La forma corretta, usando il mio codice qui sopra, dovrebbe essere:

C:
    TailX[0] = coordinate_precedenti_x;
    TailY[0] = coordinate_precedenti_y;

    //movimento della coda
    for (int k = 2; k < lenght; k++)
    {
        TailX[k] = TailX[k - 1];
        TailY[k] = TailY[k - 1];
    }

Tuttavia secondo me stai comunque sbagliando l'approccio.



La mia proposta è alternativa alla sua (di Super Squirrel) e più vicino a ciò che stai facendo, per non cambiare troppo della tua logica. Mantieni il tuo switch delle direzioni, e in base alla direzione scelta, setti a 1 a -1 (in base alla direzione) o a 0 le variabili.
Con un esempio:

C:
    switch (_getch())
    {
    case 'a':
        x = -1;
        break;

    case 'w':
        y = -1;
        break;

    case 'd':
        x = 1;
        break;

    case 's':
        y = 1;
        break;
    }

Ora se vuoi puoi cambiare le posizioni alla testa, e verificare se si scontra con qualcosa:
C:
snake[0].x += x;
snake[0].y += y;

// Qui puoi fare controlli sulle coordinate del cibo, di un muro o del serpente stesso (se vuoi che il gioco termini quando si scontra con sè stesso)

Fatto ciò, sposti tutto il suo corpo, ma partendo dalla coda (per non sovrascrivere i valori):
C:
for(int i = snake_length-1; i > 0; i--) {
    snake[i].x = snake[i-1].x + x;
    snake[i].y = snake[i-1].y + y;
}

In questo modo che sia in avanti o all'indietro oppure a dx o a sx, vai a spostare ogni parte di snake. A questo punto puoi anche introdurre poi il passaggio/transizione di snake da dx verso sx o dall'alto verso il basso (superando i "bordi" che delimitano la board).

In pratica le variabili x e y li sopra saranno temporanee, verranno settate ad ogni nuova iterazione in base alla direzione che gli viene data.
 
@DispatchCode in realtà non direi che la mia versione sia poi così tanto "alternativa", per esempio non ho affrontato la questione dello "switch delle direzioni" semplicemente perché mi sono limitato a descrivere la logica generale.

Per quanto riguarda l'array che deve tener traccia della posizione del serpente, anche io lo aggiorno ad ogni iterazione, però invece di farlo spostando 2N elementi (dove N è il numero di unità che costituiscono il corpo del serpente in una data iterazione, mentre il 2 si riferisce alle coordinate x e y), mi limito a modificare i due puntatori p_TESTA e p_CODA (implementando una sorta di array circolare).
Ho scelto questa strada (che riduce il numero di modifiche a 2 indipendentemente dalla lunghezza del serpente) non solo per una questione astratta di efficienza, ma anche per rendere il gioco più fluido (anche se probabilmente, con i moderni computer, l'occhio non noterebbe alcuna differenza tra le due implementazioni, non so...).
 
@DispatchCode in realtà non direi che la mia versione sia poi così tanto "alternativa", per esempio non ho affrontato la questione dello "switch delle direzioni" semplicemente perché mi sono limitato a descrivere la logica generale.

Per quanto riguarda l'array che deve tener traccia della posizione del serpente, anche io lo aggiorno ad ogni iterazione, però invece di farlo spostando 2N elementi (dove N è il numero di unità che costituiscono il corpo del serpente in una data iterazione, mentre il 2 si riferisce alle coordinate x e y), mi limito a modificare i due puntatori p_TESTA e p_CODA (implementando una sorta di array circolare).
Ho scelto questa strada (che riduce il numero di modifiche a 2 indipendentemente dalla lunghezza del serpente) non solo per una questione astratta di efficienza, ma anche per rendere il gioco più fluido (anche se probabilmente, con i moderni computer, l'occhio non noterebbe alcuna differenza tra le due implementazioni, non so...).

Ho parlato di alternativa in quanto "diversa" da quella riportata da me; tu infatti modifichi i due puntatori, io no, e vado a scorrere tutti gli elementi dell'array.

Comunque su CUI non saprei se si hanno problemi di fluidità, io la scrissi oltre 12 anni fa in Java (dove non si hanno puntatori, ma solo riferimenti) e facevo uso di una GUI (Swing).
le iterazioni sono comunque pressochè irrilevanti per un pc "moderno"; che siano 10 iterazioni o 50 dovrebbe cambiare poco e niente. Il tuo rimane sicuramente più rapido in quanto eviti tutto questo ciclo.
 
  • Mi piace
Reazioni: Super Squirrel


La mia proposta è alternativa alla sua (di Super Squirrel) e più vicino a ciò che stai facendo, per non cambiare troppo della tua logica. Mantieni il tuo switch delle direzioni, e in base alla direzione scelta, setti a 1 a -1 (in base alla direzione) o a 0 le variabili.
Con un esempio:

C:
switch (_getch())
{
case 'a':
x = -1;
break;

case 'w':
y = -1;
break;

case 'd':
x = 1;
break;

case 's':
y = 1;
break;
}
Ora se vuoi puoi cambiare le posizioni alla testa, e verificare se si scontra con qualcosa:
C:
snake[0].x += x;
snake[0].y += y;

// Qui puoi fare controlli sulle coordinate del cibo, di un muro o del serpente stesso (se vuoi che il gioco termini quando si scontra con sè stesso)
Fatto ciò, sposti tutto il suo corpo, ma partendo dalla coda (per non sovrascrivere i valori):
C:
for(int i = snake_length-1; i > 0; i--) {
snake[i].x = snake[i-1].x + x;
snake[i].y = snake[i-1].y + y;
}
In questo modo che sia in avanti o all'indietro oppure a dx o a sx, vai a spostare ogni parte di snake. A questo punto puoi anche introdurre poi il passaggio/transizione di snake da dx verso sx o dall'alto verso il basso (superando i "bordi" che delimitano la board).

In pratica le variabili x e y li sopra saranno temporanee, verranno settate ad ogni nuova iterazione in base alla direzione che gli viene data.
Non vorrei dire boiate visto che non ho modo di provare, ma secondo me in ogni case devi assegnare un valore sia ad x che ad y
C:
witch (_getch()) {
case 'a':
  x = -1;
  y = 0;
  break;
  // ...
}
altrimenti ti ritrovi con il serpente che va in diagonale. Mentre il ciclo for secondo me l'hai invertito:
C:
if (grow) snake_length++;

// prima sposto la coda
for(int i = snake_length-1; i > 0; i--) {
    snake[i].x = snake[i-1].x; // senza +x
    snake[i].y = snake[i-1].y; // senza +y
}

// poi sposto la testa
snake[0].x += x;
snake[0].y += y;

Comunque l''approccio di Super Squirrel è abbastanza interessante. Non ho capito la sua spiegazione ("p_TESTA andrà a puntare all'elemento immediatamente successivo in V"), ma ho capito il concetto che ci sta dietro: tieni quasi tutto il corpo fermo e sposti solo e gestisci solo i due estremi. Sicuramente meno intuitivo, però è molto pulito.
 
Ultima modifica:
Non ho capito la sua spiegazione ("p_TESTA andrà a puntare all'elemento immediatamente successivo in V"), ma ho capito il concetto che ci sta dietro: tieni quasi tutto il corpo fermo e sposti solo e gestisci solo i due estremi. Sicuramente meno intuitivo, però è molto pulito.
Ciao, ho provato a schematizzarlo, spero possa risultare più chiaro:


All'iterazione N+2 mostro anche cosa intendevo con "array circolare".
 
Ultima modifica:
Ciao, ho provato a schematizzarlo, spero possa risultare più chiaro:


All'iterazione N+2 mostro anche cosa intendevo con "array circolare".
Non sono sicuro di aver capito benissimo, ma più tardi me lo guardo con calma e ci rifletto. Mal che vada ti chiedo spiegazioni.

Il concetto di muovere solo testa e coda e di tenere il resto del corpo inalterato io l'avrei implementato così (tenendo buona la bozza di codice di DispatchCode):
C:
// se necessario, allungo la coda
if (grow) snake_length++;

// la coda prende il posto della testa
snake[snake_length - 1].x = snake[0].x;
snake[snake_length - 1].y = snake[0].y;

// sposto la testa
snake[0].x += x;
snake[0].y += y;
Sottolineo ancora che non ho compilato ed eseguito quindi ci sta che ci sia qualcosa che mi sfugge, però mi sembra molto semplice. EDIT: nevermind, ci ho ripensato e non funziona.

EDIT2: fatto con i puntatori dovrebbe essere una roba di questo tipo (grow e move è l'unica cosa che conta, il resto è per testare)
C:
#include <stdio.h>
#include <stdlib.h>

struct point {
  int x, y;
};

struct node {
  struct point pos;
  struct node *succ, *pred;
};

struct snake {
  struct node *head, *tail;
};

enum direction { UP, DOWN, LEFT, RIGHT };

void grow(struct snake *snake) {
  struct node *t = malloc(sizeof(struct node));
  t->succ = NULL;
  t->pred = snake->tail;
  snake->tail->succ = t;
  snake->tail = t;
}

void move(struct snake *snake, enum direction direction) {
  if (snake->tail != snake->head) {
    struct node *t = snake->tail;
    snake->tail = t->pred;
    snake->tail->succ = NULL;
    t->pos = snake->head->pos;
    snake->head->pred = t;
    t->succ = snake->head;
    t->pred = NULL;
    snake->head = t;
  }

  switch (direction) {
  case UP: snake->head->pos.y -= 1; break;
  case DOWN: snake->head->pos.y += 1; break;
  case LEFT: snake->head->pos.x -= 1; break;
  case RIGHT: snake->head->pos.x += 1; break;
  }
}

int main() {
  struct node *first = malloc(sizeof(struct node));
  first->pos = (struct point){ 0, 0 };
  first->pred = NULL;
  first->succ = NULL;

  struct snake snake = { first, first };

  enum direction direction = RIGHT;
  int food = 5; // nutricious food, keep growing for 5 turns

  // game loop
  while (1) {
    if (food > 0) {
      grow(&snake);
      food--;
    }

    // I don't have getchar
    char c, newline;
    printf(">>> ");
    scanf("%c", &c);
    scanf("%c", &newline);
    printf("=> %c\n", c);

    switch (c) {
    case 'w': direction = UP; break;
    case 'a': direction = LEFT; break;
    case 's': direction = DOWN; break;
    case 'd': direction = RIGHT; break;
    }

    move(&snake, direction);

    // can't bother to write the whole board for testing
    printf("[");
    for (struct node *n = snake.head; n != NULL; n = n->succ)
      printf(" (%d %d) ", n->pos.x, n->pos.y);
    printf("]\n");
  }

  return 0;
}
 
  • Mi piace
Reazioni: 0xbro
@St3ve mi sembra di capire che hai implementato una sorta di lista, io invece coi puntatori intendevo qualcosa di diverso.
Nel seguente frammento di codice ho provato a condensare il cuore della mia implementazione:

C:
#define RIG 14
#define COL 28
#define DIM (RIG * COL)

int grid[RIG][COL];
//0 --> casella con ostacolo
//1 --> casella libera senza boccone
//2 --> casella libera con boccone

COORD snake[DIM] = {{x0, y0}, {x1, y1}, {x2, y2}};
COORD *tail = snake;
COORD *head = snake + 2;

//----------------------------------------------------------

int snake_update(int grid[RIG][COL], COORD *snake, COORD **tail, COORD **head, const unsigned int direction)
{
    COORD temp = **head;
    *head = (*head == snake + DIM - 1) ? snake : *head + 1;
    **head = temp;
    switch(direction)
    {
        case UP:    (*head)->Y -= 1; break;
        case DOWN:  (*head)->Y += 1; break;
        case LEFT:  (*head)->X -= 1; break;
        case RIGHT: (*head)->X += 1; break;
    }
    if(grid[(*head)->Y][(*head)->X])
    {
        if(grid[(*head)->Y][(*head)->X] == 1)
        {
            grid[(*tail)->Y][(*tail)->X] = 1;
            *tail = (*tail == snake + DIM - 1) ? snake : *tail + 1;
        }
        else
        {
            genera_nuovo_boccone(grid);
        }
        grid[(*head)->Y][(*head)->X] = 0;
        return 1;
    }
    return 0;//GAME OVER
}
 
@St3ve mi sembra di capire che hai implementato una sorta di lista, io invece coi puntatori intendevo qualcosa di diverso.
Sì, credo di aver capito ed è essenzialmente la stessa. Abbiamo scritto entrambi una doubly-linked list, solo che io l'ho implementata in modo classico con i nodi mentre tu l'hai implementata all'interno di un array. Usando gli array volendo si possono rimuovere tutti i puntatori:
C:
#include <stdio.h>
#include <stdlib.h>

// WIDTH * HEIGHT
#define MAX 10

struct point {
  int x, y;
};

struct snake {
  struct point body[MAX];
  size_t head, tail, length;
};

enum direction { UP, DOWN, LEFT, RIGHT };

void move(struct snake *snake, enum direction direction) {
  // clean the board (I didn't implement it) at position snake->body[snake->tail]
  if (snake->head != snake->tail) {
    size_t x = snake->tail;
    snake->tail = (MAX + x - 1) % MAX;
    x = (MAX + snake->head - 1) % MAX;
    snake->body[x] = snake->body[snake->head];
    snake->head = x;
  }

  switch (direction) {
  case UP: snake->body[snake->head].y -= 1; break;
  case DOWN: snake->body[snake->head].y += 1; break;
  case LEFT: snake->body[snake->head].x -= 1; break;
  case RIGHT: snake->body[snake->head].x += 1; break;
  }
  // draw the board (I didn't implement it) at position snake->body[snake->head]
}

void grow(struct snake *snake) {
  snake->length++;
  snake->tail = (snake->tail + 1) % MAX;
}

int main() {
  struct snake snake = { {(struct point){0, 0}}, 0, 0, 1 };
  enum direction direction = RIGHT;
  int food = 5; // nutricious food, keep growing for 5 turns

  // game loop
  while (1) {
    if (food > 0) {
      grow(&snake);
      food--;
    }

    char c, newline;
    printf(">>> ");
    scanf("%c", &c);
    scanf("%c", &newline);
    printf("=> %c\n", c);

    switch (c) {
    case 'w': direction = UP; break;
    case 'a': direction = LEFT; break;
    case 's': direction = DOWN; break;
    case 'd': direction = RIGHT; break;
    }

    move(&snake, direction);

    // can't bother to write the whole board for testing
    printf("[");
    for (size_t i = snake.head; i != (snake.tail + 1) % MAX; i = (i + 1) % MAX)
      printf(" (%d %d) ", snake.body[i].x, snake.body[i].y);
    printf("] <== snake\n");

    printf("[");
    for (size_t i = 0; i < MAX; i++)
      printf(" (%d %d) ", snake.body[i].x, snake.body[i].y);
    printf("] <== internals\n");
  }

  return 0;
}
Per far vedere ancora meglio che è la stessa cosa ho aggiunto anche la stampa dell'array per come l'hai disegnato nel tuo schema. L'unica differenza è che io non mi preoccupo di cancellare la board, perché non ho nessuna board (non ho implementato tutto il gioco, ho solo postato l'implementazione di quello che avevi descritto tu).
 
@Super Squirrel interessante comunque, sarei curioso di vedere l'intero source. Ne hai una versione su github o simili oppure lo tieni closed?

@St3ve lascio il codice del mio sotto spoiler. Considera che probabilmente sarà del 2010 circa, quindi ci saranno cose sicuramente più contorte di quanto dovrebbero (per non parlare dei commenti)... :asd:

Java:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import java.util.*;

// Classe che gestisce ogni singolo pezzo del serpente
class Snake {
  private Image img;
  private int x, y;
  
  Snake(String urlImg) {
    this.img = new ImageIcon(urlImg).getImage();
    this.x = 15;
    this.y = 15;
  }
  
  void setX(int x) {
    this.x = x;
  }
  void setY(int y) {
    this.y = y;
  }
  
  int getX() {
    return x;
  }
  int getY() {
    return y;
  }
  
  Rectangle getRectangle() {
    return new Rectangle(x,y,img.getWidth(null), img.getHeight(null));
  }
  
  Image getImg() {
    return img;
  }
}

// Classe che gestisce il frutto
class Frutto {
  private int x, y;
  private Image img;
  private Random random;
  
  Frutto(String urlImg) {
    this.img = new ImageIcon(urlImg).getImage();
    
    random = new Random();
    inizializza();
  }
  
  void inizializza() {
    this.x = random.nextInt(592);
    this.y = random.nextInt(523);
  }
  
  Image getImg() {
    return img;
  }
  
  int getX() {
    return x;
  }
  int getY() {
    return y;
  }
  
  Rectangle getRectangle() {
    return new Rectangle(x,y,img.getWidth(null), img.getHeight(null));
  }
} 


// Pannello ove vengono disegnati i componenti
class Pannello extends JPanel implements Runnable, KeyListener {
  private Thread thread;                       //  Thread d'esecuzione per il movimento dello Snake
  
  private int tmpX, tmpY;                      // Variabili temporanee utilizzate per settare il valore alla testa del serpente
  private final int WIDTH = 592;               // Larghezza del piano
  private final int HEIGHT = 523;              // altezza del piano
  private final int PAUSE_MOV = 50;
  private Vector<Snake> partiSnake;         // Ogni singola parte del corpo di Snake
  private boolean avvia,nuovoCibo,perso;       // Controllo Thread; controllo cibo, o sconfitta
  private Frutto frutto;                       // Rappresenta il frutto
  private Rectangle rectangle;                 // Rettangolo di gioco
  private JLabel puntiLabel;
  private int punti;
  private Pannello pannello;
 
  Pannello(Frame frame) {
    nuovoCibo = true;
    perso = false;
    punti = 0;
    avvia = true;
    pannello = this;

    JMenuBar barraMenu = new JMenuBar();
    frame.setJMenuBar(barraMenu);
    
    JMenu file = new JMenu("File");
    JMenuItem nuovo = new JMenuItem("Nuova Partita");
    JMenuItem salva = new JMenuItem("Salva...");
    JMenuItem apri = new JMenuItem("Apri...");
    JMenuItem esci = new JMenuItem("Esci");
    
    JMenu impostazioni = new JMenu("Impostazioni");
    JMenuItem impostazioniGioco = new JMenuItem("Impostazioni Gioco");
    
    JMenu mappa = new JMenu("Mappe");
    JMenuItem selezionaMappa = new JMenuItem("Seleziona Mappa...");
    JMenuItem creaMappa = new JMenuItem("Crea Mappa...");
    
    file.add(nuovo);
    file.add(salva);
    file.add(apri);
    file.addSeparator();
    file.add(esci);
    
    impostazioni.add(impostazioniGioco);
    
    mappa.add(selezionaMappa);
    mappa.add(creaMappa);
    
    barraMenu.add(file);
    barraMenu.add(impostazioni);
    barraMenu.add(mappa);
    
    
    // Pressione su Nuova Partita
    // ---------------------------------------------------------------------------------------------------------------------------------
    nuovo.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent ae) {
        Graphics2D g2 = (Graphics2D) pannello.getGraphics();
        g2.clearRect(0,0,WIDTH,HEIGHT);
        inizializzaOggetti();
      }
    });
    // ---------------------------------------------------------------------------------------------------------------------

    JToolBar toolBar = new JToolBar();
    puntiLabel = new JLabel("Punti: "+punti);
    toolBar.add(puntiLabel);
    frame.add(toolBar);

    // Rettangolo del piano
    rectangle = new Rectangle(0,0,WIDTH,HEIGHT);
    
    // Opzioni finestra
    setFocusable(true);
    setDoubleBuffered(true);
    // Gestisce pressione tasti sul piano
    addKeyListener(this);
    frame.getContentPane().add(toolBar,BorderLayout.NORTH);
    
    //frame.setUndecorated(true);
    //frame.getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
    
    // Creo thread
    
    thread = new Thread(this);
    thread.setPriority(Thread.MAX_PRIORITY);
    thread.start();   
  }
  
  synchronized private void inizializzaOggetti() {
    // Creo ed inizializzo il frutto
    punti = 0;
    puntiLabel.setText("Punti: 0");
    frutto = new Frutto("mela.jpg");
    frutto.inizializza();
    
    // Creo ed inizializzo Array e serpente iniziale
    partiSnake = new Vector<Snake>();
    System.out.println(partiSnake.size());
    partiSnake.add(new Snake("snake.jpg"));
    partiSnake.add(new Snake("snake.jpg"));
    partiSnake.add(new Snake("snake.jpg"));
    partiSnake.add(new Snake("snake.jpg"));
  }

  synchronized void avvia() {
    synchronized(thread) {   
      avvia = true; 
      thread.notify();
    }
  }
  
  
  // Metodi definiti da KeyListener
  public void keyTyped(KeyEvent ke) {}
  public void keyReleased(KeyEvent ke) {}
  
  public void keyPressed(KeyEvent ke) {
    if(ke.getKeyCode() == KeyEvent.VK_UP) {
      tmpY = -15;
      tmpX = 0;
    }
    else if(ke.getKeyCode() == KeyEvent.VK_DOWN) {
      tmpY = 15;
      tmpX = 0;
    }
    else if(ke.getKeyCode() == KeyEvent.VK_LEFT) {
      tmpX = -15;
      tmpY = 0;
    }
    else if(ke.getKeyCode() == KeyEvent.VK_RIGHT) {
      tmpX = 15;
      tmpY = 0;
    }
    if(thread.isAlive()) {
      avvia();
      System.gc();
    }
  }

  void pausa() {
    synchronized(thread) {
    try {
    thread.wait();
    } catch(Exception e) {e.printStackTrace();}
    }
  }

  public void run() {
    try {
      pausa();
      while(avvia) {
        Thread.sleep(PAUSE_MOV);       // Mezzo secondo di pausa - QUESTO DATO IN FUTURO VERRA' SETTATO
        // Inizializzo la posizione del Serpente (della testa)
        //partiSnake.get(0).setX(partiSnake.get(0).getX()+tmpX);
        //partiSnake.get(0).setY(partiSnake.get(0).getY()+tmpY);
        
        Snake snake = partiSnake.get(0);
        snake.setX(snake.getX()+tmpX);
        snake.setY(snake.getY()+tmpY);
        
        // Prendo la testa e verifico se e' in una posizione permessa
        // e se ha incontrato del cibo
        
        if(scontrato(snake)) {
          perso = true;
          avvia = false;
          snake = null;
          repaint();
          pausa();
          
          continue;
        }
         snake = null;
         
        // Cambio coordinate a tutti i pezzi del Serpente dalla coda alla testa
        for(int i=partiSnake.size()-1; i>0; i--) {
          partiSnake.get(i).setX(partiSnake.get(i-1).getX());
          partiSnake.get(i).setY(partiSnake.get(i-1).getY());
        }
              
        repaint();
        System.gc();
     
     }
    }catch(Exception e) {e.printStackTrace();}

  }
  
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;
    if(partiSnake == null) return;
    
    // In questo caso il serpente di e' scontrato
    if(perso) {
      g2.clearRect(0,0,WIDTH,HEIGHT);
      g2.setFont(new Font("Monospace", Font.BOLD, 38));
      g2.setColor(Color.RED);
      g2.drawString("Hai Perso!",(WIDTH/2)-80, HEIGHT/2);
      perso = false;
      return;
    }
    
    // Disegno ogni parte del corpo del serpente
    for(Snake s : partiSnake) {
      g2.drawImage(s.getImg(),s.getX(),s.getY(),null);
    }
    // Disegno il frutto
    g2.drawImage(frutto.getImg(), frutto.getX(), frutto.getY(), null);
  }

  boolean scontrato(Snake snake) {
    // Verifico se il serpente si e' scontrato ai bordi
    if(!snake.getRectangle().intersects(rectangle)) {
      return true;
    }
    // Verifico se il serpente ha mangiato il frutto
    else if(snake.getRectangle().intersects(frutto.getRectangle())) {
      frutto.inizializza();
      nuovoCibo = true;
      partiSnake.add(new Snake("snake.jpg"));
      punti+=5;
      puntiLabel.setText("Punti: "+punti);
      return false;
    }
    
    // Scontro contro se stesso
    return verificaScontro(snake);
  }

  private boolean verificaScontro(Snake s) {
    if(punti == 0) return false;
    
    for(int i=1; i<partiSnake.size()-1; i++) {
      if(s.getRectangle().intersects(partiSnake.get(i).getRectangle())) {
        return true;
      }
    }
    
    return false;
  }
  
}

class Frame extends JFrame {
  Pannello pannello;
  
  Frame() {
    pannello = new Pannello(this); 
    pannello.setBackground(Color.WHITE);
    pannello.setOpaque(true);
    getContentPane().add(pannello);
    
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  }
  
  public static void makeGUI() {
    Frame f = new Frame();
    f.setSize(610,600);
    f.setResizable(false);
    f.setVisible(true);
  }
  
  public static void main(String[] args) {
    try {
      SwingUtilities.invokeAndWait(new Runnable() {
        public void run() {
          makeGUI();
        }
      });
    } catch(Exception e) {}
  }
}

Su questo laptop non ho il JDK, altrimenti avrei allegato una versione compilata.
 
  • Mi piace
Reazioni: St3ve
@Super Squirrel interessante comunque, sarei curioso di vedere l'intero source. Ne hai una versione su github o simili oppure lo tieni closed?
Non ho molta familiarità con github (nel senso che col senno di poi mi rendo conto di essermici imbattuto qualche volta, ma non so precisamente in cosa consiste), in ogni caso posso anche postare il codice qui, non c'è problema:

C:
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#include <windows.h>

#define R 15
#define C 29
#define DIM (R - 2) * (C - 2)
#define START_LENGTH 3
#define DIM_OBSTACLE 31

#define BITE 3
#define SOUND 7
#define BLOCK 79

#define UL 201
#define UR 187
#define DL 200
#define DR 188
#define HOR 205
#define VER 186
#define CROSS 206
#define UL_THROUGH 218
#define UR_THROUGH 191
#define DL_THROUGH 192
#define DR_THROUGH 217
#define HOR_THROUGH 196
#define VER_THROUGH 179

#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77

COORD coord_obstacle[DIM_OBSTACLE] = {{ 3, R / 2}, { 4, R / 2}, { 5, R / 2}, { 6, R / 2}, { 7, R / 2}, { 8, R / 2}, { 9, R / 2},
                                      {10, R / 2}, {11, R / 2}, {12, R / 2}, {13, R / 2}, {15, R / 2}, {16, R / 2}, {17, R / 2},
                                      {18, R / 2}, {19, R / 2}, {20, R / 2}, {21, R / 2}, {22, R / 2}, {23, R / 2}, {24, R / 2},
                                      {25, R / 2}, {C / 2,  3}, {C / 2,  4}, {C / 2,  5}, {C / 2,  6}, {C / 2,  8}, {C / 2,  9},
                                      {C / 2, 10}, {C / 2, 11}, {C / 2, R / 2}};

int char_obstacle[DIM_OBSTACLE] = {HOR, HOR, HOR, HOR, HOR, HOR, HOR, HOR, HOR, HOR, HOR, HOR, HOR, HOR, HOR, HOR, HOR, HOR,
                                   HOR, HOR, HOR, HOR, VER, VER, VER, VER, VER, VER, VER, VER, CROSS};

void set_position(const COORD xy)
{
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), xy);
}

void delay(const int ms)
{
    clock_t goal = ms * CLOCKS_PER_SEC / 1000 + clock();
    while(goal > clock());
}

int get_input(const int direction)
{
    return getch() == 224 ? getch() : direction;
}

void choose_settings(int *through, int *obstacle)
{
    char c;
    do
    {
        printf("THROUGH MODE (y/n) --> ");
        scanf(" %c", &c);
    }
    while(c != 'y' && c != 'n');
    *through = c == 'y';
    do
    {
        printf("OBSTACLES (y/n) --> ");
        scanf(" %c", &c);
    }
    while(c != 'y' && c != 'n');
    *obstacle = c == 'y';
    system("CLS");
}

void initialize_grid(int grid[R][C])
{
    for(unsigned int i = 1; i < R - 1; ++i)
    {
        for(unsigned int j = 1; j < C - 1; grid[i][j++] = 1);
    }
}

void print_and_apply_border(int grid [R][C], const int through)
{
    printf("%c", through ? UL_THROUGH : UL);
    for(unsigned int i = 0; i < C - 2; ++i)
    {
        printf("%c", through ? HOR_THROUGH : HOR);
    }
    printf("%c\n", through ? UR_THROUGH : UR);
    for(unsigned int i = 0; i < R - 2; ++i)
    {
        printf("%c", through ? VER_THROUGH : VER);
        set_position((COORD){C - 1, i + 1});
        printf("%c\n", through ? VER_THROUGH : VER);
    }
    printf("%c", through ? DL_THROUGH : DL);
    for(unsigned int i = 0; i < C - 2; ++i)
    {
        printf("%c", through ? HOR_THROUGH : HOR);
    }
    printf("%c\n", through ? DR_THROUGH : DR);
    if(!through)
    {
        for(unsigned int j = 0; j < C; ++j)
        {
            grid[0][j] = grid[R - 1][j] = 0;
        }
        for(unsigned int i = 1; i < R - 1; ++i)
        {
            grid[i][0] = grid[i][C - 1] = 0;
        }
    }
}

void print_and_apply_snake(int grid[R][C], const COORD *snake)
{
    for(unsigned int i = 0; i < START_LENGTH; ++i)
    {
        set_position(snake[i]);
        printf("%c", BLOCK);
        grid[snake[i].Y][snake[i].X] = 0;
    }
}

void print_and_apply_obstacle(int grid[R][C])
{
    for(unsigned int i = 0; i < DIM_OBSTACLE; ++i)
    {
        set_position(coord_obstacle[i]);
        printf("%c", char_obstacle[i]);
        grid[coord_obstacle[i].Y][coord_obstacle[i].X] = 0;
    }
}

void print_score(const unsigned int score)
{
    set_position((COORD){C + 3, 1});
    printf("SCORE: %u", score);
}

void generate_bite(int grid[R][C], const unsigned int score, const int obstacle)
{
    unsigned int r = rand() % (DIM - score / 10 - START_LENGTH - obstacle * DIM_OBSTACLE) + 1;
    for(unsigned int i = 1; i < R - 1; ++i)
    {
        for(unsigned int j = 1; j < C - 1; ++j)
        {
            if(!(r -= grid[i][j] == 1))
            {
                grid[i][j] = 2;
                set_position((COORD){j, i});
                printf("%c", BITE);
                return;
            }
        }
    }
}

void define_direction(int *direction, const int input)
{
    if((*direction == UP || *direction == DOWN) && (input == LEFT || input == RIGHT) || (*direction == LEFT || *direction == RIGHT) && (input == UP || input == DOWN))
    {
        *direction = input;
    }
}

int snake_update(int grid[R][C], COORD *snake, COORD **tail, COORD **head, unsigned int *score, const int obstacle, const int direction, const int through)
{
    COORD temp = **head;
    *head = *head == snake + DIM - 1 ? snake : *head + 1;
    **head = temp;
    switch(direction)
    {
        case UP:    (*head)->Y = through && (*head)->Y == 1 ? R - 2 : (*head)->Y - 1; break;
        case DOWN:  (*head)->Y = through && (*head)->Y == R - 2 ? 1 : (*head)->Y + 1; break;
        case LEFT:  (*head)->X = through && (*head)->X == 1 ? C - 2 : (*head)->X - 1; break;
        case RIGHT: (*head)->X = through && (*head)->X == C - 2 ? 1 : (*head)->X + 1;
    }
    if(grid[(*head)->Y][(*head)->X])
    {
        if(grid[(*head)->Y][(*head)->X] == 1)
        {
            grid[(*tail)->Y][(*tail)->X] = 1;
            set_position(**tail);
            printf(" ");
            *tail = *tail == snake + DIM - 1 ? snake : *tail + 1;
        }
        else
        {
            *score += 10;
            printf("%c", SOUND);
            print_score(*score);
            generate_bite(grid, *score, obstacle);
        }
        grid[(*head)->Y][(*head)->X] = 0;
        set_position(**head);
        printf("%c", BLOCK);
        return 1;
    }
    return 0;
}

int main()
{
    srand(time(0));
    int grid[R][C];
    int through;
    int obstacle;
    int direction = RIGHT;
    unsigned int score = 0;
    COORD snake[DIM] = {{2, R / 4}, {3, R / 4}, {4, R / 4}};
    COORD *tail = snake;
    COORD *head = snake + START_LENGTH - 1;
    choose_settings(&through, &obstacle);
    initialize_grid(grid);
    print_and_apply_border(grid, through);
    print_and_apply_snake(grid, snake);
    print_score(score);
    if(obstacle)
    {
        print_and_apply_obstacle(grid);
    }
    generate_bite(grid, score, obstacle);
    set_position((COORD){0, R + 1});
    printf("Press any key to start.\n");
    define_direction(&direction, get_input(direction));
    set_position((COORD){0, R + 1});
    printf("                       ");
    do
    {
        delay(220);
        if(kbhit())
        {
            define_direction(&direction, get_input(direction));
        }
    }
    while(snake_update(grid, snake, &tail, &head, &score, obstacle, direction, through));
    set_position((COORD){0, R + 1});
    printf("GAME OVER!\n");
    return 0;
}

Anche nel mio caso si tratta di un codice di parecchi anni fa, mi sono ripromesso più volte di aggiornarlo e ampliarlo, ma non l'ho mai fatto! :asd:


P.S.
I thread sono un argomento che mi manca, ma sono anche loro nella lista dei buoni propositi!
 
  • Mi piace
Reazioni: DispatchCode
Grazie @Super Squirrel
Mi sa che lo guarderei domani da pc, da smartphone mi trovo scomodo.

Due parole su Github: se non lo conosci, vale la pena che guardi "git". Git ti permette di gestire un progetto e di tenere traccia di tutte le modifiche. Ogni volta che modifichi qualcosa o aggiungi qualcosa (o la togli), puoi creare quella che puoi vedere come un'immagine (un nodo, in realtà) dei file, chiamato "commit".
Se collabori, git si occupa pure del merge automatico delle modifiche delle varie persone che collaborano, e se non riesce... te li smazzi a mano.
Github è in parole povere un host per repository git (ne esistono altri, tipo Bitbucket e Gitlab).


Mi stavo infatti chiedendo come gestissi la generazione del cibo ad esempio, se con thread o no.
 
Grazie @Super Squirrel
Mi sa che lo guarderei domani da pc, da smartphone mi trovo scomodo.
Di niente

Mi stavo infatti chiedendo come gestissi la generazione del cibo ad esempio, se con thread o no.
Nel caso dello snake si riesce ad abbozzare abbastanza agevolmente qualcosa di decente facendo a meno dei thread, ma nell'implementazione del tetris ho dovuto utilizzare un po' di fantasia per farne a meno! 😅