// https://wokwi.com/projects/378469495159511041
// https://forum.arduino.cc/t/snake-game-on-8-8-leds-not-fully-working/1176943
# include <FastLED.h>
// board size, new snake size, snake speed, colors
# define SIZEX 16 // square matrix 16 x 16
const byte initSnakeSize = 5; // birth length
const unsigned int startSpeed = 347; // game time constant old man speed.
CRGB crgbAPPLE = CRGB(255, 0, 0); // Couleur de la pomme (rouge)
CRGB crgbSNAKE = CRGB(100, 255, 0);
CRGB crgbTRAIL = CRGB(0x30, 0x20, 0x10);
# define gameMaxInt 0x10 // power of two is matrix dimension, this bit is over/underflow
# define DATA_PIN 2 // Broche de données du carré de LED
# define NUM_LEDS (SIZEX * SIZEX) // Nombre de LEDs (sizex square)
# define RESET_SW 8 // Broche pour le bouton du réinitialisation
# define LED_INDEX 12 // Indice de la LED de départ du Snake
CRGB leds[NUM_LEDS];
// colour constants
//CRGB crgbAPPLE = CRGB(255, 0, 0); // Couleur de la pomme (rouge)
CRGB crgbBLACK = CRGB(0, 0, 0); // Éteindre toutes les LEDs
CRGB crgbSCORET = CRGB(255, 242, 242);// score text color
CRGB crgbSCOREX = CRGB(10, 20, 30); // score background colour
//CRGB crgbSNAKE = CRGB(100, 255, 0);
//CRGB crgbTRAIL = CRGB(10, 20, 30);
// pins left right up down
# define LEFT 3
# define RIGHT 4
# define UP 6
# define DOWN 5
const unsigned char jPin[4] = {LEFT, RIGHT, UP, DOWN};
int snakeLength = initSnakeSize; // Longueur initiale du Snake
bool kFreebie; // one-time free to eat pass
// ring buffer for snake pixel coordonnées
byte snakeYX[NUM_LEDS];
//...
int headIdx; // maintained as head of snake
int tailIdx; // temporary is just H + L -1, so may not even need it
const int iMask = 0xff; // mask for snake number math 0..255
// const int long kF = 4; // shift for multiply by SIZEX
unsigned char appleYX; // old school, appleY; // Coordonnées de la pomme
int direction = 1; // Direction du Snake (0: haut, 1: droite, 2: bas, 3: gauche)
int speed = startSpeed; // Vitesse de déplacement du Snake (plus la valeur est basse, plus il est rapide)
unsigned long lastMoveTime; // Dernier moment où le Snake a bougé
bool gameOverFlag = false; // Drapeau pour indiquer la fin du jeu
int applesEaten; // Nombre de pommes mangées
bool restartGame = false; // Drapeau pour indiquer le redémarrage du jeu
volatile unsigned long randomNuber; // don't optimise me away
void setup() {
Serial.begin(115200);
Serial.println("no flying blind!\n");
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
FastLED.show();
// let's have the same gane every time for awhile randomSeed(analogRead(0)); // Initialisation du générateur de nombres aléatoires
lastMoveTime = millis();
for (unsigned char tt = 0; tt < 4; tt++)
pinMode(jPin[tt], INPUT_PULLUP);
pinMode(RESET_SW, INPUT_PULLUP);
resetGame();
}
bool collide;
void loop() {
if (gameOverFlag) {
// Afficher le nombre de pommes mangées en rouge
displayScore();
// Attendre que le bouton du joystick soit enfoncé pour redémarrer le jeu
while (digitalRead(RESET_SW) == HIGH)
; // hang out until
Serial.println("BUTTON");
resetGame(); // need to RESET game
return;
}
unsigned long currentTime = millis();
if (currentTime - lastMoveTime >= speed) {
// Serial.println(randomNuber); // don't optimize me
collide = false;
moveSnake();
lastMoveTime = currentTime;
}
checkCollision();
if (!digitalRead(LEFT)) {
randomNuber += random(1000);
changeDirection(3);
// Serial.println("LEFT");
}
if (!digitalRead(RIGHT)) {
randomNuber += random(1000);
// Serial.println("RIGHT");
changeDirection(1);
}
if (!digitalRead(UP)) {
randomNuber += random(1000);
changeDirection(0);
// Serial.println("UP");
}
if (!digitalRead(DOWN)) {
randomNuber += random(1000);
changeDirection(2);
// Serial.println("DOWN");
}
// Si restartGame est vrai, réinitialise le jeu
if (restartGame) {
resetGame();
}
FastLED.show();
}
void moveSnake() {
// Déplacer le Snake en ajoutant une nouvelle tête dans la direction actuelle
int newHeadYX = snakeYX[headIdx];
//Serial.print(newHeadYX, 16); Serial.print(" ");
switch (direction) {
case 0: // Haut
if ((newHeadYX & 0xf0) != 0) newHeadYX -= 0x10;
else collide = true;
break;
case 1: // Droite
if ((newHeadYX & 0xf) != 0xf) newHeadYX++;
else collide = true;
break;
case 2: // Bas
if ((newHeadYX & 0xf0) != 0xf0) newHeadYX += 0x10;
else collide = true;
break;
case 3: // Gauche
if ((newHeadYX & 0xf) != 0) newHeadYX--;
else collide = true;
break;
}
// collide = false;
//Serial.print(newHeadYX, 16); Serial.print(" ");
/* now the snake element are in a ring buffer, so no need to shuffle
// Déplacer le corps du Snake
for (int i = snakeLength - 1; i > 0; i--) {
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
*/
// Mettre à jour la position de la tête du Snake
headIdx--; headIdx &= iMask; // snake grows to the left (-), and extends right (+)
snakeYX[headIdx] = newHeadYX;
// Vérifier si le Snake a mangé une pomme
kFreebie = false;
if (snakeYX[headIdx] == appleYX) {
snakeLength++;
spawnApple();
kFreebie = true;
// Accélérer légèrement le Snake
if (speed > 20) {
// speed -= 5;
}
}
// Afficher le Snake et la pomme
// clearBoard();
drawSnake();
drawApple();
}
void printSnake()
{
for (unsigned char sl = 0; sl < snakeLength; sl++) {
int theIdx = (headIdx + sl) & iMask;
Serial.print(snakeYX[theIdx], 16); Serial.print(" ");
}
Serial.println("");
}
void checkCollision() {
// Vérifier si le Snake a heurté les bords
// if (snakeX[headIdx] & gameMaxInt || snakeY[headIdx] & gameMaxInt) {
if (collide) {
Serial.println("game over hit a wall");
gameOver();
return;
}
// Vérifier si le Snake a mangé son propre corps
for (int i = 1; i < snakeLength; i++) {
int theIdx = (i + headIdx) & iMask;
if (snakeYX[theIdx] == snakeYX[headIdx]) {
if (!kFreebie) {
kFreebie = false; // but should have been resetted?
Serial.println("game over snake bite");
gameOver();
}
}
}
}
void spawnApple() {
applesEaten += 3 + random(5); // seems only fair
if (0) { // testing eating the apple on the snake
int xIdx = (headIdx + 3) & iMask;
appleYX = snakeYX[xIdx];
Serial.print(" new apple @ "); Serial.println(appleYX);
return;
}
// sometimes aim the apple onto the snake but not its head
if (random(1000) > 777) { // (random(1000) > 100) { hasn't worked well
int randomSnake = random(1, snakeLength - 1);
int theIdx = (headIdx + randomSnake) & iMask;
appleYX = snakeYX[theIdx];
}
// and sometimes totally random, including the snake's head...
else {
appleYX = random(0, NUM_LEDS);
}
}
// minimum draw
void drawSnake() {
// Serial.print("gonna draw the head and undraw the (old?) tail... ");
// Serial.print(snakeX[headIdx]); Serial.print(",");
// Serial.print(snakeY[headIdx]); Serial.print(" ");
// Serial.println();
int thePixel = snakeYX[headIdx];
// int thePixel = snakeY[headIdx] * SIZEX + snakeX[headIdx];
leds[thePixel] = crgbSNAKE; // Couleur du Snake (vert)
int theIdx = (headIdx + snakeLength) & iMask;
thePixel = snakeYX[theIdx];
// thePixel = snakeY[theIdx] * 16 + snakeX[theIdx];
leds[thePixel] = crgbTRAIL; // snake leaves a trail
}
void drawApple() {
leds[appleYX ] = crgbAPPLE; // Couleur de la pomme (rouge)
}
void clearBoard() {
fill_solid(leds, NUM_LEDS, crgbBLACK);
}
// wrecks applesEaten!
void displayScore() {
fill_solid(leds, NUM_LEDS, crgbSCOREX);
Serial.print("your score = "); Serial.println(applesEaten);
// delay(1000); // sue me.
if (applesEaten >= 1000) applesEaten = 999;
int hundreds = applesEaten / 100;
applesEaten -= 100 * hundreds;
int tens = applesEaten / 10; // Dizaines
int units = applesEaten - 10 * tens;
int posX = 2;
if (hundreds) {
drawN(hundreds, posX, 2);
posX += 4;
}
if (hundreds || tens) {
drawN(tens, posX, 2);
posX += 4;
}
drawN(units, posX, 2);
FastLED.show();
// delay(1300); // get off my back
}
void gameOver() {
gameOverFlag = true;
clearBoard();
// displayScore();
FastLED.show();
restartGame = false; // Ne pas redémarrer automatiquement
}
void resetGame() {
snakeLength = initSnakeSize;
direction = 1;
speed = startSpeed;
gameOverFlag = false;
applesEaten = 0;
headIdx = 0;
// Réinitialiser la position de départ du Snake
for (unsigned char sl = 0; sl < snakeLength; sl++) {
int theIdx = (headIdx + sl) & iMask;
snakeYX[theIdx] = 0x30 | (9 - sl);
}
printSnake();
// Réinitialiser la position de la pomme
spawnApple();
// Effacer l'affichage du score
clearBoard();
drawSnake0(); //once draw entire snake
drawApple();
// Réinitialiser la variable restartGame à faux
restartGame = false;
}
void changeDirection(int newDirection) {
if ((newDirection & 1) != (direction & 1))
direction = newDirection; // only on valid changes. here be where
}
unsigned char font[10][3] = {
/*0*/ {0b11111, 0b10001, 0b11111,},
/*1*/ {0b00000, 0b11111, 0b00000,},
/*2*/ {0b11101, 0b10101, 0b10111,},
/*3*/ {0b10101, 0b10101, 0b11111,},
/*4*/ {0b00111, 0b00100, 0b11111,},
/*5*/ {0b10111, 0b10101, 0b11101,},
/*6*/ {0b11111, 0b10101, 0b11101,},
/*7*/ {0b00001, 0b00001, 0b11111,},
/*8*/ {0b11111, 0b10101, 0b11111,},
/*9*/ {0b10111, 0b10101, 0b11111,},
};
void drawN(int N, int X, int Y)
{
Y <<= 3;
for (unsigned char colm = 0; colm < 3; colm++, X++) {
unsigned char yT = Y;
unsigned char theBits = font[N][colm];
unsigned char mask = 0x1;
for (unsigned char row = 0; row < 5; row++) {
leds[yT + X] = (mask & theBits) ? crgbSCORET : crgbSCOREX;
mask <<= 1;
yT += SIZEX;
}
}
}
void drawSnake0() {
// Serial.print("gonna draw ");
// Serial.print(headIdx); Serial.print(" -> ");
for (int i = 0; i < snakeLength; i++) {
int theIdx = (headIdx + i) & iMask;
// Serial.print(snakeX[theIdx]); Serial.print(",");
// Serial.print(snakeY[theIdx]); Serial.print(" ");
int thePixel = snakeYX[theIdx];
leds[thePixel] = crgbSNAKE; // Couleur du Snake (vert)
}
// Serial.println();
}
void eof() {}
/* Réinitialiser la position de départ du Snake
snakeYX[headIdx] = 0x93;
for (unsigned char sl = 0; sl < snakeLength; sl++) {
int theIdx = (headIdx + sl + 1) & iMask;
int xx = 9 - sl;
int yy = 3;
snakeYX[theIdx] = (yy << 4) | xx;
}
*/
/*
void changeDirection0(int newDirection) {
if ((newDirection == 0 && direction != 2) || // Nouvelle direction : haut, direction actuelle : bas
(newDirection == 1 && direction != 3) || // Nouvelle direction : droite, direction actuelle : gauche
(newDirection == 2 && direction != 0) || // Nouvelle direction : bas, direction actuelle : haut
(newDirection == 3 && direction != 1)) { // Nouvelle direction : gauche, direction actuelle : droite
direction = newDirection;
// this is on speed dial! do a printing state trick one day if
// Serial.print("change direction to "); Serial.println(direction);
}
}
*/