Today we'll build a simple Snake game controlled by your Arduino joystick. The game will:
• Read joystick input from Arduino via serial communication
• Display a moving snake on screen using Pygame
• Grow the snake when it eats food
• End when the snake hits itself or the wall
Hardware:
• Arduino with joystick module already set up
• USB cable to connect Arduino to computer
Software:
• Python 3 installed on your computer
• Pygame library (we'll install this)
• PySerial library (we'll install this)
Open your terminal or command prompt and run these commands:
For Pygame (handles graphics and game loop):
pip install pygame
For PySerial (communicates with Arduino):
pip install pyserial
Your Arduino should already have code similar to this. It reads the joystick and sends direction commands over serial.
void setup() {
Serial.begin(9600);
pinMode(A0, INPUT); // X-axis
pinMode(A1, INPUT); // Y-axis
}
void loop() {
int xVal = analogRead(A0);
int yVal = analogRead(A1);
if (xVal < 300) Serial.println("LEFT");
else if (xVal > 700) Serial.println("RIGHT");
else if (yVal < 300) Serial.println("UP");
else if (yVal > 700) Serial.println("DOWN");
delay(100);
}
Create a new Python file called snake_game.py in your project folder.
We'll build this game step by step, adding code section by section.
Make sure your Arduino is connected via USB before running the game!
First, we need to import all the libraries we'll use in our game.
These provide the tools for graphics, serial communication, and random numbers.
import pygame
import serial
import time
import random
# Initialize Pygame
pygame.init()
Constants are values that won't change during the game. They make our code easier to read and modify.
# Screen dimensions
WIDTH = 600
HEIGHT = 600
CELL_SIZE = 20
# Colors (RGB format)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
WHITE = (255, 255, 255)
# Game speed
FPS = 10
We need to establish a serial connection to read joystick data from the Arduino.
Note: Change 'COM3' to your Arduino's port (check Arduino IDE).
# Set up serial connection
# Windows: 'COM3', 'COM4', etc.
# Mac/Linux: '/dev/ttyUSB0', '/dev/ttyACM0'
try:
arduino = serial.Serial('COM3', 9600, timeout=1)
time.sleep(2) # Wait for connection
print("Arduino connected!")
except:
print("Could not connect to Arduino")
arduino = None
Set up the Pygame window where our game will be displayed.
# Create the game window
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Arduino Snake Game")
# Clock to control game speed
clock = pygame.time.Clock()
The snake is represented as a list of coordinates. Each coordinate is one segment of the snake's body.
# Snake starting position (center of screen)
snake = [[WIDTH//2, HEIGHT//2]]
# Snake direction (starts moving right)
direction = "RIGHT"
next_direction = "RIGHT"
# Snake growth flag
grow = False
Food appears at random positions on the grid. When the snake eats it, it grows and new food appears.
# Function to create food at random position
def create_food():
x = random.randint(0, (WIDTH-CELL_SIZE)//CELL_SIZE) * CELL_SIZE
y = random.randint(0, (HEIGHT-CELL_SIZE)//CELL_SIZE) * CELL_SIZE
return [x, y]
# Place initial food
food = create_food()
This function reads data from the Arduino and returns the direction command.
def read_joystick():
if arduino and arduino.in_waiting > 0:
try:
line = arduino.readline().decode('utf-8').strip()
if line in ["UP", "DOWN", "LEFT", "RIGHT"]:
return line
except:
pass
return None
This prevents the snake from immediately reversing into itself (e.g., going LEFT when moving RIGHT).
def update_direction(current, new):
# Prevent 180-degree turns
if new == "UP" and current != "DOWN":
return new
if new == "DOWN" and current != "UP":
return new
if new == "LEFT" and current != "RIGHT":
return new
if new == "RIGHT" and current != "LEFT":
return new
return current
Calculate the new head position based on current direction and add it to the snake.
def move_snake(snake, direction, grow):
head = snake[0].copy()
if direction == "UP":
head[1] -= CELL_SIZE
elif direction == "DOWN":
head[1] += CELL_SIZE
elif direction == "LEFT":
head[0] -= CELL_SIZE
elif direction == "RIGHT":
head[0] += CELL_SIZE
snake.insert(0, head)
if not grow:
snake.pop() # Remove tail
return snake
The game ends if the snake hits the wall or collides with its own body.
def check_collision(snake):
head = snake[0]
# Check wall collision
if (head[0] < 0 or head[0] >= WIDTH or
head[1] < 0 or head[1] >= HEIGHT):
return True
# Check self collision
if head in snake[1:]:
return True
return False
Check if the snake's head is at the same position as the food.
def check_food(snake, food):
if snake[0] == food:
return True
return False
This function renders the snake, food, and score on the screen.
def draw_game(screen, snake, food, score):
screen.fill(BLACK)
# Draw snake
for segment in snake:
pygame.draw.rect(screen, GREEN,
(segment[0], segment[1],
CELL_SIZE, CELL_SIZE))
# Draw food
pygame.draw.rect(screen, RED,
(food[0], food[1],
CELL_SIZE, CELL_SIZE))
# Draw score
font = pygame.font.Font(None, 36)
text = font.render(f'Score: {score}', True, WHITE)
screen.blit(text, (10, 10))
Show the final score when the game ends.
def show_game_over(screen, score):
font = pygame.font.Font(None, 72)
text = font.render('GAME OVER', True, RED)
text_rect = text.get_rect(center=(WIDTH//2, HEIGHT//2))
screen.blit(text, text_rect)
font_small = pygame.font.Font(None, 36)
score_text = font_small.render(f'Final Score: {score}',
True, WHITE)
score_rect = score_text.get_rect(center=(WIDTH//2, HEIGHT//2 + 50))
screen.blit(score_text, score_rect)
pygame.display.flip()
The main loop runs continuously, processing input and updating the game state.
# Game variables
running = True
game_over = False
score = 0
# Main game loop
while running:
# Handle quit event
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if not game_over:
Read joystick input and update the snake's direction.
# Read joystick input
joy_input = read_joystick()
if joy_input:
next_direction = update_direction(direction,
joy_input)
# Update direction
direction = next_direction
Move the snake and check if it ate food.
# Move snake
snake = move_snake(snake, direction, grow)
grow = False
# Check if snake ate food
if check_food(snake, food):
score += 10
food = create_food()
grow = True
Check for collisions and end the game if necessary.
# Check collisions
if check_collision(snake):
game_over = True
show_game_over(screen, score)
time.sleep(3)
# Draw everything
draw_game(screen, snake, food, score)
pygame.display.flip()
Control the game speed with the clock.
# Control game speed
clock.tick(FPS)
When the game ends, close the serial connection and quit Pygame properly.
# Cleanup
if arduino:
arduino.close()
pygame.quit()
Steps to test:
1. Make sure your Arduino is connected via USB
2. Upload the joystick code to your Arduino
3. Note the COM port your Arduino is using (check Arduino IDE)
4. Update the COM port in your Python code
5. Run the Python script: python snake_game.py
6. Use your joystick to control the snake!
Problem: "Could not connect to Arduino"
• Check that Arduino is plugged in and the correct COM port is specified
• Make sure no other program (like Arduino IDE Serial Monitor) is using the port
Problem: Snake moves in wrong direction
• Check joystick wiring and calibration values in Arduino code
Problem: Game runs too fast or slow
• Adjust the FPS constant (higher = faster, lower = slower)
Once you have the basic game working, try these improvements:
• Add sound effects when eating food or game over
• Create different difficulty levels (faster speeds)
• Add obstacles or walls in the playing field
• Display a high score that persists between games
• Add a pause button using a joystick button
• Create power-ups that give special abilities
Through this project, you've learned:
• Serial communication between Arduino and Python
• Game loop architecture and frame-based updates
• Collision detection algorithms
• List manipulation for tracking game objects
• Event handling and user input processing
• Graphics rendering with Pygame
• Integrating hardware with software projects
Your Challenge:
Add functionality so that pressing the joystick button resets the game instead of having to close and restart the program.
Requirements:
• Modify the Arduino code to send a "RESET" command when button is pressed
• Update the Python code to detect the RESET command
• When RESET is received, restart the game (reset snake position, score, and food)
Hint: Think about which variables need to be reset to their initial values!