Total-Informatique

Le blog de sablier94

Projet Snakeduino

août 6th, 2012

Aujourd’hui, je vous présente mon premier gros projet sur Arduino; Snakeduino. C’est le jeu du serpent (snake) sur une matrice de 8×8 leds RG contrôlée avec une manette de wii (nunchuck). Le score est affiché sur un écran LCD et on peut choisir de jouer avec l’accéléromètre ou le joystick en appuyant sur un bouton. Le meilleur score est sauvegardé sur l’arduino (premier octet).

Une petite photo pour voir ce que ça donne :
Photo du projet Snakeduino

Instructions de jeu :
Le serpent est la série de points (2 au départ) orange qui bougent automatiquement et que vous devez diriger avec votre manette de wii.
Le point vert est la pomme à manger (passer dessus avec la tête tête du serpent) et le point rouge est la pierre qu’il ne faut pas toucher.
Si vous vous mangez vous même, vous mourrez. Si vous passez sur la pierre également. A chaque fois que vous mangez une pomme, vous grandissez d’un pixel et vous gagnez un point.
Sur l’écran LCD est affiché votre score sur la première ligne et le record à battre sur la 2ème ligne. Si vous battez le meilleur score la tête de mort à la fin du jeu sera verte, sinon elle sera rouge.
Vous pouvez passer d’un bord de la matrice (de 8×8 leds) à l’autre sans mourir. A chaque fois que vous mangez une pomme, la pierre et la pomme réapparaissent ailleurs.
Pour jouer avec l’accéléromètre, il faut appuyer sur le bouton sur la breadboard au début du jeu ou quand on est mort et que l’écran est noir.
Si la led jaune sur la breadboard est allumée, cela veut dire que le snake se dirige avec l’accéléromètre, sinon c’est avec le joystick.

Le matériel dont vous aurez besoin :
Une nunchuck (manette de Wii) connectée l’adaptateur WiiChuck : http://www.watterott.com/de/Leiterplatten/WiiChuck-Adapter
Un écran LCD 16×2 jaune (DEM16216SYH-LY) : http://www.watterott.com/de/16×2-LCD-gelb-DEM16216SYH-LY
Une matrice 8×8 leds RG avec liaison série : http://www.watterott.com/de/LED-Matrix-Serial-Interface-Red/Green
Un Fritzing Starter Kit avec un arduino uno R3 : http://www.watterott.com/de/Fritzing-Starter-Kit
Ce kit contient tout ce qu’il faut d’autre pour ce projet, comme par exemple l’arduino, la breadboard, la led, le bouton, les câbles et les résistances.

L’algorithme (pour faire bouger le serpent, pour qu’il s’agrandisse, pour utiliser l’accéléromètre de la manette, etc) était assez complexe, cela m’a pris beaucoup de temps. Le code (surtout le type des variables) est bien optimisé car à un moment je n’avais plus de place dans la mémoire de l’Arduino, mais maintenant ça fonctionne nickel.

Si vous voulez faire le montage chez vous, veuillez lire les premières lignes du code en commentaire.

Je vous fourni mon schéma avec Fritzing (vous pouvez le télécharger ici) :
Schéma platine d'essai
(Le câblage réel n’est pas très joli mais je voulais mettre le tout dans une boîte plus tard.)

Je voulais vous faire une petite vidéo pour montrer le jeu en fonctionnement mais on ne voit pas grand chose car les leds éblouissent la caméra. Dommage parce que vous verriez mieux que sur la photo.

Voilà le code final qui est entièrement commenté :


/*
Créé par : Sablier94
Date de dernière mise à jour : 22.06.12
Nom du jeu : Snakeduino
Nom du sketch : WiiSnake2D.ino
Ce sketch est sous license CC-BY-NC-SA
Le schéma fritzing est disponible sur le fichie "SchemaWiiSnake2D.fzz".
Matériel nécessaire :
Une nunchuck (manette de Wii) connectée l'adaptateur WiiChuck : http://www.watterott.com/de/Leiterplatten/WiiChuck-Adapter
Un écran LCD 16x2 jaune (DEM16216SYH-LY) : http://www.watterott.com/de/16x2-LCD-gelb-DEM16216SYH-LY
Une matrice 8x8 leds RG avec liaison série : http://www.watterott.com/de/LED-Matrix-Serial-Interface-Red/Green
Un Fritzing Starter Kit avec un arduino uno R3 : http://www.watterott.com/de/Fritzing-Starter-Kit
Ce kit contient tout ce qu'il faut d'autre pour ce projet, comme par exemple la breadboard, la led, le bouton, les câbles et les résistances.
Notes pour les développeurs :
Ce sketch a été créé avec l'IDE Arduino 1.0.1 et pourrait ne pas fonctionner sur des versions antérieures. Les librairies utilisées sont déjà dans l'IDE 1.0 par défaut.
Les positions X et Y sont inversées, il faut faire Y = Y - 1 pour monter et pareillement pour l'axe X. X = 0 pour tout à droite, Y = 0 pour tout en haut et 8 pour tout à gauche et tout en bas.
Il faut brancher la nunchuk sur l'adaptateur dans le bon sens (dans mon cas c'est avec le bout de plastique transparent dessus) pour ne pas faire de court-circuit.
La résistance pour l'écran LCD peut être modifiée par ajuster la luminosité (ou vous pouvez utiliser un potentiomètre).
Attention, ce programme va écrire le meilleur score dans le premier octet de la mémoire de l'eeprom de l'arduino. L'octet qui était à la position 0 sera donc effacé !
la limite d'écriture de l'eeprom de l'arduino étant limitée à 100'000 cycles, faîtes attention que la ligne "EEPROM.write(0, 0)" dans le setup est bien commentée à part pour remettre le score à zéro.
Le jeu fonctionne sans problème. Si le serpent devient vert un moment, c'est parce qu'il y a quelque chose qui sort de la taille tableau de la matrice, mais normalement tous ces bugs sont corrigées.
Instructions de jeu :
Le serpent est la série de points (2 au départ) orange qui bougent automatiquement et que vous devez diriger avec votre manette de wii.
Le point vert est la pomme à manger (passer dessus avec la tête tête du serpent) et le point rouge est la pierre qu'il ne faut pas toucher.
Si vous vous mangez vous même, vous mourrez. Si vous passez sur la pierre également. A chaque fois que vous mangez une pomme, vous grandissez d'un pixel et vous gagnez un point.
Sur l'écran LCD est affiché votre score sur la première ligne et le record à battre sur la 2ème ligne. Si vous battez le meilleur score la tête de mort à la fin du jeu sera verte, sinon elle sera rouge.
Vous pouvez passer d'un bord de la matrice (de 8x8 leds) à l'autre sans mourir. A chaque fois que vous mangez une pomme, la pierre et la pomme réapparaissent ailleurs.
Pour jouer avec l'accéléromètre, il faut appuyer sur le bouton sur la breadboard au début du jeu ou quand on est mort et que l'écran est noir.
Si la led jaune sur la breadboard est allumée, cela veut dire que le snake se dirige avec l'accéléromètre, sinon c'est avec le joystick.
*/

#include  //Pour utiliser la manette nunchuk
#include  //Pour utiliser l'écran LCD
#include  //Pour écrire le meilleur score dans la mémoire de l'arduino

/* Définition des constantes */
#define DATAOUT     9 //MOSI (input de la matrice)
// #define DATAIN      7 //MISO (output de la matrice) ==> Pas utilisé dans ce cas.
#define SPICLOCK    6 //SCK ((pour la synchronisation de la matrice))
#define SLAVESELECT 10 //SS ou SC

/* Couleur constantes */
#define BLACK 0 //Utilisé pour éteindre les leds
#define GREEN 1 //Utilisé pour les pommes
#define RED 2 //Utilisé pour la pierre
#define ORANGE 3 //Utilisé pour le serpent

const word intWaitTimeForPushButtonAgain = 200; //Temps de delai qu'on doit attendre pour appuyer une nouvelle fois sur le bouton sur la breadboard
const byte buttonBreadboardPin = 13; //Sur cette broche est branché le bouton qui permet de mettre le mode avec l'accéléromètre
const byte pinLedYellow = 8; //Sur ce pin est mis la led jaune qui insique si l'on joue avec ou sans l'accéléromètre
const byte bytColorSnake = ORANGE; //Couleur du serpent
const byte bytColorRock = RED; //Couleur des pierres
const byte bytColorApple = GREEN; //Couleur des pommes
const byte intSpeedSnake = 175; //Vitesse de déplacement du serpent (=taille du sleep). On ne peut pas dépasser 255 parce que c'est un byte

LiquidCrystal lcd(12, 11, 5, 4, 3, 2); //On initialise la librairie pour gérer l'écran LCD avec les numéros des pins utilisés

char* LastMove = "DOWN"; //Dernier mouvement effectué, donc il va partir vers le bas au début
boolean blnDeath; //Pour savoir si le serpent est mort
char out_buffer[64]; //Buffer de sortie pour la matrice de 64 leds

byte bytPointPositionAppleXOld; //Précédent point X de la pomme
byte bytPointPositionAppleYOld; //Précédent point Y de la pomme
byte bytPointPositionAppleX; //Position X de la pierre
byte bytPointPositionAppleY; //Position Y de la pierre
byte bytPointPositionRockX; //Position X de la pierre
byte bytPointPositionRockY; //Position X de la pierre
byte bytPointPositionRockXOld; //Précédent point X de la pierre
byte bytPointPositionRockYOld; //Précédent point Y de la pierre

byte bytAllPointsSnakeX[63]; //Contient toutes les cases de l'axe X du serpent
byte bytAllPointsSnakeY[63]; //Contient toutes les cases de l'axe Y du serpent

byte bytNumberOfCaseOfSnake; //Nombre totale de cases totale du seprent
long lngTime; //variable qui stocke la mesure du temps
boolean blnPlayWithAccel; //Pour savoir si on joue avec l'accéléromètre ou non

boolean blnMoved; //Pour savoir si le serpent a été déplacé par un mouvement de l'utilisateur
boolean blnRestartPutApple; //Pour savoir si on doit replacer la pomme
boolean blnRestartPutRock; //Pour savoir si on doit replacer la pierre
byte ValueForMoveMinus; //Valeur utilisée pour savoir quand on tourne ou non
byte ValueForMovePlus; //Valeur utilisée pour savoir quand on tourne ou non

byte bytBuffer[6]; //Buffer contenant les 6 précieux octets qui nous intéressent
byte bytCountBuffer = 0; //Index courant de buffer

byte joy_x_axis = bytBuffer[0]; //Joystick axe X (0-255)
byte joy_y_axis = bytBuffer[1]; //Joystick axe Y (0-255)
short accel_x_axis = bytBuffer[2] * 4; //Accéléromètre axe X
int accel_y_axis = bytBuffer[3] * 4; //Accéléromètre axe Y
int accel_z_axis = bytBuffer[4] * 4; //Accéléromètre axe Z
byte z_button = 0; //Bouton Z
byte c_button = 0; //Bouton C

/* Définition des images prédéfinies pouvant être affichées  */
boolean BACKGROUND[8][8] = //Tableau pour mettre toutes les leds de la même couleur
{
  {1,1,1,1,1,1,1,1},
  {1,1,1,1,1,1,1,1},
  {1,1,1,1,1,1,1,1},
  {1,1,1,1,1,1,1,1},
  {1,1,1,1,1,1,1,1},
  {1,1,1,1,1,1,1,1},
  {1,1,1,1,1,1,1,1},
  {1,1,1,1,1,1,1,1},
};

boolean TABSNAKE[8][8]; //Tableau qui contient le serpent qui sera affiché

boolean TABAPPLE[8][8]; //Tableau qui contient les pommes qui seront affichées

boolean TABROCK[8][8]; //Tableau qui contient les pierres qui seront affichées

/* Ecrit l'image sur le buffer de sortie. Cette fonction doit être précedé de la fonction fill_buffer(). */
void write_buffer()
{
  digitalWrite(SLAVESELECT, LOW); //Activation
  delay(5);
  for(byte i = 0; i < 64; i++) //Parcours toutes les leds
  {
    shiftOut(DATAOUT, SPICLOCK, MSBFIRST, out_buffer[i]); //Envoi des données pour contrôler la led
  }
  digitalWrite(SLAVESELECT, HIGH); //Désctivation
  delay(5);
}

/*
Rempli le buffer avec l'image (un tableau d'une dimension de données de 64 cases de 1 bits)
dans la couleur spécifiée. Les bits à 0 ne sont pas changé mais ceux qui sont à 1 peuvent
être ajoutés consécutivement. Un appel de fill_buffer() devrait précéder à write_buffer(); //Ecriture des tableaux sur la matrice de leds
Le tampon est rempli en premier pour éviter les erreurs de synchronisation impliqués dans la non-écriture séquentielle.
*/
void fill_buffer(boolean data[8][8], byte color)
{
   byte bytColor;
   for (byte i = 0; i < 8; i++) //Parcours de tout tableau
   {
     for (byte j = 0; j < 8; j++) //Parcours de tout le tableau      {        bytColor = data[7 - i][7 - j]; //Récupération de la couleur        if(bytColor) //S'il y a une couleur autre que noir :        {          out_buffer[i * 8 + j] = color % 4; //Ajout de la couleur dans les informations de la led        }      }    } } /* Met à jour et récrit tous les tableaux du jeu */ void RefreshAllTAB() {   fill_buffer(BACKGROUND, BLACK); //On affiche le fond en noir pour actualiser la disparition de la pierre qui vient d'être effacée   fill_buffer(TABSNAKE, bytColorSnake); //On affiche le tableau qui contient le serpent   fill_buffer(TABAPPLE, bytColorApple); //On affiche le tableau qui contient la pomme   fill_buffer(TABROCK, bytColorRock); //On affiche le tableau qui contient la pierre   write_buffer(); //Ecriture des tableaux sur la matrice de leds } void handshake() {   Wire.beginTransmission(0x52);      Wire.write(0x00);      Wire.endTransmission(); } /* Fonction utilisée dans la boucle infinie pour gérer les mouvements en fonction de la nunchuk */ void parseNunchuk() {   joy_x_axis = bytBuffer[0]; //Joystick axe X (0-255)   joy_y_axis = bytBuffer[1]; //Joystick axe Y (0-255)   accel_x_axis = bytBuffer[2] * 4; //Accéléromètre axe X   accel_y_axis = bytBuffer[3] * 4; //Accéléromètre axe Y   accel_z_axis = bytBuffer[4] * 4; //Accéléromètre axe Z   z_button = 0; //Bouton Z   c_button = 0; //Bouton C   //Mise à jour des variable sen fonctions de ce qu'on a reçu du buffer :   if((bytBuffer[5] >> 0) & 1)
    z_button = 1; //Bouton Z non appuyé

  if((bytBuffer[5] >> 1) & 1)
    c_button = 1; //Bouton C non appuyé

  if((bytBuffer[5] >> 2) & 1)
    accel_x_axis += 2; //Mise à jour des données de l'acéléromètre

  if((bytBuffer[5] >> 3) & 1)
    accel_x_axis += 1; //Mise à jour des données de l'acéléromètre

  if((bytBuffer[5] >> 4) & 1)
    accel_y_axis += 2; //Mise à jour des données de l'acéléromètre

  if((bytBuffer[5] >> 5) & 1)
    accel_y_axis += 1; //Mise à jour des données de l'acéléromètre

  if((bytBuffer[5] >> 6) & 1)
    accel_z_axis += 2; //Mise à jour des données de l'acéléromètre

  if((bytBuffer[5] >> 7) & 1)
    accel_z_axis += 1; //Mise à jour des données de l'acéléromètre

  if(blnPlayWithAccel == true)
  {
    joy_x_axis = map(accel_x_axis, 0, 1023, 0, 255); //Pour "remdimensionner" les valeurs reçues par l'accéléromètre comme si c'était le joystick
    joy_y_axis = map(accel_y_axis, 0, 1023, 0, 255); //Pour "remdimensionner" les valeurs reçues par l'accéléromètre comme si c'était le joystick
  }

  if(blnDeath == false) //On n'exécute les actions seulement si le serpent n'est pas mort
  {
    blnMoved = false; //On redefinit la variable pour dire que le serpent n'a pas encore bougé
    if(joy_x_axis = ValueForMovePlus) //Si l'axe x est dirigé vers la droite :
    {
      if(LastMove != "LEFT" && blnMoved != true) //Pour que le serpent ne puisse pas revenir directement en arrière, ni bouger 2 fois en un tour
      {
        MoveRight(); //On déplace le serpent dans la direction voulue
        LastMove = "RIGHT"; //On actualise l'information sur le déplacement
        blnMoved = true; //Le serpent a changé de direction
      }
    }
    if(joy_y_axis = ValueForMovePlus) //Si l'axe y est dirigé vers le haut :
    {
      if(LastMove != "DOWN" && blnMoved != true) //Pour que le serpent ne puisse pas revenir directement en arrière, ni bouger 2 fois en un tour
      {
        MoveUp(); //On déplace le serpent dans la direction voulue
        LastMove = "UP"; //On actualise l'information sur le déplacement
        blnMoved = true; //Le serpent a changé de direction
      }
    }

//    DisplayPositionSnakeHead(); //Pour le déboggage, vous pouvez décommenter cette ligne et voir où le serpent se situe dans la console

    if(!blnMoved) //Si le serpent n'a pas encore bougé :
    {
        if(LastMove == "UP") //On déplace le serpent dans la même direction qu'avant
        {
          MoveUp(); //On actualise l'information sur le déplacement
        }
        if(LastMove == "DOWN") //On déplace le serpent dans la même direction qu'avant
        {
          MoveDown(); //On actualise l'information sur le déplacement
        }
        if(LastMove == "LEFT") //On déplace le serpent dans la même direction qu'avant
        {
          MoveLeft(); //On actualise l'information sur le déplacement
        }
        if(LastMove == "RIGHT") //On déplace le serpent dans la même direction qu'avant
        {
          MoveRight(); //On actualise l'information sur le déplacement
        }
    }

    for(byte i = 1; i < bytNumberOfCaseOfSnake-1; i++) //Parcours de toutes les cases du serpent     {       if(bytAllPointsSnakeX[0] == bytAllPointsSnakeX[i] && bytAllPointsSnakeY[0] == bytAllPointsSnakeY[i]) //Si la tête a touché une autre partie du corps :       {         GameOver(); //Fin de la partie, le serpent s'est mangé       }     }         if(bytNumberOfCaseOfSnake >= 9) //Si le serpent a 9 cases (pour corriger un bug qui fait que quand il se mange en ligne droite à 9, on ne voit que la tête) :
    {
      byte XIsItAllTheSAmeValue = bytAllPointsSnakeX[0]; //On prend la tête comme référence pour vérifier que tous les points ne sont pas sur la même ligne
      byte YIsItAllTheSAmeValue = bytAllPointsSnakeY[0]; //On prend la tête comme référence pour vérifier que tous les points ne sont pas sur la même ligne
      boolean AllIsInALigneXForNow = true; //Par défaut, tous les points sont sur la même ligne
      boolean AllIsInALigneYForNow = true; //Par défaut, tous les points sont sur la même ligne
      for(byte i = 1; i < bytNumberOfCaseOfSnake-1; i++) //Parcours de toutes les cases du serpent
      {
        if(bytAllPointsSnakeX[i] != XIsItAllTheSAmeValue) //Si un point est différent :
        {
          AllIsInALigneXForNow = false; //Le serpent n'est pas tout sur la même ligne
        }
        if(bytAllPointsSnakeY[i] != YIsItAllTheSAmeValue) //Si un point est différent :
        {
          AllIsInALigneYForNow = false; //Le serpent n'est pas tout sur la même ligne
        }
      }
      if(AllIsInALigneXForNow) //Si tous les points ne sont pas sur la même ligne
      {
        GameOver(); //On a perdu
      }
      if(AllIsInALigneYForNow) //Si tous les points ne sont pas sur la même ligne
      {
        GameOver(); //On a perdu
      }
    }

    if(bytAllPointsSnakeX[0] == bytPointPositionRockX && bytAllPointsSnakeY[0] == bytPointPositionRockY) //Si la tête du serpent est sur la pierre :
    {
      GameOver(); //Fin de la partie, on s'est pris une pierre dans la tête #Aie
    }

    if(bytAllPointsSnakeX[0] == bytPointPositionAppleX && bytAllPointsSnakeY[0] == bytPointPositionAppleY) //Si la tête du serpent est sur la pomme :
    {
      TABAPPLE[bytPointPositionAppleX][bytPointPositionAppleY] = 0; //On supprime la pomme du tableau
      TABROCK[bytPointPositionRockX][bytPointPositionRockY] = 0; //On supprime la pierre du tableau

      do //Placement de la pomme sur la matrice :
      {
        blnRestartPutApple = false; //Par défaut, la pomme est placée correctement
        bytPointPositionAppleX = random(0, 8); //On trouve une nouvelle position X aléatoire pour la pomme
        bytPointPositionAppleY = random(0, 8); //On trouve une nouvelle position Y aléatoire pour la pomme
        for(byte i = 1; i < bytNumberOfCaseOfSnake-1; i++) //On passe sur toutes les cases
        {
          if(bytPointPositionAppleX == bytAllPointsSnakeX[i] && bytPointPositionAppleY == bytAllPointsSnakeY[i]) //On regarde si la pomme est sur le point :
          {
            blnRestartPutApple = true; //On doit replacer la pomme
          }
        }
        if(bytPointPositionAppleX == bytAllPointsSnakeX[0] && bytPointPositionAppleY == bytAllPointsSnakeY[0]) //On regarde si la pomme est sur le point :
        {
          blnRestartPutRock = true; //On doit replacer la pierre
        }
      } while(blnRestartPutApple); //On recommance tant que la pomme est sur le serpent

      do //Placement de la pierre sur la matrice :
      {
        blnRestartPutRock = false; //Par défaut, la pierre est placée correctement
        bytPointPositionRockX = random(0, 8); //On trouve une nouvelle position X aléatoire pour la pierre
        bytPointPositionRockY = random(0, 8); //On trouve une nouvelle position Y aléatoire pour la pierre
        for(byte i = 1; i < bytNumberOfCaseOfSnake-1; i++) //On passe sur toutes les cases         {           if(bytPointPositionRockX == bytAllPointsSnakeX[i] && bytPointPositionRockY == bytAllPointsSnakeY[i]) //On regarde si la pierre est sur le point :           {             blnRestartPutRock = true; //On doit replacer la pierre           }         }         if(bytPointPositionRockX == bytAllPointsSnakeX[0] || bytPointPositionRockY == bytAllPointsSnakeY[0]) //On regarde si la pierre est sur la même ligne ou colonne que le point :         {           blnRestartPutRock = true; //On doit replacer la pierre         }         if(bytPointPositionRockX == bytPointPositionAppleX && bytPointPositionRockY == bytPointPositionAppleY) //On regarde si la pierre est sur la pomme :         {           blnRestartPutRock = true; //On doit replacer la pierre         }       } while(blnRestartPutRock); //On recommance tant que la pierre est sur le serpent             TABAPPLE[bytPointPositionAppleX][bytPointPositionAppleY] = 1; //On ajoute la pomme sur le tableau       TABROCK[bytPointPositionRockX][bytPointPositionRockY] = 1; //On ajoute la pierre sur le tableau             bytNumberOfCaseOfSnake++; //On augemente la taille du serpent             lcd.setCursor(8, 0); //On positionne le curseur pour écraser l'ancien score       lcd.print(bytNumberOfCaseOfSnake); //On affiche le score du joueur             bytAllPointsSnakeX[bytNumberOfCaseOfSnake-1] = bytPointPositionAppleXOld; //On ajoute la nouvelle case dans le tableau du serpent       bytAllPointsSnakeY[bytNumberOfCaseOfSnake-1] = bytPointPositionAppleYOld; //On ajoute la nouvelle case dans le tableau du serpent       RefreshAllTAB(); //Met à jour et récrit tous les tableaux du jeu     }   } } /* Affichage les informations transmises par la nunchuk dans la console */ void DisplayWiiInfos() //Fonction utilisée pour le déboggage {   Serial.print(joy_x_axis, DEC);   Serial.print("\t");   Serial.print(joy_y_axis, DEC);   Serial.print("\t");   Serial.print(accel_x_axis, DEC);   Serial.print("\t");   Serial.print(accel_y_axis, DEC);   Serial.print("\t");   Serial.print(accel_z_axis, DEC);   Serial.print("\t");   Serial.print(z_button, DEC);   Serial.print("\t");   Serial.print(c_button, DEC);   Serial.print("\t");   Serial.println(); } /* Affichage de la position de la tête du serpent dans la console */ void DisplayPositionSnakeHead() //Fonction utilisée pour le déboggage {   Serial.print("Point X :");   Serial.print(bytAllPointsSnakeX[0]); //Position X de la tête   Serial.print(" ");   Serial.print("Point Y :");   Serial.println(bytAllPointsSnakeY[0]); //Position Y de la tête } /* Gère ce que doit faire le jeu quand le serpent est mort */ void GameOver() {   boolean blnBeatBestScore = false; //Par défaut, ce n'est pas le meilleur score   if(bytNumberOfCaseOfSnake > EEPROM.read(0)) //Si c'est le meilleur score :
  {
    EEPROM.write(0, bytNumberOfCaseOfSnake); //On sauvegarde le meilleur score
    blnBeatBestScore = true;
  }
  boolean DEATHHEAD[8][8] = //Tableau pour dessiner une tête de mort
  {
    {0,1,1,1,1,0,0,0},
    {1,1,1,1,1,0,0,0},
    {1,1,1,0,0,1,1,0},
    {1,1,1,0,1,1,1,1},
    {1,1,1,1,1,0,1,1},
    {1,1,1,0,0,1,1,1},
    {0,1,1,0,1,1,0,0},
    {0,0,0,1,1,0,0,0},
  };
  blnDeath = true; //Le serpent est mort
  for(byte i = 0; i < 5; i++) //On va faire clignoter 5 fois la matrice en rouge-noir   {     fill_buffer(BACKGROUND, BLACK); //On affiche le fond en noir     if(blnBeatBestScore) //Si on a battu le meilleur score :     {       fill_buffer(DEATHHEAD, GREEN); //On affiche la tête de mort en rouge     }     else     {       fill_buffer(DEATHHEAD, RED); //On affiche la tête de mort en rouge     }     write_buffer(); //Ecriture des tableaux sur la matrice de leds     delay(250); //Peremt de visualiser le clignotement     RefreshAllTAB(); //Met à jour et récrit tous les tableaux du jeu     delay(250); //Peremt de visualiser le clignotement   }   fill_buffer(BACKGROUND, BLACK); //On éteind toutes les leds   write_buffer(); //Ecriture des tableaux sur la matrice de leds   lcd.clear(); //On efface tout le texte qui se trouve sur l'écran LCD   lcd.setCursor(0, 0); //On se place au début de l'écran   if(blnBeatBestScore) //Si le joueur a fait le meilleur score :   {     lcd.print("Meilleur score ! Il est de : "); //On affiche le score du joueur qui vient de battre le record :   }   else   {     lcd.print("Votre score est de : "); //On affiche le score du joueur :   }   lcd.print(bytNumberOfCaseOfSnake);   if(!blnBeatBestScore) //Si le joueur n'a pas fait le meilleur score :   {     lcd.print(" points"); //On affiche "points " seulement quand on a plus de place donc quand le meilleur score n'est pas battu   }   lcd.setCursor(0, 1); //On passe à la 2ème ligne   lcd.print("Appuyez sur Z pour recommencer"); //On indique que pour recommencer, il faut presser sur la touche Z de la nunchuk   int i = 0; //Le compteur de temps est initialisé à zéro (pour le bouton z)   lngTime = millis(); //On met l'heure à jour   do //Boucle infinie tant que l'utilisateur n'appuie pas sur Z   {     z_button = 0; //On attend que le buton soit appuyé     bytCountBuffer = 0; //On remet le compteur des octets reçus à zéro     handshake();         delay(20); //On attend 50 millisecondes pour ne pas surcharger l'arduino     if(i >= 15) //On va attendre 15 fois les 20 millisecondes donc 300 millisecondes avant de décaler le texte pour avoir le temps de le lire
    {
      lcd.scrollDisplayLeft(); //On décale tous les caractères de l'écran vers la gauche
      i = 0; //On remet le compteur du temps à zéro
    }
    else //Si le temps désiré n'est pas encore atteint :
    {
      i++; //On incrémente le nombre de fois qu'on a attendu 20 millisecondes (pour le bouton z)
    }
    if(digitalRead(buttonBreadboardPin) == HIGH && (millis() - lngTime) >= intWaitTimeForPushButtonAgain)//Si on appuie sur le bouton et que ça fait 0.5 seconde que l'on a pas appuyé dessus avant :
    {
      if(blnPlayWithAccel == true) //On va changer le mode de commande
      {
        blnPlayWithAccel = false; //On utilise le joystick
        digitalWrite(pinLedYellow, LOW); //On allume la led pour indiquer que le jeu se joue avec l'accéléromètre
      }
      else //Si le bouton n'est pas appuyé ou que l'on avait appuyé il y a moins de intWaitTimeForPushButtonAgain millisecondes :
      {
        blnPlayWithAccel = true; //On utilise l'accéléromètre
        digitalWrite(pinLedYellow, HIGH); //On éteind la led pour indiquer que le jeu se joue avec le joystick
      }
      lngTime = millis(); //On met l'heure à jour
    }

    Wire.requestFrom(0x52, 6); //On demande é recevoir les octets du nunchuk
    while(Wire.available()) //Tant qu'on reçoit un octet :
    {
      bytBuffer[bytCountBuffer] = Wire.read(); //On lit l'octet reçu
      bytCountBuffer++; //On incrément le nombre d'octets reçu
    }
    if(bytCountBuffer >= 5) //Si on a reçu tous les octets :
    {
      if((bytBuffer[5] >> 0) & 1) //Si le bouton Z est appuyé :
      {
        z_button = 1; //On définit le bouton Z comme appuyé
      }
    }
  } while(z_button == 1); //On ne fait rien tant que le bouton Z n'est pas appuyé
  StartGame(); //On recommence la partie
}

/* Déplace le serpent vers le haut sur la matrice */
void MoveUp() //Y = Y - 1
{
  TABSNAKE[bytAllPointsSnakeX[bytNumberOfCaseOfSnake-1]][bytAllPointsSnakeY[bytNumberOfCaseOfSnake-1]] = 0; //On efface la dernière case du serpent
  for(byte i = bytNumberOfCaseOfSnake-1; i > 0; i--) //On déplace toutes les cases du serpent
  {
    bytAllPointsSnakeY[i] = bytAllPointsSnakeY[i-1]; //Vaut la case Y précédente
    bytAllPointsSnakeX[i] = bytAllPointsSnakeX[i-1]; //Vaut la case X précédente
  }
  if(bytAllPointsSnakeY[0] == 0) //Si la tête du serpent est au bord :
  {
    bytAllPointsSnakeY[0] = 8; //On la replace de l'autre côté
  }
  bytAllPointsSnakeY[0] = bytAllPointsSnakeY[0] - 1; //On "ajoute" la tête vers le haut
  TABSNAKE[bytAllPointsSnakeX[0]][bytAllPointsSnakeY[0]] = 1; //On ajoute la nouvelle place de la tête sur le tableau du serpent
//  //Ces 3 prochaines lignes sont commentées pour résoudre un bug qui fait disparaitre une case quand on change de côté :
//  if(bytAllPointsSnakeY[0] == 7) //Si le serpent vient de passer par un côté :
//  {
//    TABSNAKE[bytAllPointsSnakeX[bytNumberOfCaseOfSnake-1]][bytAllPointsSnakeY[7]] = 0; //On efface la dernière case du serpent qui est encore de l'autre côté
//  }
  RefreshAllTAB(); //Met à jour et récrit tous les tableaux du jeu
}

/* Déplace le serpent vers le bas sur la matrice */
void MoveDown() //Y = Y + 1
{
  TABSNAKE[bytAllPointsSnakeX[bytNumberOfCaseOfSnake-1]][bytAllPointsSnakeY[bytNumberOfCaseOfSnake-1]] = 0; //On efface la dernière case du serpent
  for(byte i = bytNumberOfCaseOfSnake-1; i > 0; i--) //On déplace toutes les cases du serpent
  {
    bytAllPointsSnakeY[i] = bytAllPointsSnakeY[i-1]; //Vaut la case Y précédente
    bytAllPointsSnakeX[i] = bytAllPointsSnakeX[i-1]; //Vaut la case X précédente
  }
  if(bytAllPointsSnakeY[0] == 7) //Si la tête du serpent est au bord :
  {
    bytAllPointsSnakeY[0] = -1; //On la replace de l'autre côté
  }
  bytAllPointsSnakeY[0] = bytAllPointsSnakeY[0] + 1; //On "ajoute" la tête vers le bas
  TABSNAKE[bytAllPointsSnakeX[0]][bytAllPointsSnakeY[0]] = 1; //On ajoute la nouvelle place de la tête sur le tableau du serpent
  if(bytAllPointsSnakeY[0] == 8) //Si le serpent vient de passer par un côté :
  {
    TABSNAKE[bytAllPointsSnakeX[bytNumberOfCaseOfSnake-1]][bytAllPointsSnakeY[0]] = 0; //On efface la dernière case du serpent qui est encore de l'autre côté
  }
  RefreshAllTAB(); //Met à jour et récrit tous les tableaux du jeu
}

/* Déplace le serpent vers la droite sur la matrice */
void MoveRight() //X = X - 1
{
  TABSNAKE[bytAllPointsSnakeX[bytNumberOfCaseOfSnake-1]][bytAllPointsSnakeY[bytNumberOfCaseOfSnake-1]] = 0; //On efface la dernière case du serpent
  for(byte i = bytNumberOfCaseOfSnake-1; i > 0; i--) //On déplace toutes les cases du serpent
  {
    bytAllPointsSnakeX[i] = bytAllPointsSnakeX[i-1]; //Vaut la case X précédente
    bytAllPointsSnakeY[i] = bytAllPointsSnakeY[i-1]; //Vaut la case Y précédente
  }
  if(bytAllPointsSnakeX[0] == 0) //Si la tête du serpent est au bord :
  {
    bytAllPointsSnakeX[0] = 8; //On la replace de l'autre côté
  }
  bytAllPointsSnakeX[0] = bytAllPointsSnakeX[0] - 1; //On "ajoute" la tête vers la gauche
  TABSNAKE[bytAllPointsSnakeX[0]][bytAllPointsSnakeY[0]] = 1; //On ajoute la nouvelle place de la tête sur le tableau du serpent
//  //Ces 3 prochaines lignes sont commentées pour résoudre un bug qui fait disparaitre une case quand on change de côté :
//  if(bytAllPointsSnakeX[0] == 7) //Si le serpent vient de passer par un côté :
//  {
//    TABSNAKE[bytAllPointsSnakeX[7]][bytAllPointsSnakeY[bytNumberOfCaseOfSnake-1]] = 0; //On efface la dernière case du serpent qui est encore de l'autre côté
//  }
  RefreshAllTAB(); //Met à jour et récrit tous les tableaux du jeu
}

/* Déplace le serpent vers la gauche sur la matrice */
void MoveLeft() //X = X + 1
{
  TABSNAKE[bytAllPointsSnakeX[bytNumberOfCaseOfSnake-1]][bytAllPointsSnakeY[bytNumberOfCaseOfSnake-1]] = 0; //On efface la dernière case du serpent
  for(byte i = bytNumberOfCaseOfSnake-1; i > 0; i--) //On déplace toutes les cases du serpent
  {
    bytAllPointsSnakeX[i] = bytAllPointsSnakeX[i-1]; //Vaut la case X précédente
    bytAllPointsSnakeY[i] = bytAllPointsSnakeY[i-1]; //Vaut la case Y précédente
  }
  if(bytAllPointsSnakeX[0] == 7) //Si la tête du serpent est au bord :
  {
    bytAllPointsSnakeX[0] = -1; //On la replace de l'autre côté
  }
  bytAllPointsSnakeX[0] = bytAllPointsSnakeX[0] + 1; //On "ajoute" la tête vers la droite
  TABSNAKE[bytAllPointsSnakeX[0]][bytAllPointsSnakeY[0]] = 1; //On ajoute la nouvelle place de la tête sur le tableau du serpent
  if(bytAllPointsSnakeX[0] == 8) //Si le serpent vient de passer par un côté :
  {
    TABSNAKE[bytAllPointsSnakeX[0]][bytAllPointsSnakeY[bytNumberOfCaseOfSnake-1]] = 0; //On efface la dernière case du serpent qui est encore de l'autre côté
  }
  RefreshAllTAB(); //Met à jour et récrit tous les tableaux du jeu
}

/* On écrit les lettre "Go" sur la matrice pour se préparer au début du jeu */
void MakeGoOnMatrix()
{
  boolean WRITEG[8][8] = //Tableau pour dessiner la lettre G
  {
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {1,0,0,0,1,1,1,1},
    {1,0,0,0,1,0,0,1},
    {1,0,0,0,0,0,0,1},
    {1,1,1,1,1,1,1,1},
  };
  boolean WRITEO[8][8] = //Tableau pour dessiner la lettre o
  {
    {0,0,0,0,1,1,1,1},
    {0,0,0,0,1,0,0,1},
    {0,0,0,0,1,0,0,1},
    {0,0,0,0,1,1,1,1},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
  };
  fill_buffer(WRITEG, GREEN); //On affiche la lettre G pour écrire Go sur la matrice
  fill_buffer(WRITEO, ORANGE); //On affiche la lettre o pour écrire Go sur la matrice
  write_buffer(); //Ecriture des tableaux sur la matrice de leds
}

/* Initialisation ou recommencement du jeu */
void StartGame()
{
  lngTime = 0; //On réinitialise le compteur de temps
  bytNumberOfCaseOfSnake = 2; //Taille de sépart du serpent
  lcd.clear(); //On efface tout le texte qu'il y a sur l'écran
  lcd.setCursor(0, 0); //On met le curseur au début de la première ligne
  lcd.print("Score : "); //On affiche l'information pour le score
  lcd.print(bytNumberOfCaseOfSnake);
  lcd.setCursor(0, 1); //On met le curseur au début de la 2ème ligne
  lcd.print("Record : "); //On affiche l'information pour le meilleur score
  lcd.print(EEPROM.read(0)); //On affiche le meilleur score

  for(byte i = 0; i < 8; i++) //On efface/initialise toutes les cases de tous les tableaux :
  {
    for(byte j = 0; j < 8; j++) //On efface/initialise toutes les cases de tous les tableaux :
    {
      TABSNAKE[i][j] = 0; //On rempli le tableau avec des 0 pour commencer
      TABAPPLE[i][j] = 0; //On rempli le tableau avec des 0 pour commencer
      TABROCK[i][j] = 0; //On rempli le tableau avec des 0 pour commencer
    }
  }
  if(blnPlayWithAccel) //Si on joue avec l'accéléromètre :
  {
      ValueForMoveMinus = 100; //Sensibilité de l'accéléromètre
      ValueForMovePlus = 150; //Sensibilité de l'accéléromètre
  }
  else //Si on joue avec le joystick :
  {
      ValueForMoveMinus = 64; //Sensibilité du joystick
      ValueForMovePlus = 192; //Sensibilité du joystick
  }
  bytAllPointsSnakeX[0] = random(0, 7); //On place la tête du serpent aléatoirement sur l'axe x
  bytAllPointsSnakeY[0] = bytAllPointsSnakeX[0]; //On place la tête du serpent
  bytAllPointsSnakeX[1] = bytAllPointsSnakeX[0]; //On place la queue du serpent
  bytAllPointsSnakeY[1] = bytAllPointsSnakeX[0] + 1; //On place la queue du serpent
  TABSNAKE[bytAllPointsSnakeX[0]][bytAllPointsSnakeY[0]] = 1; //On place la case du serpent sur le tableau
  TABSNAKE[bytAllPointsSnakeX[1]][bytAllPointsSnakeY[1]] = 1; //On place la case du serpent sur le tableau
  bytNumberOfCaseOfSnake = 2; //Taille de sépart du serpent
  LastMove = "DOWN"; //On le fera partir vers le haut (ne fonctionne pas pour le moment)
  blnDeath = false; //Le serpent est vivant
  blnMoved = false; //L'utilisateur n'a pas encore bougé
  do //Placement de la pomme sur la matrice :
  {
    blnRestartPutApple = false; //Par défaut, la pomme est placée correctement
    bytPointPositionAppleX = random(0, 8); //On trouve une nouvelle position X aléatoire pour la pomme
    bytPointPositionAppleY = random(0, 8); //On trouve une nouvelle position Y aléatoire pour la pomme
    for(byte i = 1; i < bytNumberOfCaseOfSnake-1; i++) //On passe sur toutes les cases
    {
      if(bytPointPositionAppleX == bytAllPointsSnakeX[i] && bytPointPositionAppleY == bytAllPointsSnakeY[i]) //On regarde si la pomme est sur le point :
      {
        blnRestartPutApple = true; //On doit replacer la pomme
      }
    }
    if(bytPointPositionRockX == bytAllPointsSnakeX[0] && bytPointPositionRockY == bytAllPointsSnakeY[0]) //On regarde si la pomme est sur la tête :
    {
      blnRestartPutRock = true; //On doit replacer la pomme
    }
  } while(blnRestartPutApple); //On recommance tant que la pomme est sur le serpent

  do //Placement de la pierre sur la matrice :
  {
    blnRestartPutRock = false; //Par défaut, la pierre est placée correctement
    bytPointPositionRockX = random(0, 8); //On trouve une nouvelle position X aléatoire pour la pierre
    bytPointPositionRockY = random(0, 8); //On trouve une nouvelle position Y aléatoire pour la pierre
    for(byte i = 1; i < bytNumberOfCaseOfSnake-1; i++) //On passe sur toutes les cases
    {
      if(bytPointPositionRockX == bytAllPointsSnakeX[i] && bytPointPositionRockY == bytAllPointsSnakeY[i]) //On regarde si la pierre est sur le point :
      {
        blnRestartPutRock = true; //On doit replacer la pomme
      }
    }
    if(bytPointPositionRockX == bytAllPointsSnakeX[0] || bytPointPositionRockY == bytAllPointsSnakeY[0]) //On regarde si la pierre est sur la même ligne ou colonne que le point :
    {
      blnRestartPutRock = true; //On doit replacer la pierre
    }
  } while(blnRestartPutRock); //On recommance tant que la pierre est sur le serpent

  TABAPPLE[bytPointPositionAppleX][bytPointPositionAppleY] = 1; //On place la pomme sur le tableau
  TABROCK[bytPointPositionRockX][bytPointPositionRockY] = 1; //On place la pierre sur le tableau
  RefreshAllTAB(); //Met à jour et récrit tous les tableaux du jeu
}

/* Initialisation des pins, de la matrice, de l'écran, de la transmition série et de la calibration du nunchuck */
void setup()
{
  Serial.begin(9600); //Vitesse de transfert du port série (usb)

  randomSeed(analogRead(0)); //Pour avoir vraiment des nombre aléatoire. (Il faut utiliser un pin connecté à rien et non initialisé)

  pinMode(DATAOUT, OUTPUT); //Pin pour envoyer les données à la matrice de leds
//  pinMode(DATAIN, INPUT); //Pas utilisé dans ce cas
  pinMode(SPICLOCK, OUTPUT); //Pin pour la synchronisation
  pinMode(SLAVESELECT, OUTPUT);
  digitalWrite(SLAVESELECT, HIGH); //Désactivation

  pinMode(pinLedYellow, OUTPUT); //On définit le pin de la led jaune en sortie
  pinMode(buttonBreadboardPin, INPUT); //On définit le pin du bouton en entrée

  blnPlayWithAccel = false; //Par défaut, on ne joue pas avec l'aaccéléromètre
  digitalWrite(pinLedYellow, LOW); //On allume la led pour indiquer que le jeu se joue avec l'accéléromètre

  Wire.begin(); //On commence la séquence d'initialisation de la nunchuk
  Wire.beginTransmission(0x52);
  Wire.write(0xF0);
  Wire.write(0x55);
  Wire.write(0x00); //Envoie les envois à l'adresse zero.
  Wire.endTransmission(); //Stoppe la transmition

//  EEPROM.write(0, 0); //Pour remettre le meilleur score à 0. Attention, le nombre de cycle d'écriture sur l'arduino est limité à 100'000 ! Donc ne pas oublier de décommenter cette ligne !

  lcd.begin(16, 2); //On indique le nombre de colonnes et de lignes de l'écran
  lcd.print("Bienvenue sur"); //Message d'accueil
  lcd.setCursor(0, 1); //On met le curseur au début de la deuxième ligne
  lcd.print("Snakeduino !"); //On affiche le nom du jeu

  MakeGoOnMatrix(); //On affiche "Go" sur la matrice de leds

  lngTime = millis(); //On met l'heure à jour
  for(byte i = 0; i < 100; i++) //On va attendre 2 secondes en regardant si l'utilisateur appuie sur le bouton :   {     delay(20); //On attend un petit peu pour pouvoir lire l'écran et se préparer au début du jeu     if(digitalRead(buttonBreadboardPin) == HIGH && (millis() - lngTime) >= intWaitTimeForPushButtonAgain)//Si on appuie sur le bouton et que ça fait 0.5 seconde que l'on a pas appuyé dessus avant :
    {
      if(blnPlayWithAccel == true) //On va changer le mode de commande
      {
        blnPlayWithAccel = false; //On utilise le joystick
        digitalWrite(pinLedYellow, LOW); //On allume la led pour indiquer que le jeu se joue avec l'accéléromètre
      }
      else
      {
        blnPlayWithAccel = true; //On utilise l'accéléromètre
        digitalWrite(pinLedYellow, HIGH); //On éteind la led pour indiquer que le jeu se joue avec le joystick
      }
      lngTime = millis(); //On met l'heure à jour
    }
  }

  StartGame(); //On initialise et démarre le jeu
}

/* Boucle infinie principale qui va appeler la fonction pour les données nunchuck puis faire le sleep de la boucle : */
void loop()
{
  Wire.requestFrom(0x52, 6); //On demande à recevoir les données
  while(Wire.available()) //Tant qu'on reçoit un byte :
  {
    bytBuffer[bytCountBuffer] = Wire.read(); //On lit le byte reçu
    bytCountBuffer++; //On incrémente le nombre de bytes reçu
  }
  if(bytCountBuffer >= 5) //Si on a reçu les 6 bytes :
  {
    parseNunchuk(); //On va analyser ce qu'ils contiennent et bouger le jeu en fonction de ces données
  }
  bytCountBuffer = 0; //On remet le compteur du buffer à zéro
  handshake();
  delay(intSpeedSnake); //On attend quelques millisecondes de pause pour que le serpent n'aie pas trop vite
}

Si vous avez des questions ou des idées d’amélioration, n’hésitez pas 😉

Total-Informatique

Le blog de sablier94