Published on 10/02/2022 by

Nevulo profile picture
Nevulo

Making Snake in Python

How to create a basic 2D game using Python and pygame, explaining everything along the way

Cover image by Katy Wilkens

Python is a beginner-friendly language that makes it easy to dive straight into making your own games with a graphical user interface (GUI). In this guide, we’ll walk through the basics of setting up a Python installation, creating a window to display our game, and updating the window with a fully functional snake game.

1. Install Python.

If you haven’t already installed Python, you can

. Click “Download Python (version)” and go through the installation process.

2. Open your text editor or IDE.

Having an IDE is helpful for catching errors and knowing when your syntax is wrong quickly, but you can always program using a normal text editor, like Notepad or TextEdit.

Open it up and save the file with the .py extension, this means it’s a Python script which we’ll be able to run from the command line later.

3. Let’s get a window up.

We want our snake game to appear in a window that the user can interact with, like any other application. To achieve that, we’ll need to install a dependency.

What’s a dependency?

A dependency is code from another location that we import into our code. It’s called a dependency because our code depends on the other person’s code to work.

We’ll need to install the pygame dependency to make our game, and we can install it by using pip on the command line, which comes with the Python installation. pygame allows us to quickly develop a game and handles all the complicated logic for us.

Installing the pygame dependency

Open a command prompt and type in pip install pygame, wait a little and pygame should be available to use in your code.

We can import it into our code:

1import pygame

Great! We’ve got pygame in our code. Now what? Well, we want to show a window to display our snake game, how can we do that with pygame?

Initialising a window

pygame has a few “helper methods” for things like modifying the display window, events, etc. Inside pygame.display is a set_mode function which tells the computer to initialise a new window to be displayed on the screen.

1import pygame
2
3screen_width = 720
4screen_height = 480
5screen = pygame.display.set_mode((screen_width, screen_height))

We set a screen_width variable to equal 720 and screen_height to equal 480 to use in set_mode, so we can initialise a window that is 720×480.

In the code above, we’re saying “let the variable screen equal the return value of set_mode”, which is a Surface object.

Now that screen is a Surface object (which represents the contents of the window), we're able to do things like change the background colour:

1screen.fill((0, 0, 0)) # 0, 0, 0 is RGB code for the colour black (0 red, 0 green, 0 blue)

We need to tell pygame.display to update whenever we make any changes we make to the screen, like changing the background colour. This is done by calling pygame.display.flip:

1pygame.display.flip()

Updating our game on a loop

There’s just one last thing before we’re past the first hurdle of bringing up a window properly: the event loop. What is it?

The event loop represents any actions or things that are happening in your game, including when the user quits your app.

1snake_speed = 30
2clock = pygame.time.Clock()
3running = True
4# While "running" is true
5# (always true unless user quits):
6while running:
7 screen.fill((0,0,0))
8
9 pygame.display.flip()
10 clock.tick(snake_speed)
11
12 # Get the next events from the queue
13 # For each event returned from get(),
14 for event in pygame.event.get():
15 # If the event is "QUIT"
16 if event.type == pygame.QUIT:
17 # Set running to False, stop event loop
18 running = False

You can think of executing pygame.event.get() like walking the next “step” your game will take. If you don’t have pygame.event.get() running in a loop, your game won’t really work. It’s also important that we move any updates we make to the screen inside the loop, so we’re constantly updating the contents of the screen on each frame.

The clock variable allows us to specify how fast our game should update. This is not super necessary, however if you find that the snake moves too fast you can make snake_speed a smaller value. You can also increase it to make the game more challenging!

4. Running our program.

If we run the script as it is now, we should see a little window with a black background pop up on the screen!

This is all it is now, but it will become a functional snake game 🐍

5. Adding the actual snake.

Okay, we’re done getting excited about windows now. Let’s get a snake in the damn window.

To draw graphics on the screen like a rectangle for our snake, we’ll need to call pygame.draw.rect, passing in the following as arguments:

  • The display we want to update (the screen variable in our case)
  • The colour this rectangle should be (define a variable blue with the HEX value for the colour blue)
  • The coordinates to draw the rectangle on the screen and how big it should be
    • The first two numbers 200 & 150 represent the X and Y coordinates for where to display the rectangle
    • The last two numbers represent the size of the rectangle, 10 by 10 pixels.
1blue = (0, 0, 255) # RGB code for blue
2pygame.draw.rect(screen, blue, [200, 150, 10, 10])

Include this call before the line where you update your screen, run your program again, and you should see a small blue square near the top left of your screen!

6. Moving the snake.

To move the rectangle that’s being drawn to the screen, we need to do three things:

  1. Storing the current position of the snake

    1# Set the snake in the middle of the screen
    2snake_x = screen_width / 2
    3snake_y = screen_height / 2
  2. Storing the current speed of the snake, so we know where (and how much) to move the snake on the next frame

    1speed_x = 0
    2speed_y = 2

These first two things should be defined at the top of your script after importing pygame, so we can use them later on.

  1. Listening for keyboard events such as the arrow keys to control movement
    1# Handling key events
    2for event in pygame.event.get():
    3 # If the event is "KEYDOWN"
    4 if event.type == pygame.KEYDOWN:
    5 # Go up on arrow key UP
    6 if event.key == pygame.K_UP:
    7 speed_x = 0
    8 speed_y = -2
    9 # Go down on arrow key DOWN
    10 if event.key == pygame.K_DOWN:
    11 speed_x = 0
    12 speed_y = 2
    13 # Go left on arrow key LEFT
    14 if event.key == pygame.K_LEFT:
    15 speed_y = 0
    16 speed_x = -2
    17 # Go right on arrow key RIGHT
    18 if event.key == pygame.K_RIGHT:
    19 speed_y = 0
    20 speed_x = 2

To make the snake move when we press one of the arrow keys, we need to update our event loop to listen for a specific event.type (KEYDOWN, a key being pressed down) and also the type of key being pressed to know how to change the snake's speed.

With all this, we can now update the speed of the snake on every frame to get our snake moving!

1# Update the speed of the snake
2snake_x += speed_x
3snake_y += speed_y

It should look something like this so far:

1import pygame
2
3screen_width = 720
4screen_height = 480
5screen = pygame.display.set_mode((screen_width, screen_height))
6
7# Set the snake in the middle of the screen
8snake_x = screen_width / 2
9snake_y = screen_height / 2
10speed_x = 0
11speed_y = -2
12
13running = True
14while running:
15 screen.fill((255, 255, 255)
16 pygame.draw.rect(screen, blue, [snake_x, snake_y, 10, 10])
17 # Update the snake's X & Y position each frame based
18 # on speed
19 snake_x += speed_x
20 snake_y += speed_y
21 pygame.display.flip()
22
23 for event in pygame.event.get():
24 # If the event is "KEYDOWN"
25 if event.type == pygame.KEYDOWN:
26 # Movement (up, down, left, right arrow keys)
27 if event.key == pygame.K_UP:
28 speed_x = 0
29 speed_y = -2
30 if event.key == pygame.K_DOWN:
31 speed_x = 0
32 speed_y = 2
33 if event.key == pygame.K_LEFT:
34 speed_y = 0
35 speed_x = -2
36 if event.key == pygame.K_RIGHT:
37 speed_y = 0
38 speed_x = 2
39 if event.type == pygame.QUIT:
40 # Set running to False, stop event loop
41 running = False

7. Spawning fruit.

We need to define the position for the fruit near the top of our file, after our imports:

1fruit_x = 300
2fruit_y = 400
3red = (255, 0, 0)

We’ve also defined a variable for the colour red, which we’ll use to draw the fruit.

To draw the fruit on the screen, draw another rectangle on the screen each frame where the fruit will be located: this code will run in your game loop

1pygame.draw.rect(screen, red, [fruit_x, fruit_y, 10, 10])

8. Growing the size of the snake when eating a fruit.

Now that we have fruit spawning on the map, we want to do two things to replicate snake:

  1. Spawning the fruit in a new location when the snake goes over it
  2. Incrementing the size of the snake by 1

Re-spawning fruit when eaten

Let’s knock the first thing out of the way: spawning the fruit in a new place when the snake goes over it. How can we do this? Well, we know the position of the snake at all times, as well as the position of the fruit.

In this case, it’s as simple as checking for if the x and y position match for both the fruit and the snake:

1# If the snake is touching fruit (x and y position match for snake head and fruit), set the fruit to a new, random position
2if snake_x == fruit_x and snake_y == fruit_y:
3 # ⚠️ Make sure to "import random" at the top of your file!
4 fruit_x = round(random.randrange(0, screen_width - 10) / 10.0) * 10.0
5 fruit_y = round(random.randrange(0, screen_height - 10) / 10.0) * 10.0

This logic is saying “pick a random new location for the fruit’s x position, making sure it’s within the left and right side of the screen”. The same logic applies for the y position of the fruit.

We’ll need to add import random at the very top of our file for this code to work, since we’re using the random module to generate a random range to spawn the new fruit.

Adding length to our snake

When we eat fruit, we want to increment the length of the snake by 1.

We’ll need some place to store the current length of the snake (put this near the top of your file):

1snake_length = 1

We’re also going to need to change how we update the snake on the screen, currently its only drawing one rectangle each frame, but we wish to draw one new rectangle as the value of snake_length goes up.

Define a new variable called snake_blocks as an empty list at the top of your file to track all the rectangles we’ll have to draw for each point the user has:

1snake_blocks = []

Now we just need some way to keep appending new blocks to the end of the snake At the top of your game loop, add this logic to continuously push the snake's current x and y position (as a list) to snake_blocks

1# Set the snake head to the current position, append to snake blocks to keep track
2snake_head = []
3snake_head.append(snake_x)
4snake_head.append(snake_y)
5snake_blocks.append(snake_head)

But since this is running in a loop, it will just keep adding more and more blocks to snake_blocks forever. If the user has eaten 7 fruits, the snake should have 7 blocks (plus one for the snake head).

So, inside the game loop, we want to check if the length of the snake_blocks we have stored is greater than the snake_length, and remove the first block if so. This will stop our snake from infinitely growing!

1# Ensure the snake is only as big as the length we've set, delete the end blocks
2if len(snake_blocks) > snake_length:
3 del snake_blocks[0]

Replace the draw.rect call to update your snake with the code below to loop through each of the snake blocks and draw a rectangle on the screen.

1# Draw a snake block for each point the user has
2for block in snake_blocks:
3 pygame.draw.rect(screen, blue, [block[0], block[1], snake_size, snake_size])

The value of block in this for loop will be a list, the first element (block[0]) represents the x position for that block, and the second element (block[1]) represents the y position.

Lastly, we want to add 1 to snake_length when we pass over fruit, so go to the code we added where we’re checking for the x and y position of the snake and fruit being the same, and add a line to let snake_length equal itself plus 1:

1if snake_x == fruit_x and snake_y == fruit_y:
2 snake_length += 1
3 fruit_x = round(random.randrange(0, screen_width - 10) / 10.0) * 10.0
4 fruit_y = round(random.randrange(0, screen_height - 10) / 10.0) * 10.0

9. Game over!

By this point, our snake game is working great. One thing I haven’t touched on though is the loss condition. Typically, in snake, the game ends when you:

  • Touch the edge of the screen, or
  • You bump into your tail.

A good place to start would be to define a boolean variable (true or false) for the game over state:

1game_over = False # put at the top of your file after imports

Touching the edge of the screen

When the snake goes past the edge of the screen, we want to set game_over to True:

1# If the snake goes beyond the left or right side of the screen,
2if (snake_x >= screen_width or snake_x < 0 or
3 # If the snake goes beyond the top of bottom of the screen,
4 snake_y >= screen_height or snake_y < 0):
5 # Set game over to true
6 game_over = True

Here, we’re checking if the x position of the snake is greater than or equal to the width of the screen OR if the x position is less than 0. What does this mean in practice? Since we’ve defined the screen_width as 720, if our snake goes too far right on the screen making the snake_x variable go past 720 (or too far left, making it go below 0), we know the snake is on or past the edge of the screen, meaning it’s game over.

The same checks above apply for the y coordinate too (up and down movement), so if the snake goes above the top of the screen or below the bottom of the screen, game_over will be set to True.

Running into your tail

We also wanted to end the game if the snake’s head touches its tail.

1# Not counting the snake head, check if any other existing snake blocks are crossing over the snake head (dead)
2for x in snake_blocks[:-1]:
3 if x == snake_head:
4 game_over = True

The special [:-1] after snake_blocks is a “slice”, and this specific slice means “get all the snake blocks except the last one”.

Slice notation in Python looks like this: x[start:stop], and in our code above we’re omitting the start value (which defaults to 0) by just putting a colon (:). Next is the stop value which says at what index to end the slice, in this case it’s -1, so what does this mean?

In slice notation, negative indices will roll over to count from the end of the array instead of the start.

So, if we had a snake with a length of 5, snake_blocks[:-1] in our case would start at 0 (like it normally does), and end at whatever the length of snake_blocks is minus 1.

In practice, this prevents an issue where game_over will constantly be True because the snake head position will always equal itself.

Showing a game over screen

Excluding the pygame.display.flip call, the clock.tick call and the event for loop, move all the logic in your game so far into an if condition (if the game has not ended):

1# If the user hasn't lost the game:
2if not game_over:
3 screen.fill((255,255,255))
4 # ...
5else:

Our game over screen logic will be inside the else statement. Let’s start with a blue screen if the game is over:

1# Game over logic (screen showing users score + how to continue)
2else:
3 screen.fill(blue)

So now, when the user hits the edge of the screen or runs into their tail, the screen will turn blue. Let’s add some text to this screen to let the user know what their score was.

To add fonts and text into our game, we need to define the font we want to use in a variable at the top of the file, after the imports:

1font = pygame.font.SysFont('Arial', 30)

Then, we can call font.render with the text we wish to render on the screen. The second argument (True) is for anti-aliasing the font (you likely want to leave this as True to make the font smooth), and the last argument is what colour the text should be.

1score = font.render('You scored ' + str(snake_length), True, black)

Something to keep in mind though is that font.render won’t actually make any text appear on the screen just yet; we’ll need to save the result of font.render to a variable which we can use to apply the text on the screen in a certain position using screen.blit. blit is a confusing name (

), but it essentially means to draw one surface on top of another surface.

1screen.blit(score, (10, screen_height / 2 - 100))

The second argument is where we want the text to appear on the screen – the x and y coordinates. In this case, we’re saying we want the score to appear 10 pixels from the left side of the screen and halfway down the screen, minus 100 pixels, just so it appears a bit higher than halfway.

Allowing the user to restart

In your event for loop, inside the check for KEYDOWN, let’s add a check to see if the spacebar has been pressed:

1for event in pygame.event.get():
2 # If the event is "KEYDOWN"
3 if event.type == pygame.KEYDOWN:
4 # If space is pressed, reset game
5 if event.key == pygame.K_SPACE:
6 game_over = False
7 snake_x = screen_width / 2
8 snake_y = screen_height / 2
9 snake_blocks = []
10 snake_length = 1

The last 4 lines are just resetting variables to their default value, so when the user restarts the game, they start out in the middle of the screen and have no length on the snake.

Using what we learned from earlier about drawing text on the screen, let’s also draw some instructions when the game is over, so the user knows how to restart:

1# Game over logic (screen showing users score + how to continue)
2else:
3 screen.fill(blue)
4 score = font.render('You scored ' + str(snake_length), True, black)
5 screen.blit(score, (10, screen_height / 2 - 100))
6 text = font.render('You lost! Press \'Q\' to quit, or Spacebar to play again', False, black)
7 screen.blit(text, (10, screen_height / 2))

The full, final code for our Snake game

I’ve done some minor clean-up as well as adding additional comments to explain some logic, but this is all the code to get a fully functional snake game with things like score tracking, eating fruits and a game over state.

1import pygame
2import random
3
4### Initialization
5pygame.init() # Not necessary
6font = pygame.font.SysFont('Arial', 30)
7
8# Colours
9blue = (0, 0, 255) # hex code for blue
10black = (0, 0, 0) # hex code for black
11red = (255, 0, 0) # hex code for red
12screen_width = 720
13screen_height = 480
14screen = pygame.display.set_mode((screen_width, screen_height))
15
16# Set the snake in the middle of the screen
17snake_x = screen_width / 2
18snake_y = screen_height / 2
19snake_speed = 30
20snake_size = 10
21snake_length = 1
22snake_blocks = []
23
24fruit_x = 300
25fruit_y = 400
26
27speed_x = 0
28speed_y = 10
29
30game_over = False
31
32running = True
33clock = pygame.time.Clock()
34
35# While "running" is true (always true unless user quits):
36while running:
37 # If the user hasn't lost the game:
38 if not game_over:
39 screen.fill((255,255,255)) # 255, 255, 255 is hexadecimal for the colour black
40
41 # Set the snake head to the current position, append to snake blocks to
42 # keep track
43 snake_head = []
44 snake_head.append(snake_x)
45 snake_head.append(snake_y)
46 snake_blocks.append(snake_head)
47
48 # Ensure the snake is only as big as the length we've set
49 if len(snake_blocks) > snake_length:
50 del snake_blocks[0]
51
52 # Not counting the last block, check if any other existing snake
53 # blocks are crossing over the snake head (dead)
54 for x in snake_blocks[:-1]:
55 if x == snake_head:
56 game_over = True
57
58 # Draw a snake block for each point the user has
59 for block in snake_blocks:
60 pygame.draw.rect(screen, blue, [block[0], block[1], snake_size, snake_size])
61 pygame.draw.rect(screen, red, [fruit_x, fruit_y, snake_size, snake_size])
62
63 # Update the speed of the snake
64 snake_x += speed_x
65 snake_y += speed_y
66
67 # If the snake is touching fruit (x and y position match for snake head and
68 # fruit), set the fruit to a new, random position and update snake length
69 if snake_x == fruit_x and snake_y == fruit_y:
70 fruit_x = round(random.randrange(0, screen_width - snake_size) / 10.0) * 10.0
71 fruit_y = round(random.randrange(0, screen_height - snake_size) / 10.0) * 10.0
72 snake_length += 1
73
74 # If the snake goes beyond the left or right side of the screen,
75 if (snake_x >= screen_width or snake_x < 0 or
76 # if the snake goes beyond the top of bottom of the screen,
77 snake_y >= screen_height or snake_y < 0):
78 # Set game over to true
79 game_over = True
80
81 # Game over logic (screen showing users score + how to continue)
82 else:
83 screen.fill(blue)
84 score = font.render('You scored ' + str(snake_length), False, black)
85 screen.blit(score, (10, screen_height / 2 - 100))
86 text = font.render('You lost! Press \'Q\' to quit, or Spacebar to play again', False, black)
87 screen.blit(text, (10, screen_height / 2))
88
89 # Update the screen
90 pygame.display.flip()
91 clock.tick(snake_speed)
92
93 ### Event Loop
94 # Get the next events from the queue
95 # For each event returned from get(),
96 for event in pygame.event.get():
97 # If the event is "KEYDOWN"
98 if event.type == pygame.KEYDOWN:
99 # If "Q" is pressed, stop game
100 if event.key == pygame.K_q:
101 running = False
102 # If space is pressed, reset game
103 if event.key == pygame.K_SPACE:
104 game_over = False
105 snake_x = screen_width / 2
106 snake_y = screen_height / 2
107 snake_blocks = []
108 snake_length = 1
109 # Movement (up, down, left, right arrow keys)
110 if event.key == pygame.K_UP:
111 speed_x = 0
112 speed_y = -10
113 if event.key == pygame.K_DOWN:
114 speed_x = 0
115 speed_y = 10
116 if event.key == pygame.K_LEFT:
117 speed_y = 0
118 speed_x = -10
119 if event.key == pygame.K_RIGHT:
120 speed_y = 0
121 speed_x = 10
122 # If the event is "QUIT" (when user clicks X on window)
123 if event.type == pygame.QUIT:
124 # Set running to False, stop event loop
125 running = False

Conclusion

Using pygame in Python is an effective way to quickly prototype game ideas and develop simple 2D games without much effort.

Now that you know how to do basic things like drawing rectangles and capturing keyboard events, the possibilities are endless! With enough time and practice, you could make a side-scroller, a top-down shooter, or countless other fun, simple games.

If you’re looking for more fun things to add to your game, take a look at the

. It’s a great resource to continue learning about how to extend your game to do even more, and about making games in general. If you need clarification on any of the topics covered in this article, the gives a good run down.

Comments

0

You need to be signed in to comment