I want to write a script about snake game, if i press RESET button, the snake will spin. I'm wondering why my display freezes after I press the RESET button, but it's actually running? How can I solve it?
I am using ESP32 and 1602A LCD
#include <LiquidCrystal_I2C.h> //LCD library
#include <stdlib.h>
#include <limits.h>
#include "Arduino_1602_Snake.h"
#include <Preferences.h>
#define GRAPHIC_WIDTH 16
#define GRAPHIC_HEIGHT 4
// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
LiquidCrystal_I2C lcd(0x27,16,2); //0x27 I2c address
Preferences prefs; // 声明Preferences对象
byte block[3] = {
B01110,
B01110,
B01110,
};
byte apple[3] = {
B00100,
B01010,
B00100,
};
struct Pos {
uint8_t x = 0, y = 0;
};
int8_t snakePosHistory[GRAPHIC_HEIGHT * GRAPHIC_WIDTH][2] = {{0}}; //first element is the head.
size_t snakeLength = 0;
enum Sd {SNAKE_LEFT, SNAKE_UP, SNAKE_RIGHT, SNAKE_DOWN};
Sd snakeDirection;
int8_t applePos[2];
unsigned long lastGameUpdateTick = 0;
unsigned long gameUpdateInterval = 1000;
bool thisFrameControlUpdated = false;
enum Gs {GAME_MENU, GAME_PLAY, GAME_LOSE, GAME_WIN};
Gs gameState;
uint8_t graphicRam[GRAPHIC_WIDTH * 2 / 8][GRAPHIC_HEIGHT];
void graphic_generate_characters()
{
/*
space: 0 0
0: 0 A
1: 0 B
2: A 0
3: A A
4: A B
5: B 0
6: B A
7: B B
*/
for (size_t i = 0; i < 8; i++) {
byte glyph[8];
int upperIcon = (i + 1) / 3;
int lowerIcon = (i + 1) % 3;
memset(glyph, 0, sizeof(glyph));
if (upperIcon == 1)
memcpy(&glyph[0], &block[0], 3);
else if (upperIcon == 2)
memcpy(&glyph[0], &apple[0], 3);
if (lowerIcon == 1)
memcpy(&glyph[4], &block[0], 3);
else if (lowerIcon == 2)
memcpy(&glyph[4], &apple[0], 3);
lcd.createChar(i, glyph);
}
delay(1); //Wait for the CGRAM to be written
}
void graphic_clear() {
memset(graphicRam, 0, sizeof(graphicRam));
}
void graphic_add_item(uint8_t x, uint8_t y, enum DisplayItem item) {
graphicRam[x / (8 / 2)][y] |= (uint8_t)item * (1 << ((x % (8 / 2)) * 2));
}
void graphic_flush() {
lcd.clear();
for (size_t x = 0; x < 16; x++) {
for (size_t y = 0; y < 2; y++) {
enum DisplayItem upperItem = (DisplayItem)((graphicRam[x / (8 / 2)][y * 2] >> ((x % (8 / 2)) * 2)) & 0x3);
enum DisplayItem lowerItem = (DisplayItem)((graphicRam[x / (8 / 2)][y * 2 + 1] >> ((x % (8 / 2)) * 2)) & 0x3);
if (upperItem >= GRAPHIC_ITEM_NUM)
upperItem = GRAPHIC_ITEM_B;
if (lowerItem >= GRAPHIC_ITEM_NUM)
lowerItem = GRAPHIC_ITEM_B;
lcd.setCursor(x, y);
if (upperItem == 0 && lowerItem == 0)
lcd.write(' ');
else
lcd.write(byte((uint8_t)upperItem * 3 + (uint8_t)lowerItem - 1));
}
}
}
void game_new_apple_pos()
{
bool validApple = true;
do {
applePos[0] = rand() % GRAPHIC_WIDTH;
applePos[1] = rand() % GRAPHIC_HEIGHT;
validApple = true;
for (size_t i = 0; i < snakeLength; i++)
{
if (applePos[0] == snakePosHistory[i][0] && applePos[1] == snakePosHistory[i][1]) {
validApple = false;
break;
}
}
} while (!validApple);
}
void game_init() {
srand(micros());
gameUpdateInterval = 1000;
gameState = GAME_PLAY;
snakePosHistory[0][0] = 3; snakePosHistory[0][1] = 1;
snakePosHistory[1][0] = 2; snakePosHistory[1][1] = 1;
snakePosHistory[2][0] = 1; snakePosHistory[2][1] = 1;
snakePosHistory[3][0] = 0; snakePosHistory[3][1] = 1;
snakeLength = 4;
snakeDirection = SNAKE_RIGHT;
game_new_apple_pos();
thisFrameControlUpdated = false;
}
void game_calculate_logic() {
if (gameState != GAME_PLAY) //Game over. Don't bother calculating game logic.
return;
//Calculate the movement of the tail
for (size_t i = snakeLength; i >= 1; i--) { //We intentionally use i=snakeLength instead of i=snakeLength-1 so that the snake will be lengthened right after it eats the apple
memcpy(snakePosHistory[i], snakePosHistory[i-1], sizeof(snakePosHistory[i]));
}
//Calculate the head movement
memcpy(snakePosHistory[0], snakePosHistory[1], sizeof(snakePosHistory[0]));
switch (snakeDirection) {
case SNAKE_LEFT: snakePosHistory[0][0]--; break;
case SNAKE_UP: snakePosHistory[0][1]--; break;
case SNAKE_RIGHT: snakePosHistory[0][0]++; break;
case SNAKE_DOWN: snakePosHistory[0][1]++; break;
}
//Look for wall collision
if (snakePosHistory[0][0] < 0 || snakePosHistory[0][0] >= GRAPHIC_WIDTH || snakePosHistory[0][1] < 0 || snakePosHistory[0][1] >= GRAPHIC_HEIGHT) {
gameState = GAME_LOSE;
return;
}
//Look for self collision
for (size_t i = 1; i < snakeLength; i++) {
if (snakePosHistory[0][0] == snakePosHistory[i][0] && snakePosHistory[0][1] == snakePosHistory[i][1]) {
gameState = GAME_LOSE;
return;
}
}
if (snakePosHistory[0][0] == applePos[0] && snakePosHistory[0][1] == applePos[1]) {
snakeLength++;
gameUpdateInterval = gameUpdateInterval * 9 / 10;
if (snakeLength >= sizeof(snakePosHistory) / sizeof(*snakePosHistory))
gameState = GAME_WIN;
else
game_new_apple_pos();
}
}
void game_calculate_display() {
graphic_clear();
switch (gameState) {
case GAME_LOSE:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Boo! You lose!");
lcd.setCursor(0, 1);
lcd.print("Length: ");
lcd.setCursor(8, 1);
lcd.print(snakeLength);
break;
case GAME_WIN:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("You won. Congratz!");
lcd.setCursor(0, 1);
lcd.print("Length: ");
lcd.setCursor(8, 1);
lcd.print(snakeLength);
break;
case GAME_PLAY:
for (size_t i = 0; i < snakeLength; i++)
graphic_add_item(snakePosHistory[i][0], snakePosHistory[i][1], GRAPHIC_ITEM_A);
graphic_add_item(applePos[0], applePos[1], GRAPHIC_ITEM_B);
graphic_flush();
break;
case GAME_MENU:
//Do nothing
break;
}
}
void setup() {
Serial.begin(115200);
prefs.begin("snake1"); // 打开命名空间snake
Serial.print(prefs.getInt("gameState", 5));
if (prefs.getInt("gameState", 0) == 1) {
Serial.println("turn");
prefs.getBytes("snakePosHistory", &snakePosHistory, prefs.getBytesLength("snakePosHistory"));
snakeLength = (size_t) prefs.getUChar("snakeLength", 0);
snakeDirection = Sd(prefs.getInt("snakeDirection"));
prefs.getBytes("applePos", &applePos, prefs.getBytesLength("applePos"));
lastGameUpdateTick = prefs.getULong("lastGameUpdateTick", 0);
gameUpdateInterval = prefs.getULong("gameUpdateInterval", 1000);
gameState = Gs(prefs.getInt("gameState", 0));
prefs.getBytes("graphicRam", &graphicRam, prefs.getBytesLength("graphicRam"));
thisFrameControlUpdated = prefs.getBool("thisFrameControlUpdated");
switch (gameState) {
case GAME_PLAY:
switch (snakeDirection) {
case SNAKE_LEFT: snakeDirection = SNAKE_DOWN; break;
case SNAKE_UP: snakeDirection = SNAKE_LEFT; break;
case SNAKE_RIGHT: snakeDirection = SNAKE_UP; break;
case SNAKE_DOWN: snakeDirection = SNAKE_RIGHT; break;
}
thisFrameControlUpdated = true;
break;
case GAME_MENU:
game_init();
break;
graphic_generate_characters();
}
} else {
Wire.begin(0,21); //LCD init SDA(IO0) SCL(IO21)
lcd.begin(16, 2);
lcd.backlight();
lcd.clear();
lcd.home();
lcd.print("1602 LCD Snake");
lcd.setCursor(0, 1);
lcd.print("Press RESET to rotate snake");
gameState = GAME_MENU;
delay(1000);
Serial.println("init");
graphic_generate_characters();
game_init();
}
}
void loop() {
lcd.setCursor(0, 0);
lcd.setCursor(8, 0);
if (millis() - lastGameUpdateTick > gameUpdateInterval) {
Serial.println("loop");
game_calculate_logic();
game_calculate_display();
lastGameUpdateTick = millis();
thisFrameControlUpdated = false;
prefs.putBytes("snakePosHistory", &snakePosHistory, sizeof("snakePosHistory"));
prefs.putUChar("snakeLength", (uint8_t) snakeLength);
prefs.putInt("snakeDirection", (int)snakeDirection);
prefs.putBytes("applePos", &applePos, sizeof("applePos"));
prefs.putULong("lastGameUpdateTick", lastGameUpdateTick);
prefs.putULong("gameUpdateInterval", gameUpdateInterval);
Serial.println((int)gameState);
prefs.putInt("gameState", (int)gameState);
prefs.putBytes("graphicRam", &graphicRam, sizeof("graphicRam"));
prefs.putBool("thisFrameControlUpdated", thisFrameControlUpdated);
}
}