Classic Snake Game

Create the iconic Snake game controlled by the joystick, with customizable speed, obstacles, and scoring.

Intermediate2-3 hoursGames
Classic Snake Game

Required Components:

MatriXDeck V1JoystickButtons

Classic Snake Game

Snake Game

Introduction

Recreate the iconic Snake game on your MatriXDeck! This project shows how to build a fully functional Snake game controlled by the built-in joystick. The game includes features like increasing difficulty, score tracking, and customizable gameplay elements.

This project is perfect for intermediate-level makers who want to create an engaging, interactive game while learning about:

  • Game loop architecture
  • Matrix-based graphics
  • Player input handling
  • Collision detection
  • Game state management

Materials Needed

  • MatriXDeck V1
  • USB-C cable for power (or use the battery)

Project Overview

We'll create a Snake game with the following features:

  • Control the snake using the joystick
  • Collect food to grow the snake and increase your score
  • Avoid collisions with the walls and the snake's own body
  • Progressive difficulty (snake speeds up as it grows)
  • Game over detection and score display
  • Restart functionality

Step-by-Step Instructions

1. Setting Up the Game Constants and Variables

First, let's define our game constants and global variables:

#include <MatriXDeck.h>

MatriXDeck matrix;

// Joystick pins
#define JOYSTICK_X 36
#define JOYSTICK_Y 39
#define BUTTON_A 32

// Game constants
#define GRID_WIDTH 32
#define GRID_HEIGHT 16
#define INITIAL_SNAKE_LENGTH 3
#define INITIAL_SPEED 200  // Milliseconds per move

// Game states
#define STATE_TITLE 0
#define STATE_PLAYING 1
#define STATE_GAME_OVER 2

// Direction constants
#define DIR_UP 0
#define DIR_RIGHT 1
#define DIR_DOWN 2
#define DIR_LEFT 3

// Snake and food variables
int snakeX[GRID_WIDTH * GRID_HEIGHT];
int snakeY[GRID_WIDTH * GRID_HEIGHT];
int snakeLength;
int direction;
int foodX, foodY;
int score;
int gameSpeed;
int gameState;

// Timing variables
unsigned long lastMoveTime;

2. Game Initialization Function

Let's create a function to initialize or reset the game:

void initGame() {
  // Set initial snake position (middle of screen)
  int startX = GRID_WIDTH / 2;
  int startY = GRID_HEIGHT / 2;
  
  // Place snake segments
  for (int i = 0; i < INITIAL_SNAKE_LENGTH; i++) {
    snakeX[i] = startX - i;
    snakeY[i] = startY;
  }
  
  snakeLength = INITIAL_SNAKE_LENGTH;
  direction = DIR_RIGHT;
  gameSpeed = INITIAL_SPEED;
  score = 0;
  
  // Place initial food
  placeFood();
  
  // Reset timing
  lastMoveTime = millis();
}

void placeFood() {
  // Find a random position not occupied by the snake
  bool validPosition = false;
  
  while (!validPosition) {
    foodX = random(GRID_WIDTH);
    foodY = random(GRID_HEIGHT);
    
    validPosition = true;
    
    // Check if food overlaps with snake
    for (int i = 0; i < snakeLength; i++) {
      if (snakeX[i] == foodX && snakeY[i] == foodY) {
        validPosition = false;
        break;
      }
    }
  }
}

3. Main Setup and Loop Functions

Now, let's implement the setup and main game loop:

void setup() {
  matrix.begin();
  
  // Initialize random seed
  randomSeed(analogRead(A0));
  
  // Set up joystick pins
  pinMode(JOYSTICK_X, INPUT);
  pinMode(JOYSTICK_Y, INPUT);
  pinMode(BUTTON_A, INPUT_PULLUP);
  
  // Start in title screen state
  gameState = STATE_TITLE;
  initGame();
}

void loop() {
  // Read joystick input
  readInput();
  
  // Handle current game state
  switch (gameState) {
    case STATE_TITLE:
      showTitleScreen();
      break;
    case STATE_PLAYING:
      updateGame();
      drawGame();
      break;
    case STATE_GAME_OVER:
      showGameOver();
      break;
  }
}

4. Input Handling

Let's add input handling with the joystick:

void readInput() {
  // Read joystick values (0-4095)
  int joyX = analogRead(JOYSTICK_X);
  int joyY = analogRead(JOYSTICK_Y);
  
  // Check if button is pressed (LOW when pressed due to pull-up)
  bool buttonPressed = !digitalRead(BUTTON_A);
  
  // Handle button press based on game state
  if (buttonPressed) {
    if (gameState == STATE_TITLE || gameState == STATE_GAME_OVER) {
      gameState = STATE_PLAYING;
      initGame();
      delay(200); // Debounce
    }
  }
  
  // Only change direction when playing
  if (gameState == STATE_PLAYING) {
    // Determine joystick direction
    // Avoid 180-degree turns (can't turn directly back on yourself)
    if (joyY < 1000 && direction != DIR_DOWN) {
      direction = DIR_UP;
    } else if (joyY > 3000 && direction != DIR_UP) {
      direction = DIR_DOWN;
    } else if (joyX < 1000 && direction != DIR_RIGHT) {
      direction = DIR_LEFT;
    } else if (joyX > 3000 && direction != DIR_LEFT) {
      direction = DIR_RIGHT;
    }
  }
}

5. Game Update Logic

Now let's implement the core game update logic:

void updateGame() {
  unsigned long currentTime = millis();
  
  // Only update snake position at the right speed
  if (currentTime - lastMoveTime >= gameSpeed) {
    lastMoveTime = currentTime;
    
    // Calculate new head position
    int newHeadX = snakeX[0];
    int newHeadY = snakeY[0];
    
    // Update based on direction
    switch (direction) {
      case DIR_UP:
        newHeadY--;
        break;
      case DIR_RIGHT:
        newHeadX++;
        break;
      case DIR_DOWN:
        newHeadY++;
        break;
      case DIR_LEFT:
        newHeadX--;
        break;
    }
    
    // Check for wall collision
    if (newHeadX < 0 || newHeadX >= GRID_WIDTH || 
        newHeadY < 0 || newHeadY >= GRID_HEIGHT) {
      gameState = STATE_GAME_OVER;
      return;
    }
    
    // Check for self collision
    for (int i = 0; i < snakeLength; i++) {
      if (snakeX[i] == newHeadX && snakeY[i] == newHeadY) {
        gameState = STATE_GAME_OVER;
        return;
      }
    }
    
    // Check if snake eats food
    bool eating = (newHeadX == foodX && newHeadY == foodY);
    
    // Move snake body (don't move the tail if eating)
    for (int i = snakeLength - 1; i > 0; i--) {
      snakeX[i] = snakeX[i - 1];
      snakeY[i] = snakeY[i - 1];
    }
    
    // Update head to new position
    snakeX[0] = newHeadX;
    snakeY[0] = newHeadY;
    
    // Handle eating food
    if (eating) {
      // Increase snake length
      snakeLength++;
      
      // Increase score
      score += 10;
      
      // Speed up the game
      gameSpeed = max(50, INITIAL_SPEED - (score / 10) * 5);
      
      // Place new food
      placeFood();
    }
  }
}

6. Drawing the Game

Now let's implement the drawing functions:

void drawGame() {
  matrix.clear();
  
  // Draw snake
  for (int i = 0; i < snakeLength; i++) {
    // Head is yellow, body is green
    if (i == 0) {
      matrix.setPixel(snakeX[i], snakeY[i], YELLOW);
    } else {
      matrix.setPixel(snakeX[i], snakeY[i], GREEN);
    }
  }
  
  // Draw food (red)
  matrix.setPixel(foodX, foodY, RED);
  
  // Draw score at the top
  char scoreText[10];
  sprintf(scoreText, "%d", score);
  matrix.print(scoreText, 0, 0, GREEN);
  
  matrix.update();
}

void showTitleScreen() {
  matrix.clear();
  matrix.print("SNAKE", 7, 4, GREEN);
  matrix.print("Press A", 3, 12, YELLOW);
  matrix.update();
}

void showGameOver() {
  matrix.clear();
  matrix.print("GAME", 8, 2, RED);
  matrix.print("OVER", 8, 8, RED);
  
  // Wait a moment
  delay(2000);
  
  // Show score
  matrix.clear();
  matrix.print("SCORE:", 5, 2, YELLOW);
  
  char scoreText[10];
  sprintf(scoreText, "%d", score);
  matrix.print(scoreText, 10, 8, GREEN);
  
  matrix.print("A to retry", 0, 15, GREEN);
  matrix.update();
}

Complete Code

The complete code combines all these elements and adds additional features like gameplay sounds and difficulty options. You can find the full code in our GitHub repository.

Extending the Project

Here are some ideas to enhance your Snake game:

  1. Multiple Levels: Create different levels with obstacles
  2. Power-ups: Add special food items that grant temporary abilities
  3. Two-Player Mode: Create a competitive or cooperative mode
  4. High Score Storage: Save high scores to flash memory
  5. Custom Graphics: Create custom sprites for the snake and food

Troubleshooting

Snake moves too fast/slow

  • Adjust the INITIAL_SPEED constant and the speed increase logic

Joystick not responding well

  • Adjust the joystick thresholds in the readInput() function
  • Clean the joystick or check the connections

Game crashes at high scores

  • Check array bounds to prevent buffer overflows when the snake gets very long

Conclusion

Congratulations! You've created a classic Snake game on your MatriXDeck. This project demonstrates game development fundamentals while creating a fun, playable game. The skills you've learned here—input handling, game state management, collision detection—are transferable to many other game projects.

Challenge your friends to beat your high score, and keep experimenting with new features and gameplay elements!


Share your Snake game variations with us on our Discord community or tag us on social media with #MatriXDeckGames!