Maze Runner: A Fun Game Built on ESP32 with Joystick and OLED Display

To build a Maze Runner game on your ESP32 with a joystick and a 0.9-inch OLED screen, I’ll outline the basic setup and give you the skeleton code to get started. The game will allow you to control a player (typically represented by a simple symbol) with the joystick on the OLED screen to navigate through a simple maze.

Components:

  1. ESP32 – Used as the microcontroller.
  2. Joystick – For player movement (X and Y axes).
  3. OLED screen – To display the maze and player.
  4. Push Button (optional) – For starting/resetting the game.

Wiring:

  • Joystick:
    • VCC to 3.3V on ESP32.
    • GND to GND on ESP32.
    • X (horizontal) to an analog pin (e.g., GPIO34).
    • Y (vertical) to an analog pin (e.g., GPIO35).
  • OLED Screen (assuming I2C interface):
    • VCC to 3.3V.
    • GND to GND.
    • SDA to GPIO21 (default).
    • SCL to GPIO22 (default).

Libraries Required:

  • Adafruit_SSD1306 (for OLED display).
  • Adafruit_GFX (for graphics functions).
  • Wire (I2C communication).

Install these libraries through the Arduino Library Manager.

Code:

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Joystick pins
#define JOY_X_PIN 34
#define JOY_Y_PIN 35

int playerX = 1;  // Initial X position of player
int playerY = 1;  // Initial Y position of player

// Define a larger, more complex maze layout
int maze[8][16] = {
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
  {1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1},
  {1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1},
  {1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1},
  {1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1},
  {1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1},
  {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1},
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1}
};

// Destination position
int destX = 14;
int destY = 7;

void setup() {
  Serial.begin(115200);
  pinMode(JOY_X_PIN, INPUT);
  pinMode(JOY_Y_PIN, INPUT);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }

  display.display();
  delay(2000);  // Pause for 2 seconds
  display.clearDisplay();
}

void loop() {
  int joyX = analogRead(JOY_X_PIN);
  int joyY = analogRead(JOY_Y_PIN);

  // Map joystick input to movement range (-1 to 1)
  int moveX = map(joyX, 0, 4095, -1, 1);
  int moveY = map(joyY, 0, 4095, -1, 1);

  // Update player position based on joystick movement
  if (moveX > 0 && playerX < 15 && maze[playerY][playerX + 1] == 0) {
    playerX++;
  } else if (moveX < 0 && playerX > 0 && maze[playerY][playerX - 1] == 0) {
    playerX--;
  }

  if (moveY > 0 && playerY < 7 && maze[playerY + 1][playerX] == 0) {
    playerY++;
  } else if (moveY < 0 && playerY > 0 && maze[playerY - 1][playerX] == 0) {
    playerY--;
  }

  // Draw the maze and player
  drawMaze();
  display.fillRect(playerX * 8 + 2, playerY * 8 + 2, 4, 4, WHITE); // Smaller player as a 4x4 square
  
  // Check for win condition
  if (playerX == destX && playerY == destY) {
    display.clearDisplay(); // Clear the display before showing the win message
    display.setTextSize(2);      
    display.setTextColor(SSD1306_WHITE);  
    display.setCursor(30, 25);     
    display.print(F("You Win!"));
    display.display();
    delay(2000); // Wait for a while before resetting the game
    resetGame(); // Reset the game
  }

  display.display();
  delay(100);  // Small delay to make the game playable
}

void drawMaze() {
  // Draw the maze structure (walls and paths)
  for (int y = 0; y < 8; y++) {
    for (int x = 0; x < 16; x++) {
      if (maze[y][x] == 1) {  // 1 represents a wall
        display.fillRect(x * 8, y * 8, 8, 8, WHITE); // Draw walls as squares
      } else {  // 0 represents a path
        display.fillRect(x * 8, y * 8, 8, 8, BLACK); // Fill the path area with black
      }
    }
  }

  // Draw the destination point (goal) in a different color
  display.fillRect(destX * 8, destY * 8, 8, 8, WHITE);  // Mark the destination with a white square
}

void resetGame() {
  playerX = 1; // Reset player to start
  playerY = 1;
  display.clearDisplay();
  drawMaze();
}

Key Points:

  1. Joystick Handling: The joystick is read using analogRead(), and the values are mapped to movement directions.
    • The horizontal joystick axis (JOY_X_PIN) controls the left-right movement of the player.
    • The vertical joystick axis (JOY_Y_PIN) controls the up-down movement.
  2. Maze Representation: The maze is a 2D array where 1 represents a wall and 0 represents a path. This is a very simple static maze. You can modify the maze array for different levels.
  3. Player Movement: The player is drawn as a rectangle. The position is updated based on the joystick input, ensuring they only move through the open paths (maze[y][x] == 0).
  4. Display: The OLED screen is updated every loop cycle to show the player and the maze. And display “You Win!” after reaching destination.