Go Down

Topic: Snake (Read 178 times) previous topic - next topic

greg06


Bonjour a tous,

J'ai découvert l'Arduino, l'électronique et le code, il y a moins d'un ans.

Je viens de réaliser le jeu Snake sur une matrice de led 8x8 trois couleurs (Adafruit).

Tout marche nickel.


N'ayant aucune expérience, j'aimerai avoir l'avis d'expert pour mon code, clarté, simplicité, bonne pratique etc...  il y aurait il des volontaires ;)

N'hésitez pas, je cherche a progresser et a faire le mieux possible.

J'aimerai enchainer sur un casse brique et j'aimerai savoir si je suis sur la bonne voie.


MERCI


Code: [Select]
//***************************** LIBRARIES ***
#include <Wire.h>
#include "Adafruit_LEDBackpack.h"
#include "Adafruit_GFX.h"

//****************************** VARIALBE ***
Adafruit_BicolorMatrix matrix = Adafruit_BicolorMatrix();

const int switchPinTurnLeft=8;
const int switchPinTurnRight=5;
int switchValueTurnLeft=0;
int switchValueTurnRight=0;

int headPositionX=1;
int headPositionY=1;
int snakeDirection=0;

int tailPositionMemoryX[40];
int tailPositionMemoryY[40];
int tailLength=0;

int beaconPositionX=0;
int beaconPositionY=0;

int flagBeaconOnSnake=0;
int flagOneTimePerStep=0;

int gameSpeed=1000;
unsigned long nextStepTime=0;

//**************************** VOID SETUP ***
void setup(){

  Serial.begin(9600);

  matrix.begin(0x70);

  pinMode(switchPinTurnLeft, INPUT);
  pinMode(switchPinTurnRight, INPUT);

  randomSeed(analogRead(0));

  beaconNewPosition();
}

//***************************** VOID LOOP ***
void loop(){

  if (nextStepTime<=millis()){

    nextStepTime=nextStepTime+gameSpeed;
    flagOneTimePerStep=0;

    snakeDisplacement();
    checkSnakeItSelf();
    snakeDisplay();
    checkEatBeacon();
  }

  checkButtons();
}

//******************************* DISPLAY ***
void snakeDisplay(){

  matrix.clear();

  //HEAD
  matrix.drawPixel(headPositionX, headPositionY, LED_GREEN);

  //TAIL
  for (int i=2; i<=tailLength+1 ;i++){
    matrix.drawPixel(tailPositionMemoryX[i], tailPositionMemoryY[i], LED_GREEN);
  }

  //BEACON
  matrix.drawPixel(beaconPositionX, beaconPositionY, LED_YELLOW);

  matrix.writeDisplay();
}

//************************* CHECK BUTTONS ***
void checkButtons(){

  switchValueTurnLeft=digitalRead(switchPinTurnLeft);
  switchValueTurnRight=digitalRead(switchPinTurnRight);

  if (flagOneTimePerStep==0){

    if (switchValueTurnLeft==1){
      flagOneTimePerStep=1;
      snakeDirection=snakeDirection-1;
    }

    else if (switchValueTurnRight==1){
      flagOneTimePerStep=1;
      snakeDirection=snakeDirection+1;
    }

    delay(100);
  }
}

//********************** CHECK EAT BEACON ***
void checkEatBeacon(){

  if (headPositionX==beaconPositionX && headPositionY==beaconPositionY){
    tailLength=tailLength+1;
    beaconNewPosition();

    if (gameSpeed>=280){
      //gameSpeed=gameSpeed-80;
    }
  }
}

//******************** SNAKE DISPLACEMENT ***
void snakeDisplacement(){

  if (snakeDirection==4){
    snakeDirection=0;
  }
  if (snakeDirection==-1){
    snakeDirection=3;
  }

  //HEAD DISPLACEMENT
  switch (snakeDirection){
  case 0:
    headPositionX++;
    break;
  case 1:
    headPositionY++;
    break;
  case 2:
    headPositionX--;
    break;
  case 3:
    headPositionY--;
  }

  checkBorderLimit();

  //TAIL DISPLACEMENT
  for (int i=tailLength+1; i>1; i--){
    tailPositionMemoryX[i]=tailPositionMemoryX[i-1];
    tailPositionMemoryY[i]=tailPositionMemoryY[i-1];
  }

  tailPositionMemoryX[1]=headPositionX;
  tailPositionMemoryY[1]=headPositionY;
}

//******************** CHECK BORDER LIMIT ***
void checkBorderLimit(){

  //CHECK LEFT BORDER
  if (headPositionX==8){
    headPositionX=7;
    if (headPositionY==7){
      headPositionY--;
      snakeDirection=3;
    }
    else {
      headPositionY++;
      snakeDirection=1;
    }
  }

  //CHECK RIGHT BORDER
  else if (headPositionX==-1){
    headPositionX=0;
    if (headPositionY==0){
      headPositionY++;
      snakeDirection=1;
    }
    else {
      headPositionY--;
      snakeDirection=3;
    }
  }

  //CHECK TOP BORDER
  else if (headPositionY==8){
    headPositionY=7;
    if (headPositionX==0){
      headPositionX++;
      snakeDirection=0;
    }
    else {
      headPositionX--;
      snakeDirection=2;
    }
  }

  //CHECK BOTTOM BORDER
  else if (headPositionY==-1){
    headPositionY=0;
    if (headPositionX==7){
      headPositionX--;
      snakeDirection=2;
    }
    else {
      headPositionX++;
      snakeDirection=0;
    }
  }
}

//************************** SNAKE ITSELF ***
void checkSnakeItSelf(){

  for (int i=2; i<tailLength+2; i++){
    if (headPositionX==tailPositionMemoryX[i] && headPositionY==tailPositionMemoryY[i]){
      gameOver();
    }
  }
}

//******************* BEACON NEW POSITION ***
void beaconNewPosition(){

  //CHECK BEACON NEW POSITION NOT ON SNAKE
  do {
    flagBeaconOnSnake=0;

    beaconPositionX=random(0,8);
    beaconPositionY=random(0,8);

    for (int i=2; i<tailLength+2; i++){
      if (beaconPositionX==tailPositionMemoryX[i] && beaconPositionY==tailPositionMemoryY[i] || beaconPositionX==headPositionX && beaconPositionY==headPositionY){
        flagBeaconOnSnake=1;
      }
    }
  }
  while (flagBeaconOnSnake==1);
}

//***************************** GAME OVER ***
void gameOver(){

  delay(1200);

  for (int i=0; i<4; i++){
    matrix.drawRect(0+i,0+i, 8-(i*2),8-(i*2), LED_RED);
    matrix.writeDisplay();
    delay(800);
  }
  delay(1500);

  matrix.setTextWrap(false);
  matrix.setTextSize(1);
  matrix.setTextColor(LED_GREEN);
  matrix.setRotation(1);

  for (int iii=1; iii<3; iii++){
    for (int ii=1; ii<3; ii++){
      for (int i=7; i>=-30; i--) {
        matrix.clear();
        matrix.setCursor(i,0);
        switch (ii){
        case 1:
          matrix.print("Score");
          break;
        case 2 :
          matrix.print(tailLength);
        }
        matrix.writeDisplay();
        delay(130);
      }
    }
  }

  delay(10000);

  //RAZ NEW GAME
  tailLength=0;
  headPositionX=1;
  headPositionY=1;
  gameSpeed=1000;
  nextStepTime=0;
  matrix.clear();
  matrix.writeDisplay();
  beaconNewPosition();
}

greg06

Ça apporte pas grand chose, mais c'est toujours mieux avec une photo ;)

J-M-L

#2
May 20, 2017, 10:25 am Last Edit: May 20, 2017, 10:32 am by J-M-L
Bonjour et bravo pour le code et le projet

Quelques commentaires qui se veulent constructifs :

- Vous pourriez utiliser des const byte au lieu des int pour vos N° de pin et sans doute des byte pour tout ce qui est lié à la mémoire de votre snake puisque votre matrice a bien moins de 256 valeurs.  (attention un byte ne pourra pas être négatif dans les calculs - éventuellement un int_8 pourra prendre la valeur -1 (entre -128 et 127))

Vous optimiserez l'usage mémoire et la rapidité du code (processeur 8 bits) puisqu'il s'agira de copier qu'un seul octet au lieu de deux à chaque fois que votre serpent bouge

- ce code ne sert à rien
Code: [Select]
    if (gameSpeed>=280){
      //gameSpeed=gameSpeed-80;
    }
autant le virer

- pour des raisons de clarté de lecture du code, comme digitalRead retourne HIGH ou LOW, vous feriez mieux de stocker les valeurs dans un byte plutôt qu'un int et dans les test ne pas comparer avec 0 ou 1 mais avec HIGH et LOW. (Vous pourriez directement mettre des booléens avec un == HIGH après le digitalRead)

- vous ne gérez pas le rebond possible des boutons de commande

- au lieu de décaler les éléments du tableau pour la gestion du déplacement de la queue vous pourriez utiliser la notion de buffer circulaire même si c'est un peu plus compliqué à écrire

- les valeurs magiques 40 devraient être des #define ou des const byte plutôt qu'être en clair dans le texte.

- les valeurs pour snakeDirection seraient plus lisibles si vous les déclariez dans un enum
Code: [Select]
enum direction_t { DROITE, HAUT, GAUCHE, BAS } snakeDirection;ça simplifierait la lecture du code ensuite par exemple
Code: [Select]
//HEAD DISPLACEMENT
  switch (snakeDirection){
  case 0:
    headPositionX++;
    break;
  case 1:
    headPositionY++;
    break;
  case 2:...
deviendrait
Code: [Select]
//HEAD DISPLACEMENT
  switch (snakeDirection){
  case DROITE:
    headPositionX++;
    break;
  case HAUT:
    headPositionY++;
    break;
  case GAUCHE:...
Attention cependant à vos opérations mathématiques sur ces valeurs et au -1. faudra réfléchir un peu :)


- vous ne testez pas le dépassement de taille des tableaux - que se passera-t-il si je joue très bien et j'ai un serpent de plus de 40 cases de long ?

- votre gestion de la vitesse n'est pas idéale et contient un bug (pas trop grave dans votre cas)
Code: [Select]
  if (nextStepTime<=millis()){
    nextStepTime=nextStepTime+gameSpeed;
...
il faudrait jouer pendant 50 jours pour le voir mais vous allez mal gérer le retour à zero du compteur (nextStepTime étant un unsigned long, en faisant l'addition avec gameSpeed à un moment vous allez retourner à proche de zéro et donc nextStepTime sera plus petit que millis() (qui lui sera toujours vers 50 jours - durée max en millisecondes représentable sur un unsigned long de 4 octets ) et déclenchera l'action immédiatement au lieu d'attendre et votre jeu va partir en vitesse max). c'est pour cela qu'on procède toujours pas soustraction quand on gère le temps avec millis()
Code: [Select]
if (millis()-previousStepTime >= gameSpeed) {previousStepTime+= gameSpeed; ...}


- ces deux variables
Code: [Select]
int flagBeaconOnSnake=0;
int flagOneTimePerStep=0;
sont des drapeaux et donc devraient être des booléens que vous mettrez à true ou false


- ce code semble indiquer
Code: [Select]
  pinMode(switchPinTurnLeft, INPUT);
  pinMode(switchPinTurnRight, INPUT);
que vous avez câblé vos boutons avec des résistances de pull down sur votre breadboard (que l'on voit sur la photo). Vous pourriez faire l'économie des résistances en utilisant un mode INPUT_PULLUP sur vos boutons et en les connectant à 5V au lieu de GND. ça changera vos test pour détecter l'appui, faudra comparer à LOW au lieu de HIGH.



voilà - rien de majeur mais quelques idées d'améliorations

Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums
Pas de messages privés SVP

greg06

AU TOP !
MERCI pour ces commentaires je vais regarder ça en détail.

greg06

Salut,

Les points auxquelles je ne répond pas, c'est que j'ai bien compris. Je connaissais pas ENUM et le buffer, c'est génial et encore merci pour tout.

C'est au top Arduino.

Quote
- ce code ne sert à rien
Code: [Select]

    if (gameSpeed>=280){
      //gameSpeed=gameSpeed-80;
    }

autant le virer
J'ai oublié de virer le //, cette partie sert a accélérer le jeu après chaque balise mangé.

Quote
- pour des raisons de clarté de lecture du code, comme digitalRead retourne HIGH ou LOW, vous feriez mieux de stocker les valeurs dans un byte plutôt qu'un int et dans les test ne pas comparer avec 0 ou 1 mais avec HIGH et LOW. (Vous pourriez directement mettre des booléens avec un == HIGH après le digitalRead)
Depuis quelques temps sans trop savoir pourquoi j'ai remplacé tout les HIGH & LOW par 1 & 0, je vois pas différence, peux tu m'expliquer.
J'ai pas compris cette partie "Vous pourriez directement mettre des booléens avec un == HIGH après le digitalRead", ca veut dire que les variable switchValue ne doivent etre ni int & byte mais booléen ?

Quote
- vous ne gérez pas le rebond possible des boutons de commande
La variable flagOneTimePerStep ne fait pas cette fonction ?

Quote
- ce code semble indiquer
Code: [Select]

  pinMode(switchPinTurnLeft, INPUT);
  pinMode(switchPinTurnRight, INPUT);

que vous avez câblé vos boutons avec des résistances de pull down sur votre breadboard (que l'on voit sur la photo). Vous pourriez faire l'économie des résistances en utilisant un mode INPUT_PULLUP sur vos boutons et en les connectant à 5V au lieu de GND. ça changera vos test pour détecter l'appui, faudra comparer à LOW au lieu de HIGH.
Pour le mode INPUT_PULLUP le bouton doit etre connecté a la pin Arduino et au GND ou au 5V ?


J-M-L

Quote
Depuis quelques temps sans trop savoir pourquoi j'ai remplacé tout les HIGH & LOW par 1 & 0, je vois pas différence, peux tu m'expliquer.
ça ne change rien si ce n'est la lisibilité du code. en effet HIGH est défini comme valant 1 et LOW comme valant 0... mais si d'aventure un jour dans un moment de folie les développeurs venaient à changer ces valeurs - ce que retourne digitalRead serait toujours HIGH et LOW et ceux qui ont codé avec ces constantes auront un code fonctionnel, ceux qui ont codé avec 0 et 1 ...

Quote
Vous pourriez directement mettre des booléens avec un == HIGH après le digitalRead", ca veut dire que les variable switchValue ne doivent etre ni int & byte mais booléen ?
Comme ce qui vous intéresse c'est de savoir si votre bouton est appuyé, vous voulez une variable logique - vrai ou faux.

vous pourriez effectivement avoir un booléen boolean switchValueTurnLeftAppuye;

et directement affecter la valeur de vérité dans le booléen
switchValueTurnLeftAppuye = (digitalRead(switchPinTurnLeft) == HIGH);


Quote
Pour le mode INPUT_PULLUP le bouton doit etre connecté a la pin Arduino et au GND ou au 5V ?
D'un côté à la pin mise en mode INPUT_PULLUP et de l'autre côté à GND. tant que le bouton n'est pas appuyé, vous voyez HIGH et quand vous appuyez vous verrez LOW (et faites l'économie de la résistance puisque vous en utilisez une qui est intégrée directement dans le micro-processeur)
Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums
Pas de messages privés SVP

greg06

Encore une petite question, la variable switchValueTurnLeft a effectivement que deux etats.
Donc l'adresser en Boolean est judicieux.
Mais le boolean c'est TRUE ou FALSE et pas HIGH ou LOW.

Ce ne pose pas de problème a l'IDE de donner du HIGH et LOW a un boolean, il comprend, mais ca me parait bizarre et terme de bonne pratique, je me trompe ?

Code: [Select]
const byte switchPinTurnLeft=5;
boolean switchValueTurnLeft=HIGH;

void setup(){
  pinMode(switchPinTurnLeft, INPUT_PULLUP);
}

void loop(){
  switchValueTurnLeft=digitalRead(switchPinTurnLeft);
  if (switchValueTurnLeft==LOW){
    action();
  }
}




J-M-L

#7
May 23, 2017, 11:45 am Last Edit: May 23, 2017, 12:04 pm by J-M-L
Encore une petite question, la variable switchValueTurnLeft a effectivement que deux etats.
Donc l'adresser en Boolean est judicieux.
Mais le boolean c'est TRUE ou FALSE et pas HIGH ou LOW.

Ce ne pose pas de problème a l'IDE de donner du HIGH et LOW a un boolean, il comprend, mais ca me parait bizarre et terme de bonne pratique, je me trompe ?
si vous écrivez comme proposé (ajusté pour me pull-up)
Code: [Select]
switchValueTurnLeftAppuye = (digitalRead(switchPinTurnLeft) == LOW); // Logique inversée en INPUT_PULLUP

alors vous lisez bien HIGH ou LOW, puis vous comparez avec LOW et donc ce qui va dans votre variable c'est le résultat d'un test logique, qui retourne bien true ou false. donc c'est cohérent

Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums
Pas de messages privés SVP

greg06

Merci encore pour tes retours, mais j'arrive pas a saisir la différence entre ces deux expressions :

Code: [Select]
switchValueTrunLeft = (digitalRead(switchPinTurnLeft)==LOW);

switchValueTrunLeft = digitalRead(switchPinTurnLeft);



kamill

#9
May 23, 2017, 01:38 pm Last Edit: May 23, 2017, 01:39 pm by kamill
Merci encore pour tes retours, mais j'arrive pas a saisir la différence entre ces deux expressions :
Code: [Select]
switchValueTrunLeft = (digitalRead(switchPinTurnLeft)==LOW);
switchValueTrunLeft = digitalRead(switchPinTurnLeft);

Bonjour,

Chacune donne un résultat inverse de l'autre

J-M-L

#10
May 23, 2017, 02:09 pm Last Edit: May 23, 2017, 02:12 pm by J-M-L
Alors décomposons



switchValueTrunLeft =  (digitalRead(switchPinTurnLeft)==LOW);

est une expression d'affectation on dit à la variable switchValueTrunLeft de prendre une valeur qui résulte de l'évaluation de l'expression se trouvant à droite du signe =

 Que fait donc cette expression ? c'est une expression qui comporte un test (le ==) donc l'expression va comparer 2 valeurs et retourner une valeur de vérité, vrai ou faux. pour cela l'arduino va évaluer (calculer) la partie gauche de l'expression, puis la partie droite et enfin les comparer. Donc

1/ le compilateur appelle digitalRead(switchPinTurnLeft) qui retourne une valeur, HIGH ou LOW, en fonction de l'état de la pin switchPinTurnLeft

2/ Puis le compilateur génère du code qui évalue la partie droite, qui est toute simple puisque c'est un littéral il n'y a rien à calculer LOW vaut LOW :)

3/ le compilateur génère du code pour faire la comparaison entre la première valeur et la seconde. Ce test généré une réponse vrai ou faux.

4/ le compilateur génère du code qui copie cette valeur de vérité dans la variable booléenne switchValueTrunLeft

Au final on a donc vrai ou faux dans la variable en fonction de si votre pin était LOW ou pas. (vrai si LOW, faux sinon).




Dans l'autre cas switchValueTrunLeft = digitalRead(switchPinTurnLeft);

on a aussi une affectation, la partie gauche est une variable et la partie droite une expression simple d'appel de fonction. on va donc demander au compilateur de stocker dans la case mémoire de la partie gauche le résultat de l'appel à la fonction, éventuellement "promu" (converti) automatiquement par le compilateur dans un format compatible avec le type de la variable de gauche.

Si la variable à gauche est un booléen, on va essayer de mettre dedans digitalRead(switchPinTurnLeft) qui est un entier. La loi sur les conversion automatique en C/C++ dit qu'un entier nul est converti en faux (false) et toute autre valeur en vrai (true).

Comme HIGH c'est 1, si l'appel a retourné HIGH - ce sera converti en vrai
Comme LOW c'est 0, si l'appel a retourné LOW - ce sera converti en faux


donc au final l'expression d'affectation va mettre  vrai ou faux dans la variable en fonction de si votre pin était HIGH ou pas. (vrai si HIGH, faux sinon).

--> comme le dit kamill donc c'est bien l'opposé.

c'est clair ?

(Et c'est Turn pas Trun qu'il faut écrire :) )
Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums
Pas de messages privés SVP

greg06


c'est claire,..., MAIS  :D

Pourquoi utiliser cette expression :
switchValueTrunLeft =  (digitalRead(switchPinTurnLeft)==LOW);

Plutôt que l'autre:
switchValueTrunLeft = digitalRead(switchPinTurnLeft);

J'ai bien compris que dans un cas identique ( Pin = HIGH ), je vais recevoir un LOW avec la première expression et un HIGH avec la deuxième.

J'ai donc juste a changer ma condition, au lieu d'un if(variable==HIGH) j'aurai un if(variable==LOW), ou l'inverse je sais plus a force ;D. Bref ce que je veux dire c'est que je comprend pas pourquoi tu me conseils plutôt la première expression.


Quote
(Et c'est Turn pas Trun qu'il faut écrire :) )
Trun ? dans mon cas c'est bien turn : switchValueTurnLeft: variable du switch pour la rotation a gauche !

J-M-L

#12
May 23, 2017, 05:25 pm Last Edit: May 23, 2017, 05:26 pm by J-M-L
Pour Trun j'ai copié depuis le post #8

la raison c'est simplement parce que mentalement on se dit souvent que bouton appuyé ça doit être vrai quand le bouton est appuyé, mais si vous utilisez un input-pullup alors la logique est inversée, le bouton devient LOW quand il est actionné et LOW se transforme en false... donc après on comprend plus rien.

en prenant un booléen avec un nom bien choisi ça facile aussi la lecture

Code: [Select]
if (buttonIsPressed) {...} ça se comprend aisément, plus que si on fait

Code: [Select]
if (valeurDuBouton == LOW) {...} parce que là on se se souvient plus si on a le pull-up ou pas...

 Mais bon Chacun fait comme il veut ! :)

Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums
Pas de messages privés SVP

kamill

+1, avec ma petite tête j'ai du mal a raisonner en logique négative.

greg06

Au top j'ai tout compris.

Grand MERCI


Quote
Mais bon Chacun fait comme il veut ! :)
Bien sur mais ça m'aide beaucoup d'avoir des retours d'experts. Même sur de la typo je suis preneur.

Les conseils sur le millis(), enum, les types de données, etc... c'est au TOP.


Des que j'ai fini d'intégrer toutes vos remarques, je passe au casse brique et a tetris (toujours sur matrice de led). Je vous les soumettrais, si il vous reste un peu de courage  ;)


Go Up