#include <LiquidCrystal_I2C.h>
#define GRAPHIC_WIDTH 16
#define GRAPHIC_HEIGHT 4
#define BUZZER_PIN 5
#define BUTTON_LEFT 2
#define BUTTON_RIGHT 4
#define MAX_SNAKE_LEN 65
struct Coord {
byte x;
byte y;
};
enum Direction { LEFT, UP, RIGHT, DOWN } snakeDirection;
enum GameStates { START, GAME, LOSE } gameStates;
unsigned long lastFrameUpdate = 0;
unsigned long oneFrameDuration = 1000;
bool isFrameOver = false;
bool isChecked = false;
byte matrix[16][2] = {0};
byte snakeLen = 3; // Длинна змеи
Coord snakeCoords[MAX_SNAKE_LEN]; //snakeCoord[0] - голова;
Coord appleCoords;
//snakeCoord[snakeLen] - хвост;
LiquidCrystal_I2C lcd(0x27, 16, 2);
void initCustomSymbols();
void initStartSnakeCoords();
void drawSnakeAndApple();
void moveSnake();
void checkDirection();
void generateAppleCoords();
void tryEatApple();
void playStartMelody();
void playLoseMelody();
void setup() {
lcd.init();
lcd.backlight();
initCustomSymbols();
initStartSnakeCoords();
gameStates = START;
lcd.setCursor(1, 0);
lcd.print("Snake Game");
playStartMelody();
pinMode(2, INPUT);
pinMode(4, INPUT);
}
void loop() {
switch (gameStates) {
case START:
if (digitalRead(BUTTON_LEFT) == HIGH || digitalRead(BUTTON_RIGHT) == HIGH){
gameStates = GAME;
srand(millis());
generateAppleCoords();
delay(500);
}
break;
case GAME:
checkDirection();
if (millis() - lastFrameUpdate > oneFrameDuration){
lcd.clear();
moveSnake();
tryEatApple();
drawSnakeAndApple();
lastFrameUpdate = millis();
isChecked = false;
memset(matrix, 0, sizeof(byte) * 32);
}
break;
case LOSE:
lcd.clear();
lcd.setCursor(1, 0);
lcd.print("You lose!");
lcd.setCursor(1, 1);
lcd.print("Length: ");
lcd.print(snakeLen);
playLoseMelody();
delay(1000000);
break;
}
}
void playStartMelody() {
int melody[] = {262, 294, 330, 349, 392, 440, 494, 523};
int noteDuration = 200;
for (int i = 0; i < 8; i++) {
tone(BUZZER_PIN, melody[i], noteDuration);
delay(noteDuration * 1.3);
}
}
void playLoseMelody() {
int melody[] = {523, 494, 440, 392, 349, 330, 294, 262};
int noteDuration = 200;
for (int i = 0; i < 8; i++) {
tone(BUZZER_PIN, melody[i], noteDuration);
delay(noteDuration * 1.3);
}
}
void tryEatApple() {
if (appleCoords.x == snakeCoords[0].x && appleCoords.y == snakeCoords[0].y) {
tone(BUZZER_PIN, 800, 50);
snakeLen++;
generateAppleCoords();
if (oneFrameDuration > 150){
oneFrameDuration -= 50;
}
}
}
void generateAppleCoords() {
bool validApple = true;
do {
appleCoords.x = rand() % GRAPHIC_WIDTH;
appleCoords.y = rand() % GRAPHIC_HEIGHT;
validApple = true;
for (byte i = 0; i < snakeLen; i++) {
if (appleCoords.x == snakeCoords[i].x && appleCoords.y == snakeCoords[i].y) {
validApple = false;
break;
}
}
} while (!validApple);
}
void checkDirection() {
if (!isChecked){
if (digitalRead(BUTTON_LEFT) == HIGH){
isChecked = true;
switch (snakeDirection){
case LEFT:
snakeDirection = DOWN;
break;
case UP:
snakeDirection = LEFT;
break;
case RIGHT:
snakeDirection = UP;
break;
case DOWN:
snakeDirection = RIGHT;
break;
}
}
if (digitalRead(BUTTON_RIGHT) == HIGH){
isChecked = true;
switch (snakeDirection){
case LEFT:
snakeDirection = UP;
break;
case UP:
snakeDirection = RIGHT;
break;
case RIGHT:
snakeDirection = DOWN;
break;
case DOWN:
snakeDirection = LEFT;
break;
}
}
}
}
void moveSnake() {
Coord oldSnakeCoords[MAX_SNAKE_LEN];
memcpy(oldSnakeCoords, snakeCoords, sizeof(snakeCoords));
for(byte i = 1; i < snakeLen; i++){
snakeCoords[i] = oldSnakeCoords[i-1];
matrix[snakeCoords[i].x][snakeCoords[i].y/2]++;
}
switch (snakeDirection) {
case LEFT:
snakeCoords[0].x = (oldSnakeCoords[0].x == 0 ? 15 : oldSnakeCoords[0].x - 1);
snakeCoords[0].y = (oldSnakeCoords[0].y);
break;
case UP:
snakeCoords[0].x = (oldSnakeCoords[0].x);
snakeCoords[0].y = (oldSnakeCoords[0].y == 0 ? 3 : oldSnakeCoords[0].y - 1);
break;
case RIGHT:
snakeCoords[0].x = ((oldSnakeCoords[0].x + 1)%16);
snakeCoords[0].y = (oldSnakeCoords[0].y);
break;
case DOWN:
snakeCoords[0].x = (oldSnakeCoords[0].x);
snakeCoords[0].y = ((oldSnakeCoords[0].y + 1)%4);
break;
}
matrix[snakeCoords[0].x][snakeCoords[0].y/2]++;
for (byte i = 1; i < snakeLen; i++) {
if (snakeCoords[0].x == snakeCoords[i].x &&
snakeCoords[0].y == snakeCoords[i].y) {
gameStates = LOSE;
return;
}
}
}
void drawSnakeAndApple() {
lcd.setCursor(appleCoords.x, appleCoords.y/2);
switch (appleCoords.y%2){
case 0:
lcd.write(4);
break;
case 1:
lcd.write(5);
break;
}
matrix[appleCoords.x][appleCoords.y/2]++;
for (byte i = 0; i < snakeLen; i++){
lcd.setCursor(snakeCoords[i].x, snakeCoords[i].y/2);
switch (matrix[snakeCoords[i].x][snakeCoords[i].y/2]) {
case 1:
if (snakeCoords[i].y == 0 || snakeCoords[i].y == 2) {
lcd.write(1);
}
else {
lcd.write(2);
}
break;
case 2:
if (appleCoords.x == snakeCoords[i].x && appleCoords.y/2 == snakeCoords[i].y/2) {
if (snakeCoords[i].y == 0 || snakeCoords[i].y == 2) {
lcd.write(6);
}
else {
lcd.write(7);
}
}
else {
lcd.write(3);
}
break;
}
}
}
void initStartSnakeCoords(){
snakeCoords[0].x = 3;
snakeCoords[0].y = 0;
snakeCoords[1].x = 2;
snakeCoords[1].y = 0;
snakeCoords[2].x = 1;
snakeCoords[2].y = 0;
snakeDirection = RIGHT;
}
void initCustomSymbols() {
byte customSymbol0[] = {
B00000,
B00000,
B00000,
B00000,
B00000,
B00000,
B00000,
B00000
};
byte customSymbol1[] = {
B01110,
B01110,
B01110,
B00000,
B00000,
B00000,
B00000,
B00000
};
byte customSymbol2[] = {
B00000,
B00000,
B00000,
B00000,
B01110,
B01110,
B01110,
B00000
};
byte customSymbol3[] = {
B01110,
B01110,
B01110,
B00000,
B01110,
B01110,
B01110,
B00000
};
byte customSymbol4[] = {
B00100,
B01010,
B00100,
B00000,
B00000,
B00000,
B00000,
B00000
};
byte customSymbol5[] = {
B00000,
B00000,
B00000,
B00000,
B00100,
B01010,
B00100,
B00000
};
byte customSymbol6[] = {
B01110,
B01110,
B01110,
B00000,
B00100,
B01010,
B00100,
B00000
};
byte customSymbol7[] = {
B00100,
B01010,
B00100,
B00000,
B01110,
B01110,
B01110,
B00000
};
lcd.createChar(0, customSymbol0);
lcd.createChar(1, customSymbol1);
lcd.createChar(2, customSymbol2);
lcd.createChar(3, customSymbol3);
lcd.createChar(4, customSymbol4);
lcd.createChar(5, customSymbol5);
lcd.createChar(6, customSymbol6);
lcd.createChar(7, customSymbol7);
}