Arnov Sharma
Published © MIT

Snake Game Console Mini

Small Version of my previously created Snake Game Console Project

BeginnerFull instructions provided1 hour349
Snake Game Console Mini

Things used in this project

Hardware components

PCBWay esp32 s3 Display
×1
PCBWay Custom PCB
PCBWay Custom PCB
×1

Software apps and online services

Fusion
Autodesk Fusion
Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

cad file

Schematics

WIRING

Code

Main Code

C/C++
#include <Arduino.h>
#include "Arduino_GFX_Library.h"
#include "pin_config.h"

// Display setup
Arduino_DataBus *bus = new Arduino_ESP32SPI(LCD_DC, LCD_CS, LCD_SCK, LCD_MOSI);
Arduino_GFX *gfx = new Arduino_ST7789(bus, LCD_RST, 0, true, LCD_WIDTH, LCD_HEIGHT, 0, 20, 0, 0);

// GPIO definitions
#define BUTTON_UP 18
#define BUTTON_DOWN 16
#define BUTTON_LEFT 2
#define BUTTON_RIGHT 10
#define BUZZER_PIN 33

#define GRID_WIDTH 30
#define GRID_HEIGHT 35
#define CELL_SIZE 8

#define FOOD_X_MIN 5
#define FOOD_X_MAX 25
#define FOOD_Y_MIN 5
#define FOOD_Y_MAX 30

struct SnakeSegment {
    int x;
    int y;
};

SnakeSegment snake[100];
int snakeLength = 5;
int dx = 1, dy = 0;
uint16_t foodColor = RED;
int foodX, foodY;
int foodEaten = 0;
int prevTailX, prevTailY;
bool gameOver = false;

const int charWidth = 12;
const int screenMidX = LCD_WIDTH / 2;

void placeFood() {
    foodX = random(FOOD_X_MIN, FOOD_X_MAX);
    foodY = random(FOOD_Y_MIN, FOOD_Y_MAX);
}

void clearScoreArea() {
    gfx->fillRect(0, 0, LCD_WIDTH, 24, BLACK);
}

void updateFoodCounter() {
    clearScoreArea();
    gfx->setTextSize(2);
    gfx->setTextColor(WHITE);
    String paddedScore = String(foodEaten);
    while (paddedScore.length() < 3) paddedScore = "0" + paddedScore;
    String scoreText = "Food: " + paddedScore;
    int textPixelWidth = scoreText.length() * charWidth;
    int cursorX = screenMidX - (textPixelWidth / 2);
    gfx->setCursor(cursorX, 4);
    gfx->println(scoreText);
}

bool checkSelfCollision() {
    for (int i = 1; i < snakeLength; i++) {
        if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
            return true;
        }
    }
    return false;
}

void resetGame() {
    gfx->fillScreen(BLACK);
    dx = 1;
    dy = 0;
    snakeLength = 5;
    foodEaten = 0;
    gameOver = false;

    for (int i = 0; i < snakeLength; i++) {
        snake[i] = { GRID_WIDTH / 2 - i, GRID_HEIGHT / 2 };
    }

    placeFood();
    updateFoodCounter();
}

void beepFood() {
    tone(BUZZER_PIN, 1200, 100); // Short beep
}

void playGameOverMelody() {
    int melody[] = { 880, 784, 698, 622, 523, 466, 440, 0 };
    int duration = 300;

    for (int i = 0; melody[i] != 0; i++) {
        tone(BUZZER_PIN, melody[i], duration);
        delay(duration + 50);
    }

    noTone(BUZZER_PIN);
}

void showGameOverScreen() {
    gfx->fillScreen(BLACK);

    gfx->setTextSize(4);
    gfx->setTextColor(RED);
    String gameOverText = "GAME OVER";
    int textWidth = gameOverText.length() * 24;
    gfx->setCursor(screenMidX - (textWidth / 2), 90);
    gfx->println(gameOverText);

    gfx->setTextSize(2);
    gfx->setTextColor(WHITE);
    String scoreText = "Score: " + String(foodEaten);
    gfx->setCursor(screenMidX - (scoreText.length() * 12 / 2), 140);
    gfx->println(scoreText);

    playGameOverMelody();
    delay(1000);
    resetGame();
}

void setup() {
    Serial.begin(115200);
    gfx->begin();
    gfx->fillScreen(BLACK);

    pinMode(BUTTON_UP, INPUT_PULLUP);
    pinMode(BUTTON_DOWN, INPUT_PULLUP);
    pinMode(BUTTON_LEFT, INPUT_PULLUP);
    pinMode(BUTTON_RIGHT, INPUT_PULLUP);
    pinMode(BUZZER_PIN, OUTPUT);

    resetGame();
}

void loop() {
    if (gameOver) return;

    if (!digitalRead(BUTTON_UP) && dy == 0) { dx = 0; dy = -1; }
    else if (!digitalRead(BUTTON_DOWN) && dy == 0) { dx = 0; dy = 1; }
    else if (!digitalRead(BUTTON_LEFT) && dx == 0) { dx = -1; dy = 0; }
    else if (!digitalRead(BUTTON_RIGHT) && dx == 0) { dx = 1; dy = 0; }

    prevTailX = snake[snakeLength - 1].x;
    prevTailY = snake[snakeLength - 1].y;
    for (int i = snakeLength - 1; i > 0; i--) {
        snake[i] = snake[i - 1];
    }
    snake[0].x += dx;
    snake[0].y += dy;

    if (snake[0].x >= GRID_WIDTH) snake[0].x = 0;
    if (snake[0].x < 0) snake[0].x = GRID_WIDTH - 1;
    if (snake[0].y >= GRID_HEIGHT) snake[0].y = 0;
    if (snake[0].y < 0) snake[0].y = GRID_HEIGHT - 1;

    if (checkSelfCollision()) {
        gameOver = true;
        showGameOverScreen();
        return;
    }

    if (snake[0].x == foodX && snake[0].y == foodY) {
        snakeLength++;
        foodEaten++;
        placeFood();
        updateFoodCounter();
        beepFood();
    }

    gfx->fillRect(prevTailX * CELL_SIZE, prevTailY * CELL_SIZE, CELL_SIZE, CELL_SIZE, BLACK);

    for (int i = 0; i < snakeLength; i++) {
        uint8_t brightness = map(i, 0, snakeLength, 255, 80);
        uint16_t color = gfx->color565(brightness, 255 - brightness / 3, brightness / 4);
        gfx->fillRoundRect(snake[i].x * CELL_SIZE, snake[i].y * CELL_SIZE, CELL_SIZE, CELL_SIZE, 2, color);
    }

    int pulse = (millis() / 100) % 4;
    int size = CELL_SIZE - 4 + pulse;
    int offset = (CELL_SIZE - size) / 2;
    gfx->fillRoundRect(foodX * CELL_SIZE + offset, foodY * CELL_SIZE + offset, size, size, 2, foodColor);

    //  Speed up if button is held
    int frameDelay = 150;
    if (!digitalRead(BUTTON_UP) || !digitalRead(BUTTON_DOWN) || !digitalRead(BUTTON_LEFT) || !digitalRead(BUTTON_RIGHT)) {
        frameDelay = 80;
    }
    delay(frameDelay);
}

Pin Config File

C/C++
#pragma once

#define XPOWERS_CHIP_AXP2101

#define LCD_DC 4
#define LCD_CS 5
#define LCD_SCK 6
#define LCD_MOSI 7
#define LCD_RST 8
#define LCD_BL 15
#define LCD_WIDTH 240
#define LCD_HEIGHT 280

#define IIC_SDA 11
#define IIC_SCL 10

Credits

Arnov Sharma
352 projects • 360 followers
I'm Arnov. I build, design, and experiment with tech—3D printing, PCB design, and retro consoles are my jam.

Comments