Home » Tutorials » How to Make a Snake Game in Python

How to Make a Snake Game in Python

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

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!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top