Snake Game Console Mini

by Arnov Sharma in Circuits > Microcontrollers

621 Views, 13 Favorites, 0 Comments

Snake Game Console Mini

Classic Snake Game DIY Console
49.gif
50.gif
36.gif
39.gif
thumbb.JPG

Greetings Everyone and welcome back. here's something FUN.

Gaming nostalgia meets modern innovation in our latest project—the Mini Snake Game Console. This scaled-down version builds upon our previous 64x32 HUB75 RGB matrix panel-based Snake Game Console, but now, we’ve taken that concept and shrunk it down to a truly handheld gaming device.

The goal was to create a smaller version of our original Snake Game Console that could run a similar version of our Snake Game Code. While the core game logic remains the same, several modifications were necessary, including adjusting display dimensions, resizing the snake, and introducing key enhancements. Now, the snake features a gradient color, adding more visual appeal, and a buzzer-driven beep whenever a food (red dot) is consumed—bringing even more satisfaction to the gameplay experience.

If the snake bites itself, the game-over sequence is initiated, and we are greeted with a game-over screen displaying the total number of food items consumed. The game-over screen remains visible for five seconds before the game automatically restarts.

Powered by Waveshare's ESP32-S3-LCD-1.69, an ESP32 S3-based setup with a 1.69-inch ST7789V2 display, this mini console offers portability without sacrificing performance. Thanks to an onboard 500mAh Li-ion cell, you can game anywhere, anytime. All components, including the ESP32 board and custom button board, are housed inside a custom-designed enclosure, which we built in Fusion 360 and then 3D printed using the Creality K10 Max.

This article explores the whole build process of this Snake Game console and how you can rebuild it in a few easy steps, so let's get started with the build.

Supplies

  1. Custom PCB (Provided by PCBWAY)
  2. ESP32 S3 1.69 Inch Display (Got from PCBWAY GIFTSHOP)
  3. Push Buttons
  4. 500mAh 3.7V 14500 Li-ion Cell
  5. Push Switch
  6. M2 Screws
  7. Connecting wires
  8. Hot Glue
  9. 3D printed Enclosure

Snake Game Console Project and PICO Blasters

04.gif
05.gif
PICO BLASTER - Raspberry Pi PICO 2 RGB MATRIX 64x32
PICO 2 Powered Snake Game Console, 64x32 P3 Matrix Panel

Let's have a look at The Snake Game Console first, which was a compact, portable system powered by a Raspberry Pi PICO, featuring a 64x32 RGB matrix panel and a 3D-printed enclosure. It runs a classic Snake game, where you guide the snake using four-directional buttons, chase down random red dots for points, and try to avoid crashing into yourself. Plus, it's got an onboard battery, so you can take it anywhere and play on the go.

Then came PICO Blaster, which takes the same hardware setup and turns it into a fast-paced Space Invaders-style shooter. With an additional custom control board, revamped gameplay mechanics, and color-coded projectiles, this game is all about reflexes and strategy.

Players navigate a spaceship, dodging incoming attacks and firing back using two types of weaponsrapid-fire bullets and a powerful blaster that wipes out everything in its path (though with a cooldown to keep things interesting!).

Both of these games were written from scratch and were a pain to code, really. Most of the development time was spent in creating the game logic and the hardware part for both devices was super easy and was prepared in less than a day.

For the Mini Version, i have only ported the Snake Game. we could make more games for the device, which will be an idea for a future project.

Mini Version Design

MSGC.2.png
MSGC.3.png
06.gif
44.gif
45.gif
Screenshot 2025-05-18 115551.png
46.gif
MSGC.4.png
Screenshot 2025-05-18 115624.png
Screenshot 2025-05-18 115638.png
BOARD2.png

For the design of this project, we first imported the 3D model of our ESP32 Display into Fusion360, along with the Cad file of our Button PCB with Buttons, as well as the battery and push switch Cad files.

Next, we organized the components in a logical and practical layout, positioning the switch PCB beneath the display, placing the battery behind both the display and the switch PCB, and situating the push switch on the rear side just above the battery. Once everything was arranged, we proceeded to design an enclosure that encapsulates all the components and unifies them seamlessly. For the aesthetic, I opted for a boxy, retro design reminiscent of the 1990s, drawing inspiration from classic Nokia phones—elements of which can be seen in its form.

The enclosure was prepared and separated into two halves: the front enclosure and the lid part. The front enclosure houses the ESP32 display board and the Switch PCB. The ESP32 Display is pressure fitted in its position; we have also included mounting holes in case someone wishes to tighten the display properly with the enclosure. The switch PCB is fastened in place with four M2 screws.

The Lid section holds both the battery and the power switch.

Both the front enclosure and the lid section are joined together with four M2 screws since we created four screw bosses on the front enclosure and four holes on the lid to slide the screws through and attach the two pieces together.

For the 3D print, we exported the mesh files for both pieces and printed them on our new Creality K10 max with a 0.4mm nozzle and 25% infill White Hyper PLA.

ESP32 Display Dev Board

42.gif
43.gif
2024-07-19_104656.png
IMG_20240824_223007.jpg
IMG_20240824_223024.jpg
41.gif

We are using the ESP32-S3-LCD-1.69 in our project, as it is a low-cost, high-performance MCU board that perfectly suits our needs.

Equipped with a 1.69-inch capacitive LCD screen, a lithium battery charging chip, and a six-axis sensor with a three-axis accelerometer and gyroscope, this board is packed with features that enhance our Snake Game Console.

It includes an RTC chip for timing functions, a QMI8658 inertial measurement unit (IMU) for motion tracking, and an ETA6098 lithium battery charging chip for long-term power management.

The onboard buzzer provides audio feedback, while the Type-C interface allows for easy demo flashing and log printing. Additionally, the BOOT and RST buttons simplify resets and firmware downloads, and a customizable function button enables multiple input methods, including single, double, and long presses.

The ST7789V2 LCD controller supports a 240 × RGB × 320 resolution, though its active display area is 240(H) × RGB × 280(V). It supports RGB444, RGB565, and RGB666 color formats, with our project utilizing RGB565 for optimal visual output.

The four-wire SPI interface ensures efficient GPIO usage and fast communication speed, while the rounded corners of the display contribute to its sleek, compact design.

you can checkout more about this display from its Wiki page https://www.waveshare.com/wiki/ESP32-S3-LCD-1.69

As for sourcing this display, we got it from PCBWAY's Gift Shop, which is an electronic marketplace where you can find all sorts of electronic devices and modules for their genuine price.

https://pcbway.com/s/cr77wO

Switch PCB Design

Screenshot 2025-02-26 155430.png
BUTTON BOARD_page-0001.jpg
BOARD2.png
Screenshot 2025-02-26 155338.png

In this project, we repurpose one of our Button Boards from our previous Snake Game console project. This PCB features four buttons, each connected to GND, and four output pins, which will be connected to the ESP32's GPIO pins. By pushing each button, we pull the GPIO Pin down to GND, and the Microcontroller registers this change in button status.

The board design was finalized by adding buttons in their proper locations and connecting traces in the correct order, after which we exported the Gerber Data for this PCB and sent it to PCBWAY for samples.

PCBWAY

01.gif
47.gif
IMG_E5485.JPG

Following the completion of the board design, we ordered a white solder mask with black silkscreen and submitted the PCB's Gerber data on the PCBWAY quote page.

After placing the order, the PCBs were received within a week, and the PCB quality was pretty great.

Over the past ten years, PCBWay has distinguished themselves by providing outstanding PCB manufacturing and assembly services, becoming a trusted partner for countless engineers and designers worldwide.

Their commitment to quality and customer satisfaction has been unwavering, leading to significant growth and expansion.

You guys can check out PCBWAY if you want great PCB service at an affordable rate.

Switch PCB Assembly

01.gif
02.gif

Button Board assembly process was super simple. we first position the push buttons from the top side of the board, and then we solder their pads from the bottom side using a soldering iron.

Snake Game Code

Before moving ahead with the assembly process, let's have a deep dive into the code for this project.

#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);
}


Let's have an in-depth breakdown of this code and let's start with the Include library and display setup.

#include <Arduino.h>
#include "Arduino_GFX_Library.h"
#include "pin_config.h"

These include necessary libraries for ESP32 and also define important hardware pin mappings saved in the pin_config file.

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);

The above section sets up the SPI bus and initializes the ST7789 LCD display.

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

We next define GPIO pins for user controls and a buzzer.

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

Next comes the game grid size and individual cell size.

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

This controls Food placement boundaries within the grid.

struct SnakeSegment {
int x;
int y;
};
SnakeSegment snake[100]; // Stores segments of the snake
int snakeLength = 5;
int dx = 1, dy = 0;
uint16_t foodColor = RED;
int foodX, foodY, foodEaten = 0;
bool gameOver = false;

This section Defines the snake structure and necessary variables for movement, food, and game state.

Now comes the Game Logic functions which contain two major sections: Food Placement and Collision Detection.

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

This is the food placement function that Randomly assigns a new position for food when eaten.

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;
}

This is the section that Checks if the snake collides with itself.

Next comes the Game Reset & Scoring section.

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();
}

This section Resets the game state when a new round starts.

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

This Section Updates the score display on the LCD.

Now comes the Sound Effects part of this project, which plays a short beep when food is consumed and play a melody when game ends.

void beepFood() {
tone(BUZZER_PIN, 1200, 100); // Short beep when food is eaten
}

This plays a short beep when food is consumed.

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);
}

This section plays a melody when the games end.

Now comes the Game Display & Graphics part that shows a "Game Over" screen and resets the game.

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

gfx->setTextSize(4);
gfx->setTextColor(RED);
String gameOverText = "GAME OVER";
gfx->setCursor(screenMidX - (gameOverText.length() * 24 / 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();
}

Setup Function Initializes serial communication, display, and button pins.

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();
}

At last we have the most important section of this code, which is the Game Loop. This section Handles movement, collisions, food consumption, and game speed.

void loop() {
if (gameOver) return;

// Handle button inputs
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; }

// Update snake position
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;

// Collision checks & food consumption
if (checkSelfCollision()) {
gameOver = true;
showGameOverScreen();
return;
}

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

delay(150); // Controls speed
}

We've attached the pin_config.h file among the attachments, so make sure it's in the same folder as the main sketch.

Wiring Process

WIRING.png
08.gif
09.gif
10.gif
11.gif
  1. The wiring process begins by connecting five wires to the ESP32 Dev board's GPIO2, GPIO10, GPIO16, GPIO18, and GND terminals.
  2. Next, we connected the GND wire to the GND terminal of the Button board.
  3. The GPIO2 wire is attached to the switchboard's left terminal.
  4. GPIO10 connects to the right terminal.
  5. GPIO16 connects to the Down terminal.
  6. GPIO18 connects to the Up terminal.

Power Source Assembly

12.gif
13.gif
14.gif
15.gif

We're utilizing a 3.7V 500mAh 14500 Li-ion cell as the power source for this project.

They usually do not arrive with wires spot-welded, forcing people to manually solder wires to battery terminals, which is a bad practice that can lead to a fire or blast. It will undoubtedly reduce battery life and capacity; thus, we should avoid soldering wires directly to the terminals of lithium cells using a soldering iron.

  1. We now begin the power source assembly process by connecting the positive wire of the battery to the NC of the push switch.
  2. We add another wire to the common terminal of the Push Switch. We have included a push switch between the battery's positive terminals so that battery power can be turned off with this switch.
  3. Next, we connected the battery's positive and negative connection wires to the Battery connector on the ESP32 board.
  4. By making this connection, we can now power the setup with our Li-ion battery.

Final Assembly

17.gif
18.gif
19.gif
20.gif
21.gif
22.gif
23.gif
24.gif
25.gif
26.gif
27.gif
28.gif
  1. The final assembly begins with the placement of the ESP32 Display on the front enclosure, which we push in place from the front side.
  2. Next, we pass the Switch PCB from the inside of the Front Enclosure and place it on the front. This will hide the connection wire between the ESP32 Board and the Switch PCB.
  3. The switch PCB is then secured in place using four M2 screws.
  4. We now install the lithium battery inside the Lid section and the Push switch in its right location.
  5. Using a hot glue gun, we apply hot glue to the Push Switch and the Battery to secure them in place.
  6. We next apply hot glue to the rear side of the display over the GPIO Port where we have connected wires for the switchboard, preventing wires from being pulled from the ESP32 traces during the assembling process.
  7. Both halves of the enclosure are now positioned together, and four M2 screws are used to connect them.

Our Snake Game Console Mini device has been assembled.

RESULT

Classic Snake Game DIY Console
29.gif
32.gif
36.gif
40.gif

Here's the end result of this small build: the Mini Snake Game Console, a tiny, handheld gaming device that brings the classic Snake game to life in a compact form. The whole design has this nostalgic vibe, kind of like the old Nokia 6110, instantly bringing back memories of early mobile gaming.

Even though it’s small, the console runs smoothly, with responsive controls and a vibrant display powered by the ESP32-S3-LCD-1.69. The gradient-colored snake, the buzzer beep when you eat food, and the custom button board all come together to make it feel polished and fun to play. And the 3D-printed enclosure, designed in Fusion 360, gives it a solid build that fits comfortably in your hand.

Thanks to the onboard 500mAh Li-ion battery, it’s totally portable, letting you game anywhere, anytime, without needing a constant power source. The bright display and smooth gameplay make it just as engaging as the bigger version, but now it’s way more convenient to carry around.

Whats Next

39.gif

For the next iteration, I'd like to focus more on the game itself, which can be improved further by adding obstacles such as a wall that, when touched by the snake, ends the game. As the snake becomes larger, its speed increases, which is one of the adjustments I'd like to add in the next version of the project. Also, I believe I can create a better and smaller body if we utilize a LiPo cell in our future project, as well as a custom PCB with SMD buttons to increase the size of the PCB and electronics even more.

Please let me know if you require any additional assistance; all the documents, files, and code are included in the article.

In addition, we appreciate PCBWAY's support of this project. Visit them for a variety of PCB-related services, such as stencil and PCB assembly services, as well as 3D printing services

Thanks for reaching this far, and I will be back with a new project pretty soon.

Peace.