The purpose of this project is to be able to play a snake game with a joystick. The game is displayed on the JDMO.96A-1 display and can be played using a joystick to set the direction in which the snake will move. If the snake eats himself a “GAME OVER” text will be displayed, otherwise, the snake grows after each food that it ate.
I used the following components for this project:
This function was used to start the display and clear it, and after that to initialize the snake and set its color to white.
void setup() {
Serial.begin(115200);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;)
;
}
display.clearDisplay(); // Clear the display buffer
// Initialize the snake
for (int i = snakeSize - 1; i >= 0; i--) {
snakeX[i] = snakeSize - 1 - i;
snakeY[i] = 0;
}
// Set text color and size
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
// Start the game loop
gameLoop();
}
Here I update the snake position then I draw it. After the snake is on the screen, I spawn a pixel random on the screen that can be eaten by the snake.
void gameLoop() {
while (true) {
// Update the snake position
updateSnake();
// Draw the snake
drawSnake();
// Check for joystick input
checkJoystick();
// Check if the snake eats the pixel
if (snakeX[0] == pixelX && snakeY[0] == pixelY) {
snakeSize++;
spawnPixel(); // Spawn a new pixel after eating the current one
}
delay(200);
}
}
On this function I update the entire snake positions (each of the pixels that make the body of the snake). Then I move the head on the direction of the pixel, I get this direction from a function called: checkJoystick. After that, I check if the snake hit the boundaries of the screen to teleport on the other side of the screen.
void updateSnake() {
// Update the entire snake's position
for (int i = snakeSize - 1; i > 0; i--) {
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
// Move the head of the snake based on the direction
switch (direction) {
case 0: // Right
snakeX[0]++;
break;
case 1: // Down
snakeY[0]++;
break;
case 2: // Left
snakeX[0]--;
break;
case 3: // Up
snakeY[0]--;
break;
}
// Check for collisions with the screen boundaries
if (snakeX[0] >= SCREEN_WIDTH / 8) {
snakeX[0] = 0;
} else if (snakeX[0] < 0) {
snakeX[0] = SCREEN_WIDTH / 8 - 1;
}
if (snakeY[0] >= SCREEN_HEIGHT / 8) {
snakeY[0] = 0;
} else if (snakeY[0] < 0) {
snakeY[0] = SCREEN_HEIGHT / 8 - 1;
}
for (int i = 0; i < snakeSize; i++) {
Serial.println("Before collision (" + String(snakeX[i]) + "," + String(snakeY[i]) + ")");
}
// Check for collisions with the snake's body, excluding the head
for (int i = 1; i < snakeSize; i++) {
if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
// Snake head collided with its body, end the game
endGame();
}
}
}
Used to draw the snake on the screen.
void drawSnake() {
// Clear the display
display.clearDisplay();
// Draw the snake
for (int i = 0; i < snakeSize; i++) {
display.fillRect(snakeX[i] * 8, snakeY[i] * 8, 8, 8, SSD1306_WHITE);
}
// Draw the pixel
display.fillRect(pixelX * 8, pixelY * 8, 8, 8, SSD1306_WHITE);
// Display the buffer
display.display();
}
I used this function to check the position of the joystick so I can give the snake the direction that I want it to move to. In this code I also made impossible for the snake to go in the opposite direction of the current one
void checkJoystick() {
int xValue = analogRead(JOYSTICK_X);
int yValue = analogRead(JOYSTICK_Y);
int OK = 0;
if (xValue < JOYSTICK_THRESHOLD && lastXValue <= JOYSTICK_THRESHOLD) {
// Move right
if (direction != 0 && direction != 2) {
direction = 0;
OK = 1;
}
} else if (xValue > JOYSTICK_THRESHOLD2 && lastXValue >= -JOYSTICK_THRESHOLD) {
// Move left
if (direction != 2 && direction != 0) {
direction = 2;
OK = 1;
}
}
if (yValue < JOYSTICK_THRESHOLD && lastYValue <= JOYSTICK_THRESHOLD && OK == 0) {
// Move up
if (direction != 3 && direction != 1) {
direction = 3;
}
} else if (yValue > JOYSTICK_THRESHOLD2 && lastYValue >= -JOYSTICK_THRESHOLD && OK == 0) {
// Move down
if (direction != 1 && direction != 3) {
direction = 1;
}
}
lastXValue = xValue;
lastYValue = yValue;
}
I spawn the food of the snake as a pixel random on the screen.
void spawnPixel() {
// Generate random coordinates for the pixel
pixelX = random(SCREEN_WIDTH / 8);
pixelY = random(SCREEN_HEIGHT / 8);
// Ensure the pixel does not spawn on the snake
for (int i = 0; i < snakeSize; i++) {
while (pixelX == snakeX[i] && pixelY == snakeY[i]) {
pixelX = random(SCREEN_WIDTH / 8);
pixelY = random(SCREEN_HEIGHT / 8);
}
}
// Draw the pixel on the display
display.fillRect(pixelX * 8, pixelY * 8, 8, 8, SSD1306_WHITE);
display.display();
}
Print on the screen the GAME OVER message if the snake hit himself.
void endGame() {
// Add your game-over logic here
display.clearDisplay();
display.setCursor(8,8);
display.print("GAME OVER!");
display.display();
delay(5000); // Display "You Lose" for 5 seconds
// Print snake coordinates for debugging
Serial.println("Snake Coordinates:");
for (int i = 0; i < snakeSize; i++) {
Serial.println("(" + String(snakeX[i]) + "," + String(snakeY[i]) + ")");
}
while (true) {
// Do nothing
}
}
Here I declared the screen and the pins that I used. I also declared 2 THRESHOLDS because when the joystick was on the middle, I constantly get values between 1850 and 1900 and I don’t want that those values to influent the movement. On the end I declared snake characteristics.
#define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 32 // Define the I2C address for the display #define OLED_ADDR 0x3C Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_ADDR); #define JOYSTICK_X A5 #define JOYSTICK_Y A6 #define JOYSTICK_THRESHOLD 1800 #define JOYSTICK_THRESHOLD2 2000 int snakeSize = 3; int snakeX[100], snakeY[100]; int direction = 0; // 0: right, 1: down, 2: left, 3: up
The library that I used are:
#include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h>
And mostly I used the for the LCD.
As a conclusion for the project, I realized a snake game that can be controlled using a joystick. The purpose of the game is to eat as many food as possible and to not touch the rest of the body.
https://www.optimusdigital.ro/ro/senzori-senzori-de-atingere/742-modul-joystick-ps2-biaxial-negru-cu-5-pini.html?search_query=joystick&results=42
https://cleste.ro/ecra-oled-0-96-inch.html
https://cleste.ro/placa-dezvoltare-nodemcu-wifi-bluetooth-esp32.html
https://randomnerdtutorials.com/esp32-pinout-reference-gpios/
https://esp32io.com/tutorials/esp32-joystick