Last active
October 23, 2023 10:16
-
-
Save HorlogeSkynet/d7ec59050c5e8041a7bb to your computer and use it in GitHub Desktop.
Simple Snake for LCD4884 screen on Arduino
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//!\\ Visit <https://samuel.forestier.app/blog/hacking/snake-with-lcd4884-shield> //!\\ | |
#include "LCD4884.h" | |
///////////User's Parameters | |
#define USE_PUSHBUTTONS 1 | |
#define USE_BUZZER 1 | |
//PIN parameters | |
#define LEFT_BUTTON 13 | |
#define UP_BUTTON 12 | |
#define DOWN_BUTTON 11 | |
#define RIGHT_BUTTON 10 | |
#define BUZZER 8 | |
#define VOIDANALOGIC A1 //<-- An useless analogic pin to set the random seed | |
//Global parameters | |
#define MOVESNEWAPPLE 15 | |
#define DELAY_MS 400 | |
//Snake initial position | |
#define LENGHTATSTART 3 | |
#define H_X_START 1 | |
#define H_Y_START 5 | |
#define B_X_START 1 | |
#define B_Y_START 4 | |
#define T_X_START 1 | |
#define T_Y_START 3 | |
//////////////////////////// | |
/////Programmer's Parameters | |
#define DEBUG 0 | |
//Screen options | |
#define SIZE_X 6 | |
#define SIZE_Y 14 | |
#define MIN_X 0 | |
#define MIN_Y 0 | |
//////////////////////////// | |
typedef enum | |
{ | |
LEFT, | |
DOWN, | |
RIGHT, | |
UP | |
}DIRECTION; | |
typedef enum | |
{ | |
MINUS, | |
PIPE | |
}TYPE; | |
typedef struct | |
{ | |
short int posX; | |
short int posY; | |
TYPE whatChar; | |
}POSITION; | |
typedef struct | |
{ | |
POSITION *snake; | |
short int snakeLenght; | |
DIRECTION lastDirection; | |
}PLAYER; | |
typedef struct | |
{ | |
POSITION coordonates; | |
short int movingCount; | |
bool present; | |
}APPLE; | |
////////////////////////////////////////////////////////////////////////////////////////// | |
void setup() | |
{ | |
lcd.init(); | |
lcd.clear(); | |
lcd.turnBacklightOn(true); | |
randomSeed(analogRead(VOIDANALOGIC)); | |
if(USE_PUSHBUTTONS) | |
{ | |
pinMode(LEFT_BUTTON, INPUT); | |
pinMode(UP_BUTTON, INPUT); | |
pinMode(DOWN_BUTTON, INPUT); | |
pinMode(RIGHT_BUTTON, INPUT); | |
} | |
if(USE_BUZZER) | |
{ | |
pinMode(BUZZER, OUTPUT); | |
} | |
if(DEBUG) | |
{ | |
Serial.begin(9600); | |
Serial.println("Screen, OUTPUT & INPUT pins, and random seed initialised."); | |
} | |
} | |
void loop() | |
{ | |
APPLE apple; | |
PLAYER player; | |
start(&player, &apple); | |
play(&player, &apple); | |
end(&player, &apple); | |
} | |
////////////////////////////////////////////////////////////////////////////////////////// | |
void start(PLAYER *player, APPLE *apple) | |
{ | |
player->snakeLenght = LENGHTATSTART; | |
player->snake = (POSITION*)malloc(player->snakeLenght * sizeof(*player->snake)); | |
if(player->snake == NULL) | |
{ | |
if(DEBUG) | |
{ | |
Serial.println("Error during memory allocation."); | |
} | |
loop(); | |
} | |
apple->coordonates.posX = 0; | |
apple->coordonates.posY = 0; | |
apple->movingCount = 0; | |
apple->present = false; | |
player->snake[0].posX = H_X_START; | |
player->snake[0].posY = H_Y_START; | |
player->snake[0].whatChar = MINUS; | |
player->snake[1].posX = B_X_START; | |
player->snake[1].posY = B_Y_START; | |
player->snake[1].whatChar = MINUS; | |
player->snake[2].posX = T_X_START; | |
player->snake[2].posY = T_Y_START; | |
player->snake[2].whatChar = MINUS; | |
player->lastDirection = RIGHT; | |
if(USE_BUZZER) | |
{ | |
tone(BUZZER, 10000, 10); | |
} | |
lcd.writeString(CENTER("SNAKE"), 2, "SNAKE", MENU_NORMAL); | |
lcd.writeString(CENTER("START !"), 5, "START !", MENU_HIGHLIGHT); | |
while(noButtonPressed()); | |
promptMap(player, apple); | |
delay(1000); | |
while(noButtonPressed()); | |
} | |
void play(PLAYER *player, APPLE *apple) | |
{ | |
bool useLastDirection = false; | |
short int _lastPosX, _lastPosY; | |
TYPE _lastType; | |
for(short int k; ; apple->movingCount++, delay(DELAY_MS)) | |
{ | |
_lastPosX = player->snake[0].posX; | |
_lastPosY = player->snake[0].posY; | |
_lastType = player->snake[0].whatChar; | |
if(USE_PUSHBUTTONS) | |
{ | |
if(digitalRead(LEFT_BUTTON)) | |
{ | |
goLeft(player); | |
} | |
else | |
{ | |
if(digitalRead(UP_BUTTON)) | |
{ | |
goUp(player); | |
} | |
else | |
{ | |
if(digitalRead(DOWN_BUTTON)) | |
{ | |
goDown(player); | |
} | |
else | |
{ | |
if(digitalRead(RIGHT_BUTTON)) | |
{ | |
goRight(player); | |
} | |
} | |
} | |
} | |
} | |
else | |
{ | |
static short int _joystick; | |
_joystick = int(analogRead(0)); | |
Serial.println(_joystick); | |
if(_joystick < 1000) | |
{ | |
if(_joystick < 700) | |
{ | |
if(_joystick < 450) | |
{ | |
if(_joystick < 200) | |
{ | |
goDown(player); | |
} | |
else | |
{ | |
goRight(player); | |
} | |
} | |
else | |
{ | |
goUp(player); | |
} | |
} | |
else | |
{ | |
goLeft(player); | |
} | |
} | |
} | |
if(player->snake[0].posX == _lastPosX && player->snake[0].posY == _lastPosY) | |
{ | |
switch(player->lastDirection) | |
{ | |
case LEFT: | |
goLeft(player); | |
break; | |
case UP: | |
goUp(player); | |
break; | |
case DOWN: | |
goDown(player); | |
break; | |
case RIGHT: | |
goRight(player); | |
break; | |
default: | |
if(DEBUG) | |
{ | |
Serial.println("Error Switch lastDirection.\n\n"); | |
} | |
break; | |
} | |
} | |
if(isSnakePresent(player)) | |
{ | |
break; | |
} | |
else | |
{ | |
appleTest(player, apple); | |
//Head | |
if(player->lastDirection == LEFT || player->lastDirection == RIGHT) | |
{ | |
player->snake[0].whatChar = MINUS; | |
} | |
else | |
{ | |
player->snake[0].whatChar = PIPE; | |
} | |
//Tail | |
for(k = player->snakeLenght - 1; k > 1; k--) | |
{ | |
player->snake[k].posX = player->snake[k - 1].posX; | |
player->snake[k].posY = player->snake[k - 1].posY; | |
player->snake[k].whatChar = player->snake[k - 1].whatChar; | |
} | |
//Body | |
player->snake[1].posX = _lastPosX; | |
player->snake[1].posY = _lastPosY; | |
player->snake[1].whatChar = _lastType; | |
if(!apple->present) | |
{ | |
appleAppearing(player, apple); | |
} | |
promptMap(player, apple); | |
} | |
} | |
} | |
void goUp(PLAYER *player) | |
{ | |
if(player->lastDirection != DOWN) | |
{ | |
player->snake[0].posX--; | |
if(player->snake[0].posX < MIN_X) | |
{ | |
player->snake[0].posX = SIZE_X - 1; | |
} | |
player->lastDirection = UP; | |
} | |
} | |
void goLeft(PLAYER *player) | |
{ | |
if(player->lastDirection != RIGHT) | |
{ | |
player->snake[0].posY--; | |
if(player->snake[0].posY < MIN_Y) | |
{ | |
player->snake[0].posY = SIZE_Y - 1; | |
} | |
player->lastDirection = LEFT; | |
} | |
} | |
void goRight(PLAYER *player) | |
{ | |
if(player->lastDirection != LEFT) | |
{ | |
player->snake[0].posY++; | |
if(player->snake[0].posY > SIZE_Y - 1) | |
{ | |
player->snake[0].posY = MIN_Y; | |
} | |
player->lastDirection = RIGHT; | |
} | |
} | |
void goDown(PLAYER *player) | |
{ | |
if(player->lastDirection != UP) | |
{ | |
player->snake[0].posX++; | |
if(player->snake[0].posX > SIZE_X - 1) | |
{ | |
player->snake[0].posX = MIN_X; | |
} | |
player->lastDirection = DOWN; | |
} | |
} | |
void newSnake(PLAYER *player) | |
{ | |
POSITION *_snakeTemp; | |
player->snakeLenght++; | |
_snakeTemp = (POSITION*)malloc(player->snakeLenght * sizeof(*_snakeTemp)); | |
if(_snakeTemp == NULL) | |
{ | |
if(DEBUG) | |
{ | |
Serial.println("Error during memory allocation."); | |
} | |
loop(); | |
} | |
//The last value of new array takes the last value of the old array | |
_snakeTemp[player->snakeLenght - 1].posX = player->snake[player->snakeLenght - 2].posX; | |
_snakeTemp[player->snakeLenght - 1].posY = player->snake[player->snakeLenght - 2].posY; | |
for(short int i = 0; i < player->snakeLenght - 1; i++) | |
{ | |
_snakeTemp[i].posX = player->snake[i].posX; | |
_snakeTemp[i].posY = player->snake[i].posY; | |
_snakeTemp[i].whatChar = player->snake[i].whatChar; | |
} | |
if(player->snake != NULL) | |
{ | |
free(player->snake); | |
player->snake = NULL; | |
} | |
player->snake = _snakeTemp; | |
_snakeTemp = NULL; | |
} | |
void promptMap(PLAYER *player, APPLE *apple) | |
{ | |
lcd.clear(); | |
for(short int k = 0; k < player->snakeLenght; k++) | |
{ | |
if(player->snake[k].whatChar == MINUS) | |
{ | |
lcd.writeString(player->snake[k].posY * NB_PIX_X, player->snake[k].posX, "-", MENU_NORMAL); | |
} | |
else | |
{ | |
lcd.writeString(player->snake[k].posY * NB_PIX_X, player->snake[k].posX, "I", MENU_NORMAL); | |
} | |
} | |
if(apple->present) | |
{ | |
lcd.writeString(apple->coordonates.posY * NB_PIX_X, apple->coordonates.posX, "*", MENU_HIGHLIGHT); | |
} | |
} | |
void appleAppearing(PLAYER *player, APPLE *apple) | |
{ | |
short int i, j; | |
long _randy; | |
do | |
{ | |
_randy = random(0, SIZE_X * SIZE_Y); | |
j = _randy / SIZE_X; | |
i = _randy - (j * SIZE_X); | |
} while(!testCase(player, i, j)); | |
apple->coordonates.posX = i; | |
apple->coordonates.posY = j; | |
apple->present = true; | |
} | |
bool isSnakePresent(PLAYER *player) | |
{ | |
for(short int k = 1; k < player->snakeLenght; k++) | |
{ | |
if(player->snake[0].posX == player->snake[k].posX && player->snake[0].posY == player->snake[k].posY) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
bool testCase(PLAYER *player, short int caseX, short int caseY) | |
{ | |
for(short int k = 0; k < player->snakeLenght; k++) | |
{ | |
if(player->snake[k].posX == caseX && player->snake[k].posY == caseY) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
void appleTest(PLAYER *player, APPLE *apple) | |
{ | |
if(player->snake[0].posX == apple->coordonates.posX && player->snake[0].posY == apple->coordonates.posY) | |
{ | |
if(USE_BUZZER) | |
{ | |
tone(BUZZER, 10000, 5); | |
} | |
newSnake(player); | |
apple->movingCount = 0; | |
apple->present = false; | |
} | |
else | |
{ | |
if(apple->movingCount > MOVESNEWAPPLE) | |
{ | |
apple->movingCount = 0; | |
apple->present = false; | |
} | |
} | |
} | |
bool noButtonPressed() | |
{ | |
if(USE_PUSHBUTTONS) | |
{ | |
if(digitalRead(LEFT_BUTTON) != 0 || digitalRead(UP_BUTTON) != 0 || digitalRead(DOWN_BUTTON) != 0 || digitalRead(RIGHT_BUTTON) != 0) | |
{ | |
return false; | |
} | |
} | |
else | |
{ | |
if(analogRead(0) < 1000) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
void end(PLAYER *player, APPLE *apple) | |
{ | |
if(player->snake != NULL) | |
{ | |
free(player->snake); | |
player->snake = NULL; | |
} | |
lcd.clear(); | |
lcd.turnBacklightOn(false); | |
if(USE_BUZZER) | |
{ | |
tone(BUZZER, 10000, 10); | |
} | |
if(player->snakeLenght < SIZE_X * SIZE_Y) | |
{ | |
lcd.writeString(CENTER("Defeat !"), 1, "Defeat !", MENU_NORMAL); | |
} | |
else | |
{ | |
lcd.writeString(CENTER("Victory !"), 1, "Victory !", MENU_NORMAL); | |
} | |
lcd.writeString(0, 3, "Score:", MENU_NORMAL); | |
char _score[3] = {0}; | |
sprintf(_score, "%hd", player->snakeLenght - LENGHTATSTART); | |
lcd.writeString(8 * NB_PIX_X, 3, _score, MENU_NORMAL); | |
lcd.writeString(CENTER("REPLAY ?"), 5, "REPLAY ?", MENU_HIGHLIGHT); | |
delay(1000); | |
while(noButtonPressed()); | |
delay(1000); | |
lcd.clear(); | |
lcd.turnBacklightOn(true); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment