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

Required Components:
Classic 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:
- Multiple Levels: Create different levels with obstacles
- Power-ups: Add special food items that grant temporary abilities
- Two-Player Mode: Create a competitive or cooperative mode
- High Score Storage: Save high scores to flash memory
- 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!