Published on

How to make a Snake Game using Python and Pygame with an Interactive Menu

Snake is a world famous video game originating in the 1970’s that is regarded by many as a timeless classic of a video game! If you want to find out how to build a Snake Game using Python keep reading because you are about to find out!

Gameplay of our Snake Game!

Figure 1: Gameplay of our Snake Game!

Start Game screen

Figure 2: Start Game screen

End Game screen

Figure 3: End Game screen

Setup

In order to build this Snake Game we will need the following requirements:

  • Python 3.x
  • Pygame 2.x
  • Pygame-Menu 4.x

Now we’ll need to create a folder in our coding workspace which contains a python file to store all the Snake Game code. You can name it whatever you want but I’m calling mine main.py

Snake Game Folder Structure

Figure 4: Snake Game Folder Structure

Implementation

First of all, if you would rather learn how to build this Snake Game by viewing the full source code here is a GitHub Gist I created for this game. If not, feel free to code along!

Okay, let’s jump right into the coding!

First, we need to set some global variables:

import pygame_menu
import pygame
import pygame_menu
import random
import sys
from typing import Tuple, Any
from math import isclose

pygame.init()

display_width = 600
display_height = 400

black = (0,0,0)
white = (255,255,255)
red = (255,0,0)

'''
Game difficulty is assigned the following values:
* Easy = 25
* Medium = 50
* Hard = 100
'''
difficulty = 25;
win = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption('Snake Game by Shehan Atukorala')
clock = pygame.time.Clock()
player_name = '';
default_player_name = True;

Let’s quick go over the important parts of the above code:

  • First, in order to start Pygame we have to include pygame.init()
  • The main window dimensions are set by display_width & display_height
  • The game difficulty is set by difficulty which defaults to the Easy mode(read the comment above this variable for more info)
  • The main Pygame window is assigned to the win variable
  • pygame.time.Clock() helps track time in our Snake Game

Then we need to setup some functions:

def setup_snake_food():
    new_food_position = [random.randrange(1, (display_width//10))* 10, random.randrange(1, (display_height//10)) * 10]
    return new_food_position

def setup_collision_obj():
    new_collision_obj = [random.randrange(1, (display_width//10))* 10, random.randrange(1, (display_height//10)) * 10]
    return new_collision_obj

def set_game_difficulty(selected: Tuple, value: Any):
    if(value == 1):
        difficulty = 25
    elif(value == 2):
        difficulty = 50
    elif(value == 3):
        difficulty = 100
    else:
        difficulty = 25

def show_game_score(font, size, game_score):
    game_score_font = pygame.font.SysFont(font, size);
    game_score_surface = game_score_font.render("Game Score: " + str(game_score), True, white)
    game_score_rect = game_score_surface.get_rect()
    game_score_rect.midtop = (display_height/5, 15)
    win.blit(game_score_surface, game_score_rect)

def show_collision_obj(collision_obj_position, snake_width, snake_height):
    collision_obj_rect = pygame.Rect(collision_obj_position[0], collision_obj_position[1], snake_width, snake_height)
    collision_obj_image = pygame.image.load("./red-brick-wall.jpg")
    collision_obj_image_resize = pygame.transform.scale(collision_obj_image, (snake_width, snake_height))
    win.blit(collision_obj_image_resize, collision_obj_rect)

def show_start_screen():
    start_menu = pygame_menu.Menu(width=display_width, height=display_height, title='Welcome to Snake Game!', theme=pygame_menu.themes.THEME_BLUE);
    start_menu.add.text_input("Your Name: ", default='Guest');
    start_menu.add.selector("Difficulty: ", [("Easy", 1), ("Medium", 2), ("Hard", 3)], onchange=set_game_difficulty);
    start_menu.add.button("Play", game_loop);
    start_menu.add.button("Quit", pygame_menu.events.EXIT);
    start_menu.mainloop(win)

def replay_game():
    game_loop()

def show_end_screen(game_score):
    end_menu = pygame_menu.Menu(width=display_width, height=display_height, title='Game Over', theme=pygame_menu.themes.THEME_BLUE);
    end_menu.add.label("Your Score:" + str(game_score));
    end_menu.add.button("Replay Game", replay_game);
    end_menu.add.button("Quit Game", pygame_menu.events.EXIT);
    end_menu.mainloop(win)
def setup_snake_food():
    new_food_position = [random.randrange(1, (display_width//10))* 10, random.randrange(1, (display_height//10)) * 10]
    return new_food_position

def setup_collision_obj():
    new_collision_obj = [random.randrange(1, (display_width//10))* 10, random.randrange(1, (display_height//10)) * 10]
    return new_collision_obj

def set_game_difficulty(selected: Tuple, value: Any):
    if(value == 1):
        difficulty = 25
    elif(value == 2):
        difficulty = 50
    elif(value == 3):
        difficulty = 100
    else:
        difficulty = 25

def show_game_score(font, size, game_score):
    game_score_font = pygame.font.SysFont(font, size);
    game_score_surface = game_score_font.render((player_name + "'s Game Score: " + str(game_score)), True, white)
    game_score_rect = game_score_surface.get_rect()
    game_score_rect.midtop = (display_height, 15)
    win.blit(game_score_surface, game_score_rect)

def show_collision_obj(collision_obj_position, snake_width, snake_height):
    collision_obj_rect = pygame.Rect(collision_obj_position[0], collision_obj_position[1], snake_width, snake_height)
    collision_obj_image = pygame.image.load("./red-brick-wall.jpg")
    collision_obj_image_resize = pygame.transform.scale(collision_obj_image, (snake_width, snake_height))
    win.blit(collision_obj_image_resize, collision_obj_rect)

def set_player_name(name):
    global player_name;
    global default_player_name;
    player_name = name;
    default_player_name = False;

def set_default_player_name():
    global player_name;
    global default_player_name;
    player_name = "Guest"
    default_player_name = False


def show_start_screen():
    start_menu = pygame_menu.Menu(width=display_width, height=display_height, title='Welcome to Snake Game!', theme=pygame_menu.themes.THEME_BLUE);
    start_menu.add.text_input("Your Name: ", default="Guest", onchange=set_player_name);
    start_menu.add.selector("Difficulty: ", [("Easy", 1), ("Medium", 2), ("Hard", 3)], onchange=set_game_difficulty);
    start_menu.add.button("Play", game_loop);
    start_menu.add.button("Quit", pygame_menu.events.EXIT);
    if default_player_name:
        set_default_player_name();
    start_menu.mainloop(win)

def replay_game():
    game_loop()

def show_end_screen(game_score):
    end_menu = pygame_menu.Menu(width=display_width, height=display_height, title='Game Over', theme=pygame_menu.themes.THEME_BLUE);
    end_menu.add.label("Your Score:" + str(game_score));
    end_menu.add.button("Replay Game", replay_game);
    end_menu.add.button("Quit Game", pygame_menu.events.EXIT);
    end_menu.mainloop(win)def setup_snake_food():
    new_food_position = [random.randrange(1, (display_width//10))* 10, random.randrange(1, (display_height//10)) * 10]
    return new_food_position

def setup_collision_obj():
    new_collision_obj = [random.randrange(1, (display_width//10))* 10, random.randrange(1, (display_height//10)) * 10]
    return new_collision_obj

def set_game_difficulty(selected: Tuple, value: Any):
    if(value == 1):
        difficulty = 25
    elif(value == 2):
        difficulty = 50
    elif(value == 3):
        difficulty = 100
    else:
        difficulty = 25

def show_game_score(font, size, game_score):
    game_score_font = pygame.font.SysFont(font, size);
    game_score_surface = game_score_font.render("Game Score: " + str(game_score), True, white)
    game_score_rect = game_score_surface.get_rect()
    game_score_rect.midtop = (display_height/5, 15)
    win.blit(game_score_surface, game_score_rect)

def show_collision_obj(collision_obj_position, snake_width, snake_height):
    collision_obj_rect = pygame.Rect(collision_obj_position[0], collision_obj_position[1], snake_width, snake_height)
    collision_obj_image = pygame.image.load("./red-brick-wall.jpg")
    collision_obj_image_resize = pygame.transform.scale(collision_obj_image, (snake_width, snake_height))
    win.blit(collision_obj_image_resize, collision_obj_rect)

def show_start_screen():
    start_menu = pygame_menu.Menu(width=display_width, height=display_height, title='Welcome to Snake Game!', theme=pygame_menu.themes.THEME_BLUE);
    start_menu.add.text_input("Your Name: ", default='Guest');
    start_menu.add.selector("Difficulty: ", [("Easy", 1), ("Medium", 2), ("Hard", 3)], onchange=set_game_difficulty);
    start_menu.add.button("Play", game_loop);
    start_menu.add.button("Quit", pygame_menu.events.EXIT);
    start_menu.mainloop(win)

def replay_game():
    game_loop()

def show_end_screen(game_score):
    end_menu = pygame_menu.Menu(width=display_width, height=display_height, title='Game Over', theme=pygame_menu.themes.THEME_BLUE);
    end_menu.add.label("Your Score:" + str(game_score));
    end_menu.add.button("Replay Game", replay_game);
    end_menu.add.button("Quit Game", pygame_menu.events.EXIT);
    end_menu.mainloop(win)

I’ll give a brief description of what these functions do below:

  • setup_snake_food(): Setups up the coordinates for the food object on the game window
  • setup_collision_obj():Setups up the coordinates for the collision object on the game window(it will be displayed as an object with a brick wall image)
  • set_game_difficulty(selected: Tuple, value: Any): Changes the game difficulty based on the value parameter
  • show_game_score(font, size, game_score): Displays the game score on the main window. The user provided name is appended to the display text in this format: <provided_name>'s Game Score
  • show_collision_obj(collision_obj_position, snake_width, snake_height): Shows the collision object on the main window. The sprite used for the collision object("./red-brick-wall.jpg") can be found in this link(just download the image, move it to your workspace folder and reference it in your main.py file)
  • set_player_name(name): Sets the player_name global variable based on the name parameter passed from the show_start_screen() function
  • set_default_player_name(): Sets the player_name to a default value of 'Guest' if the user does not provide a name in the Start Game screen
  • show_start_screen(): Shows the Start Screen of the game. The Start Screen lets the user pick a name for themselves, allows the user to select a difficulty level(choices range from Easy, Meduim & Hard). If user does not provide a name then the set_default_player_name() function is called
  • replay_game(): Allows the user to replay the game by calling the game_loop() function
  • show_end_screen(): Shows the End Screen of the game. The End Screen displays the score achieved by the user and lets the user pick between replaying the game or quitting the program

Next, we need to setup the main logic for the Snake Game, contained in the game_loop() method:

def game_loop():
    x = display_width/2
    y = display_height/2
    snake_position = [display_width/2, display_height/2]
    snake_body = [[display_width/2, display_height/2], [(display_width/2)-10, display_height/2], [(display_width/2)-(2*10), display_width/2]]
    snake_width = 20
    snake_height = 20
    snake_speed = 5
    snake_direction = "UP"
    new_direction = snake_direction
    gameExit = False
    game_score = 0;

    food_position = setup_snake_food()
    show_food = True

    collision_obj_position = setup_collision_obj()
    show_collision = True

    while not gameExit:
        pygame.time.delay(10)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
        keys = pygame.key.get_pressed()

        if keys[pygame.K_ESCAPE]:
            pygame.quit()
            sys.exit()
            break
        if keys[pygame.K_LEFT]:
            new_direction = "LEFT";
        if keys[pygame.K_RIGHT]:
            new_direction = "RIGHT";
        if keys[pygame.K_UP]:
            new_direction = "UP";


        if keys[pygame.K_DOWN]:
            new_direction = "DOWN";
        if snake_direction != "UP" and new_direction == "DOWN":
            snake_direction = new_direction
        if snake_direction != "DOWN" and new_direction == "UP":
            snake_direction = new_direction
        if snake_direction != "LEFT" and new_direction == "RIGHT":
            snake_direction = new_direction
        if snake_direction != "RIGHT" and new_direction == "LEFT":
            snake_direction = new_direction

        if snake_direction == "UP":
            snake_position[1] -= snake_speed
        if snake_direction == "DOWN":
            snake_position[1] += snake_speed;
        if snake_direction == "LEFT":
            snake_position[0] -= snake_speed;
        if snake_direction == "RIGHT":
            snake_position[0] += snake_speed;

        snake_body.insert(0, list(snake_position));
        if isclose(snake_position[0], food_position[0], abs_tol=5) and isclose(snake_position[1], food_position[1], abs_tol=5):
            game_score += 10;
            show_food = False;
        else:
            snake_body.pop();

        if isclose(snake_position[0], collision_obj_position[0], abs_tol=(snake_width - 10)) and isclose(snake_position[1], collision_obj_position[1], abs_tol=(snake_height - 10)):
            show_end_screen(game_score);

        if not show_food:
            food_position = setup_snake_food();
            show_food = True;
        if not show_collision:
            collision_obj_position = setup_collision_obj();

            show_collision = True;

        win.fill(black);
        for pos in snake_body:
            # Draw all parts of the snake
            pygame.draw.rect(win, (255, 255, 255), pygame.Rect(pos[0], pos[1], snake_width/2, snake_height/2));


        # Draw food
        pygame.draw.rect(win, (255, 0, 255), (food_position[0], food_position[1], snake_width/2, snake_height/2));

        # Draw collision obj
        show_collision_obj(collision_obj_position, snake_width, snake_height);

        # if snake head hits the edge of the screen then end game
        if snake_position[0] < 0 or snake_position[0] > (display_width - snake_width/2):
            show_end_screen(game_score);
        if snake_position[1] < 0 or snake_position[1] > (display_height - snake_height/2):
            show_end_screen(game_score);


        show_game_score('consolas', 20, game_score)
        pygame.display.update();

        clock.tick(difficulty);

As the game_loop() function is the most vital part of this program, let’s get a basic idea of what it does:

  • Defines variables that define the snake object’s width, height, direction, new direction(basically what the new arrow key event inputted by the user is) along with gameExit for controlling game state and game_score for tracking the user’s game score
  • Setup the coordinate positions for the collision and food objects so that they can be rendered onto the main window
  • Main game loop that:
    • First reads user input from arrow keys
    • Prevents the snake object from moving in opposite directions to it’s current direction. Ex: if the snake is current moving up, it cannot move down as it’s next action
    • Adjusts the snake object’s coordinates based on the user input
    • Checks if the snake object has hit a food object, if so it increments the game score and adds an extra pygame.Rect object to the snake object in order to make it longer
    • Checks if snake object has collided with the collision object, if so it ends the game, if not it just continues
    • Display snake, collision and food objects on the main window and show the game score as text on the top of the main window. After that, update the main window via pygame.display.update()
    • Adjusts the difficulty level of the game based on how many frames per second should pass in the main window. Ex: If the difficulty is set to 'Medium' then it waits for a maximum of 50 frames to pass before starting the next iteration of the loop

Finally, we just have to call the show_start_screen() function at the end in order to start the game:

show_start_screen()

Now all that’s left is to run python main.py in order to start the game. A Pygame Window should pop up with the following content:

Snake Game Start Menu Screen

Figure 5: Snake Game Start Menu Screen

To proceed to the actual gameplay use your keyboard arrow keys to select a difficulty level and your keyboard input for a name for your character, then select the Play button.

Try to beat my score!

Figure 6: Try to beat my score!

Once our Snake collides with the borders of the main window or the collision object then the End Game screen will render, like so:

Our End Game screen

Figure 7: Our End Game screen

From the End Game screen the user has the choice of either replaying the game or quitting it.

If you made it this far, congrats! You have just finished building a Snake Game with an Interactive Menu using Python and Pygame.

Conclusion

Well, that’s it for this tutorial! If you have any questions or concerns please feel free to post a comment for this article and I will get back to you if I find the time.

I hope you found this article helpful. Thanks so much for reading my article! Feel free to follow me on Twitter and GitHub, connect with me on LinkedIn and subscribe to my YouTube channel.