- 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!
Figure 1: Gameplay of our Snake Game!
Figure 2: Start 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
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:
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.
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:
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.