Snake Game With Joystick

This code integrates a joystick with a simple Snake Game using the pygame library. It also utilizes the ACROME SMD platform to receive joystick inputs and provide feedback, like turning on a buzzer when food is consumed.

About Tools and Materials:

SMD Red (Robot Shop)

SMD USB Gateway (Robot Shop)

Buzzer Module (Robot Shop)

Joystick Module (Robot Shop)

Step 1: Hardware & Software Overview

Key Component:

  1. Buzzer Module Every time the snake eats the food Buzzer Module makes a sound.

  2. Joystick Module Handles snake movement.

  3. pygame Handles the graphics and user interface for the snake game. It displays the snake, food, score, and manages input events.

  4. ACROME SMD Communicates with the joystick and handles joystick input as well as buzzer feedback.

Project Key Features:

  1. Joystick Input: The joystick connected via the ACROME SMD platform is used to control the snake's direction. The snake moves left, right, up, or down based on the joystick's X and Y-axis values. A button on the joystick can be used to pause and resume the game.

  2. Start and Game Over Menus: The game starts with a simple menu where you can choose to start the game or quit. If the game ends (snake hits a wall or itself), a game over menu is displayed, with options to retry or quit.

  3. Game Logic: The snake grows when it eats food, and the length is increased. The snake's movement speed is fixed but can be controlled by the joystick. A buzzer sound is triggered when the snake eats the food.

  4. Pause Functionality: A button on the joystick allows pausing and resuming the game.

Project Wiring Diagram

Workflow:

Start Menu: When the program is run, the start menu is displayed. The user can click on "Start" to begin the game or "Quit" to exit.

Snake Movement: During the game, the joystick's position controls the snake's movement. Moving the joystick in any direction updates the snake’s position.

Eating Food: When the snake's head collides with the food, the snake grows, and a buzzer sound is triggered.

Game Over: The game ends when the snake hits the boundaries or itself. A "Game Over" menu is displayed with options to retry or quit.

Pausing the Game: The game can be paused and resumed using the joystick's button

import pygame
import time
import random
from smd.red import *
from serial.tools.list_ports import comports
from platform import system

def USB_Port():
	ports = list(comports())
	usb_names = {
		"Windows": ["USB Serial Port"],
		"Linux": ["/dev/ttyUSB"],
		"Darwin": [
			"/dev/tty.usbserial",
			"/dev/tty.usbmodem",
			"/dev/tty.SLAB_USBtoUART",
			"/dev/tty.wchusbserial",
			"/dev/cu.usbserial",
            		"/dev/cu.usbmodem",
			"/dev/cu.SLAB_USBtoUART",
			"/dev/cu.wchusbserial",
		]
	}
	
	os_name = system()
	if ports:
		for port, desc, hwid in sorted(ports):
			if any(name in port or name in desc for name in usb_names.get(os_name, [])):
				return port
		print("Current ports:")
		for port, desc, hwid in ports:
			print(f"Port: {port}, Description: {desc}, Hardware ID: {hwid}")
	else:
		print("No port found")
	return None
	
port = USB_Port()
m = Master(port)
m.attach(Red(0))  # Attach the Red device

# Initialize the game
pygame.init()

# Define colors
white = (255, 255, 255)
yellow = (255, 255, 102)
black = (0, 0, 0)
red = (213, 50, 80)
green = (0, 255, 0)
blue = (50, 153, 213)

# Set display dimensions
width, height = 600, 400
display = pygame.display.set_mode((width, height))
pygame.display.set_caption('Snake Game')

# Clock to control game speed
clock = pygame.time.Clock()

# Snake settings
snake_block = 10
snake_speed = 10

# Font styles
font_style = pygame.font.SysFont("bahnschrift", 25)
score_font = pygame.font.SysFont("comicsansms", 35)


def display_score(score):
    value = score_font.render("Score: " + str(score), True, black)
    display.blit(value, [0, 0])


def draw_snake(snake_block, snake_list):
    for x in snake_list:
        pygame.draw.rect(display, black, [x[0], x[1], snake_block, snake_block])


def message(msg, color, y_displace=0):
    mesg = font_style.render(msg, True, color)
    display.blit(mesg, [width / 6, height / 3 + y_displace])


# Button function to create clickable buttons
def button(text, x, y, w, h, inactive_color, active_color, action=None):
    mouse = pygame.mouse.get_pos()
    click = pygame.mouse.get_pressed()

    # Change color if mouse is over the button
    if x + w > mouse[0] > x and y + h > mouse[1] > y:
        pygame.draw.rect(display, active_color, (x, y, w, h))
        if click[0] == 1 and action != None:
            action()
    else:
        pygame.draw.rect(display, inactive_color, (x, y, w, h))

    # Render the button text
    text_surf = font_style.render(text, True, black)
    display.blit(text_surf, [x + (w / 4), y + (h / 4)])


# Start menu screen
def start_menu():
    start_game = False
    while not start_game:
        display.fill(blue)
        message("Welcome to Snake Game!", white, -50)
        
        # Create Start and Quit buttons
        button("Start", 150, 200, 100, 50, green, yellow, game_loop)  # Calls game_loop() when clicked
        button("Quit", 350, 200, 100, 50, red, yellow, pygame.quit)   # Calls pygame.quit() when clicked

        pygame.display.update()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()


# Game over menu with Retry and Quit options
def game_over_menu():
    game_over_screen = True
    while game_over_screen:
        display.fill(blue)
        message("You Lost!", red, -50)
        message("Score: " + str(length_of_snake - 1), white, 0)
        
        # Create Retry and Quit buttons
        button("Retry", 150, 200, 100, 50, green, yellow, game_loop)  # Restart the game
        button("Quit", 350, 200, 100, 50, red, yellow, pygame.quit)   # Quit the game

        pygame.display.update()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()


def game_loop():
    global length_of_snake
    paused = False
    game_over = False
    game_close = False

    # Initial snake settings
    x1 = width / 2
    y1 = height / 2
    x1_change = 0
    y1_change = 0

    snake_list = []
    length_of_snake = 1

    # Food position
    foodx = round(random.randrange(0, width - snake_block) / 10.0) * 10.0
    foody = round(random.randrange(0, height - snake_block) / 10.0) * 10.0

    while not game_over:
        joystick = m.get_joystick(0, 1)
        while game_close == True:
            game_over_menu()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                game_over = True

        # Read joystick values
        x_val = joystick[0]
        y_val = joystick[1]
        y_val = -y_val
        
        # Check if the pause button (e.g., button 1) is pressed
        if m.get_button(0, 1):  # Assuming button 1 is used for pause (check your joystick mapping)
            paused = not paused  # Toggle pause state

        # If the game is paused, display the pause message
        if paused:
            display.fill(blue)
            message("Paused! Press the button to Resume", yellow)
            pygame.display.update()
            continue  # Skip the rest of the loop while paused
        
        # Control snake direction based on joystick position
        if x_val < -20:  # Left
            x1_change = -snake_block
            y1_change = 0
        elif x_val > 20:  # Right
            x1_change = snake_block
            y1_change = 0
        elif y_val < -20:  # Up
            y1_change = -snake_block
            x1_change = 0
        elif y_val > 20:  # Down
            y1_change = snake_block
            x1_change = 0

        # Check for wall collisions
        if x1 >= width or x1 < 0 or y1 >= height or y1 < 0:
            game_close = True

        x1 += x1_change
        y1 += y1_change
        display.fill(blue)
        pygame.draw.rect(display, green, [foodx, foody, snake_block, snake_block])
        snake_head = []
        snake_head.append(x1)
        snake_head.append(y1)
        snake_list.append(snake_head)
        if len(snake_list) > length_of_snake:
            del snake_list[0]

        for x in snake_list[:-1]:
            if x == snake_head:
                game_close = True

        draw_snake(snake_block, snake_list)
        display_score(length_of_snake - 1)

        pygame.display.update()

        if x1 == foodx and y1 == foody:
            foodx = round(random.randrange(0, width - snake_block) / 10.0) * 10.0
            foody = round(random.randrange(0, height - snake_block) / 10.0) * 10.0
            length_of_snake += 1
            m.set_buzzer(0, 1, 10)  # Buzzer on collision
            time.sleep(0.1)
            m.set_buzzer(0, 1, 0)  # Turn off buzzer

        clock.tick(snake_speed)

    pygame.quit()
    m.detach(0)  # Detach the Red device using the correct integer ID
    print("Disconnected from device.")


# Start the game
start_menu()  # Display the start menu first

Conclusion: This is a fun and interactive way to use the ACROME SMD joystick to control the classic Snake game, providing a hardware-based gaming experience.

Last updated