Remember the Snake game? It’s a piece of our past that brings a smile to many faces. today’s your lucky day because I’m here to guide you on a little adventure.
In today’s tutorial, we’re going to make a Snake Game in Python using the PySimpleGUI
library, so without delays let’s get started!
Table of Contents
- Getting Started
- Imports
- Snake Game Functions
- Constants and Variables
- GUI Setup
- Main Game Loop
- Event Handling
- Game Logic
- Game Graphics
- Window Closing
- Example
- Full Code
Getting Started
Make sure to install the PySimpleGUI library for the code to function properly via the terminal or your command prompt:
$ pip install PySimpleGUI
Imports
We start by importing the PysimpleGUI
library, which allows us to create simple graphical user interfaces (GUIs) in Python. We also import the time
library for using time-related functions along with randint
and choice
from random
to generate random numbers.
import PySimpleGUI as sg
from time import time
from random import randint, choice
Snake Game Functions
grid_to_pixel
Next, we create a function that turns the grid coordinates to actual pixel positions on the computer screen.
def grid_to_pixel(cell):
top_left = cell[0] * CELL_SIZE, cell[1] * CELL_SIZE
bottom_right = top_left[0] + CELL_SIZE, top_left[1] + CELL_SIZE
return top_left, bottom_right
generate_apple_position
What we did here is make the apple appear in random positions but in a way that doesn’t collide with the snake’s body. In other words, the apple appears in a place that is not occupied by the snake.
def generate_apple_position(exclude, grid_size):
grid_points = [(x, y) for x in range(grid_size) for y in range(grid_size) if (x, y) not in exclude]
return choice(grid_points) if grid_points else None
refresh_game_window
Then we create this function that is used to keep updating our game window by drawing our game including the Snake and the apple.
def refresh_game_window(field, snake_body, apple_position):
field.DrawRectangle((0, 0), (FIELD_SIZE, FIELD_SIZE), 'black')
top_left, bottom_right = grid_to_pixel(apple_position)
field.DrawRectangle(top_left, bottom_right, 'red')
for index, part in enumerate(snake_body):
top_left, bottom_right = grid_to_pixel(part)
color = 'yellow' if index == 0 else 'green'
field.DrawRectangle(top_left, bottom_right, color)
Constants and Variables
After that, we define the size, and number of cells in the grid of our game as well as the initial position where the snake starts together with its directions, also indicating whether the snake has eaten the apple or not.
# Constants and variables
FIELD_SIZE = 500 # The bigger this number is the bigger the screen is
CELL_NUM = 15 # The bigger this number is the smaller the snake and the apple is
CELL_SIZE = FIELD_SIZE / CELL_NUM
snake_body = [(4, 4), (3, 4), (2, 4)]
directions = {'left': (-1, 0), 'right': (1, 0), 'up': (0, 1), 'down': (0, -1)}
direction = directions['up']
apple_position = generate_apple_position(snake_body, CELL_NUM)
apple_eaten = False
GUI Setup
Following that, we create a window in which we define the background color and where we can draw things thanks to the PySimpleGUI’s graph element. Additionally, we add a ‘Game Over!‘ text and a ‘Restart‘ button to reset the game. Both are initially set to be invisible.
# Setup GUI
sg.theme('Green')
field = sg.Graph(
canvas_size=(FIELD_SIZE, FIELD_SIZE),
graph_bottom_left=(0, 0),
graph_top_right=(FIELD_SIZE, FIELD_SIZE),
background_color='black')
game_over_text = sg.Text('', font=('Helvetica', 20), text_color='red')
restart_button = sg.Button('Restart', key='-RESTART-', visible=False)
layout = [
[field],
[game_over_text],
[restart_button]
]
window = sg.Window('Simple Snake Game - The Pycodes', layout, return_keyboard_events=True)
Main Game Loop
# Main loop
start_time = time()
score = 0
is_game_over = False
while True:
event, values = window.read(timeout=10)
if event == sg.WIN_CLOSED:
break
The code presented here initiates a loop and creates a score variable with an initial value of 0
. It continuously checks for events and follows the game logic. The only way to exit the game is manually through the user, by checking for the sg.WIN_CLOSED
event.
Event Handling
# Event handling
if not is_game_over:
if event.startswith('Left'):
direction = directions['left']
elif event.startswith('Up'):
direction = directions['up']
elif event.startswith('Right'):
direction = directions['right']
elif event.startswith('Down'):
direction = directions['down']
if event == '-RESTART-':
snake_body = [(4, 4), (3, 4), (2, 4)]
direction = directions['up']
apple_position = generate_apple_position(snake_body, CELL_NUM)
apple_eaten = False
game_over_text.update('')
restart_button.update(visible=False)
score = 0
is_game_over = False
What this part does is keep tabs on the user keyboard input and the ‘Restart’ button, in other words, this part determines the direction of the snake according to the user input so that the game logic moves the snake accordingly while the game is not over. However, once the game is over and the user clicks the restart button this part resets the game and hides the restart button, together with the game over text and the score.
Game Logic
Now, we create this part to manage the rules of the game, basically increasing the length of the snake when it eats the apple while creating a new one, and also updating the score(+1 when the snake eats an apple), as well as moving the snake by controlling its speed, in this case, every 0.3 seconds. Additionally, it checks whether the snake hit the walls or itself, ending the game if it happens and hereby displaying the score and also showing the game over text together with the restart button by rendering them visible.
# Game logic
if not is_game_over:
time_since_start = time() - start_time
if time_since_start >= 0.3: # This controls the speed of the snake the smaller the value the faster it gets
start_time = time()
if snake_body[0] == apple_position:
apple_position = generate_apple_position(snake_body, CELL_NUM)
apple_eaten = True
score += 1
new_head = (snake_body[0][0] + direction[0], snake_body[0][1] + direction[1])
snake_body.insert(0, new_head)
if not apple_eaten:
snake_body.pop()
apple_eaten = False
if not 0 <= snake_body[0][0] <= CELL_NUM - 1 or \
not 0 <= snake_body[0][1] <= CELL_NUM - 1 or \
snake_body[0] in snake_body[1:]:
game_over_text.update('Game Over! Score: {}'.format(score))
restart_button.update(visible=True)
is_game_over = True
Game Graphics
# Game graphics
refresh_game_window(field, snake_body, apple_position)
Here, we created the illusion of movement by redrawing everything (snake and apple) according to the game logic.
Window Closing
window.close()
Lastly, this part closes the window when the user chooses to do so.
Example
Full Code
import PySimpleGUI as sg
from time import time
from random import randint, choice
def grid_to_pixel(cell):
top_left = cell[0] * CELL_SIZE, cell[1] * CELL_SIZE
bottom_right = top_left[0] + CELL_SIZE, top_left[1] + CELL_SIZE
return top_left, bottom_right
def generate_apple_position(exclude, grid_size):
grid_points = [(x, y) for x in range(grid_size) for y in range(grid_size) if (x, y) not in exclude]
return choice(grid_points) if grid_points else None
def refresh_game_window(field, snake_body, apple_position):
field.DrawRectangle((0, 0), (FIELD_SIZE, FIELD_SIZE), 'black')
top_left, bottom_right = grid_to_pixel(apple_position)
field.DrawRectangle(top_left, bottom_right, 'red')
for index, part in enumerate(snake_body):
top_left, bottom_right = grid_to_pixel(part)
color = 'yellow' if index == 0 else 'green'
field.DrawRectangle(top_left, bottom_right, color)
# Constants and variables
FIELD_SIZE = 500 # The bigger this number is the bigger the screen is
CELL_NUM = 15 # The bigger this number is the smaller the snake and the apple is
CELL_SIZE = FIELD_SIZE / CELL_NUM
snake_body = [(4, 4), (3, 4), (2, 4)]
directions = {'left': (-1, 0), 'right': (1, 0), 'up': (0, 1), 'down': (0, -1)}
direction = directions['up']
apple_position = generate_apple_position(snake_body, CELL_NUM)
apple_eaten = False
# Setup GUI
sg.theme('Green')
field = sg.Graph(
canvas_size=(FIELD_SIZE, FIELD_SIZE),
graph_bottom_left=(0, 0),
graph_top_right=(FIELD_SIZE, FIELD_SIZE),
background_color='black')
game_over_text = sg.Text('', font=('Helvetica', 20), text_color='red')
restart_button = sg.Button('Restart', key='-RESTART-', visible=False)
layout = [
[field],
[game_over_text],
[restart_button]
]
window = sg.Window('Simple Snake Game - The Pycodes', layout, return_keyboard_events=True)
# Main loop
start_time = time()
score = 0
is_game_over = False
while True:
event, values = window.read(timeout=10)
if event == sg.WIN_CLOSED:
break
# Event handling
if not is_game_over:
if event.startswith('Left'):
direction = directions['left']
elif event.startswith('Up'):
direction = directions['up']
elif event.startswith('Right'):
direction = directions['right']
elif event.startswith('Down'):
direction = directions['down']
if event == '-RESTART-':
snake_body = [(4, 4), (3, 4), (2, 4)]
direction = directions['up']
apple_position = generate_apple_position(snake_body, CELL_NUM)
apple_eaten = False
game_over_text.update('')
restart_button.update(visible=False)
score = 0
is_game_over = False
# Game logic
if not is_game_over:
time_since_start = time() - start_time
if time_since_start >= 0.3: # This controls the speed of the snake the smaller the value the faster it gets
start_time = time()
if snake_body[0] == apple_position:
apple_position = generate_apple_position(snake_body, CELL_NUM)
apple_eaten = True
score += 1
new_head = (snake_body[0][0] + direction[0], snake_body[0][1] + direction[1])
snake_body.insert(0, new_head)
if not apple_eaten:
snake_body.pop()
apple_eaten = False
if not 0 <= snake_body[0][0] <= CELL_NUM - 1 or \
not 0 <= snake_body[0][1] <= CELL_NUM - 1 or \
snake_body[0] in snake_body[1:]:
game_over_text.update('Game Over! Score: {}'.format(score))
restart_button.update(visible=True)
is_game_over = True
# Game graphics
refresh_game_window(field, snake_body, apple_position)
window.close()
Happy Coding!