Register Login

Snake Game Using PyGame in Python?

Updated Apr 18, 2024

This article is a followup to the  “Introduction to PyGame” article. In this article, we will be using PyGame to build a snake game using Python.

If you haven’t already, check out the previous article using this link.

If you ever face a problem with comprehending the parameters of a module, you should look it up in the pygame docs.

Since we already covered the basics in the previous article, let's get started.

Making a Snake Game using Python

Step1: The boilerplate code:

Open up your favorite code editor and enter the following code in it:

import pygame
import sys
import random

pygame.init()
running = True

width,height = 750, 750

font = pygame.font.SysFont("Roboto", 100)
pygame.display.set_caption("snake")
screen = pygame.display.set_mode((width,height))
clock = pygame.time.Clock()

while running == True:
	for event in pygame.event.get():
		if event.type == pygame.QUIT:
			running == False
			pygame.quit()

    pygame.display.update()
	clock.tick(10)

If you get a black screen, it means the code is working properly.

Explanation:

Now most of the things have already been explained in the earlier article, but there are some new things that we will go over.

  1. We have imported sys and random in lines 2 and 3, which you will see later.
  2. We have also defined a font in line 9, which we will use to track score.
  3. The clock is used to define the FPS(frames per second at which the game is running)

Step 2: Creating a grid:

In our game, the movement will be grid-based. So we will make a grid using a function called “drawgrid” with two for loops inside it: one for the x direction and one for the y direction.

Add the following code to your project before the game loop:

def drawgrid():
	for x in range(0, width, blocksize):
		for y in range(0, height, blocksize):
			rect = pygame.Rect(x, y , blocksize, blocksize)
			pygame.draw.rect(screen, (255,255,255),rect, 1 )

Also, don't forget to define a variable called blocksize, alongside all the variables we defined previously.

blocksize = 50

Afterward, call your function right below where you put it

drawgrid()

The for loops are used to draw the grid that we have created both in the x and y directions, by defining a rect, from (0,0) to the max window with a size equal to the block size variable we defined earlier.

Output:

Step3: Creating the snake

Since the game is called Snake, it is obvious that we must have a snake in it. So now, we will finally code the main character of our game.

class snake:
	def __init__(self):
		self.x, self.y = blocksize, blocksize
		self.xdir = 1
		self.ydir = 0
		self.head = pygame.Rect(self.x, self.y, blocksize, blocksize)
		self.body = [pygame.Rect(self.x - blocksize, self.y, blocksize, blocksize)]
		self.death = False

We will be creating a class for our snake with an initialize function. The function contains the “self” variable and is used to refer to the snake object that we have created.

In this function, we have defined the x coordinate, y coordinate and direction.

You will see why we have defined the direction when we add proper movement.

But first, we need to understand the coordinate system in Python.

It is quite similar to your standard coordinate system, with one exception. If you notice closely, the y axis is inverted, i.e the negative y axis is upwards, when it should be downwards and vice versa.

Anyway, now that we have considered this important detail, let us move forward.

snake = snake()

Assign the snake object to a variable right below where you called the grid function.

pygame.draw.rect(screen, "blue" , snake.head)
for square in snake.body:
pygame.draw.rect(screen, "blue", square )

Then, draw the snake in the game loop.

Output:

Step4: Adding a bit of functionality to the snake

Firstly, we will need to properly define the movement of the snake’s body.

Basically, each of the snake’s body parts needs to move in the direction that the one in front of it is moving in.

We can achieve this through the following code:

def update (self):
self.body.append(self.head)
for i in range(len(self.body)-1):
self.body[i].x, self.body[i].y = self.body[i+1].x, self.body[i+1].y
self.head.x += self.xdir *blocksize
self.head.y += self.ydir *blocksize
self.body.remove(self.head)


Put this in the snake class, under def__init__.

In the first line, we are joining the snake’s head and its body. Then we use a for loop to check the length of the snake’s body. Then according to that, we are defining the direction of each part of the snake’s body to be equal to the one in front of it.

Finally, it checks the x and y direction of the head of the snake and moves the snake according to that logic.

For example: If the direction is 1,  it is in the positive x direction. It is then multiplied by block size to get the number of pixels the head is moving.

snake.update()

Finally, we will call the update class in the game loop to enforce this logic.

Output:

That doesn't seem quite right. The snake keeps on duplicating. To fix this, add the following code in the game loop, below the line where you called snake.update().

screen.fill("black")
drawgrid()

NOTE: Ensure that this code should be ABOVE the line where you drew the snake. Otherwise, it will not work
This will fill the positions where the snake has moved with black and then redraw the grid.

Output:

Step5: Adding Keyboard Input:

Add the following code in the pygame.event.get() loop, where we defined the game closed event.

if event.type == pygame.KEYDOWN:
			if event.key == pygame.K_DOWN:
				snake.ydir = 1
				snake.xdir = 0

		if event.type == pygame.KEYDOWN:
			if event.key == pygame.K_UP:
				snake.ydir = -1
				snake.xdir = 0
		if event.type == pygame.KEYDOWN:
			if event.key == pygame.K_LEFT:
				snake.xdir = -1
				snake.ydir = 0

		if event.type == pygame.KEYDOWN:
			if event.key == pygame.K_RIGHT:
				snake.xdir = 1
				snake.ydir = 0

I will not go into too much detail here, as the code should be self-explanatory.

However, do take note of the fact that the way we have defined movement, pressing keys only changes the direction of the movement. We have already explored the logic behind the snake’s movement in Step 5.

Output:

If you can move the snake with keyboard input it means that everything is perfect.

Step 6: Creating the apples for the snake

Now that we have created the snake, it is time for us to program the apples.

To code in the apples, we will draw a rect and draw it in a random position.

class Apple:
	def __init__ (self):
		
		self.x = int(random.randint(0, width)/blocksize) *blocksize
		self.y = int(random.randint(0, height)/blocksize) *blocksize 
		self.rect = pygame.Rect(self.x, self.y, blocksize, blocksize)
	def update(self):
		pygame.draw.rect(screen,(255, 0, 0), self.rect )

Then we will call the apple.update() function in the game loop, underneath the snake.update() and drawgrid functions.

Make sure the apple.update() is below both screen.fill and drawgrid else nothing will be displayed.

Output:

If you can see the apple on screen, then everything is working fine.

Step7: Implementing the eating mechanism

This will be very simple. All we have to do is check whether the snake and apple are in the same place. If yes, then we will implement a line of code to make the snake grow in size.

We will also recall the apple function to make the apple go to a random location
To do this, add the following code in the game loop, below where we drew the snake.

if snake.head.x == apple.x and snake.head.y == apple.y:
snake.body.append(pygame.Rect(square.x, square.y, blocksize, blocksize))
apple = Apple()

Output:

If your snake grows after eating, move on to the next step.

Step8: Collisions and Death:

Now we will program the snake to die, and restart the game when it either hits the wall or eats itself Add the following code in the update function of the snake class.

global apple
for square in self.body:
if self.head.x == square.x and self.head.y == square.y:
		self.death = True
	if self.head.x not in range(0,width) or self.head.y not in range(0,height):
		self.death = True


if self.death :
self.x, self.y = blocksize, blocksize
	self.xdir = 1
	self.ydir = 0
	self.head = pygame.Rect(self.x, self.y, blocksize, blocksize)
	self.body = [pygame.Rect(self.x - blocksize, self.y, blocksize, blocksize)]
	self.death = False
apple = Apple()

Here, in the for loop, the game is detecting every second iif the snake touches either itself or the wall. If either is true, self.death is set to true Once self.death is set to true we have made it so that the game will reset, i.e the snake will go back to its old location with the same direction and reset back two only two parts by using an if statement.

We have set apple to a global variable as it is inside a different class.

Output:

Now let's move on to the final step.

Step 9: Adding the score system

Add the following code where you defined all the variables.

score = font.render("1", True, (0, 255, 0))
score_rect = score.get_rect(center = (width/20, height/20))

We have done width/20 and height/20 to make the font go to the corner.

Then add the following code in the game loop below the line where you called the snake.update() function.

score = font.render(f"{len(snake.body) + 1}", True, (0,255,0))

This will get the length of the snake’s body and display the font according to that in green colour.

screen.blit(score, score_rect)

Finally, add this line right above the line with pygame.display.update()

And with this line of code, our game is finally complete.

Output:

Complete Code:

import pygame
import sys
import random
 
pygame.init()
running = True
 
width,height = 750, 750
blocksize = 50
 
font = pygame.font.SysFont("Roboto", blocksize*2)
 
screen = pygame.display.set_mode((width,height))
 
pygame.display.set_caption("snake")
 
clock = pygame.time.Clock()
 
score = font.render("1", True, (0, 255, 0))
score_rect = score.get_rect(center = (width/20, height/20))
 
# High score
hiscore = font.render("1", True, (0, 255, 0))
hiscore_rect = hiscore.get_rect(center = (width, height/20))
 
class snake:
    def __init__(self):
        self.x, self.y = blocksize, blocksize
        self.xdir = 1
        self.ydir = 0
        self.head = pygame.Rect(self.x, self.y, blocksize, blocksize)
        self.body = [pygame.Rect(self.x - blocksize, self.y, blocksize, blocksize)]
        self.death = False
 
    def update (self):
        global apple
 
        for square in self.body:
            if self.head.x == square.x and self.head.y == square.y:
                self.death = True
            if self.head.x not in range(0,width) or self.head.y not in range(0,height):
                self.death = True
 
        if self.death :
            self.x, self.y = blocksize, blocksize
            self.xdir = 1
            self.ydir = 0
            self.head = pygame.Rect(self.x, self.y, blocksize, blocksize)
            self.body = [pygame.Rect(self.x - blocksize, self.y, blocksize, blocksize)]
            self.death = False
            apple = Apple()
 
        self.body.append(self.head)
        for i in range(len(self.body)-1):
            self.body[i].x, self.body[i].y = self.body[i+1].x, self.body[i+1].y
        self.head.x += self.xdir *blocksize
        self.head.y += self.ydir *blocksize
        self.body.remove(self.head)
 
class Apple:
    def __init__ (self):
        
        self.x = int(random.randint(0, width)/blocksize) *blocksize
        self.y = int(random.randint(0, height)/blocksize) *blocksize 
        self.rect = pygame.Rect(self.x, self.y, blocksize, blocksize)
    def update(self):
        pygame.draw.rect(screen,(255, 0, 0), self.rect )
def drawgrid():
    for x in range(0, width, blocksize):
        for y in range(0, height, blocksize):
            rect = pygame.Rect(x, y , blocksize, blocksize)
            pygame.draw.rect(screen, (255,255,255),rect, 1 )
 
drawgrid()
 
 
snake = snake()
 
apple = Apple()
 
while running == True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running == False
            pygame.quit()
 
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_DOWN:
                snake.ydir = 1
                snake.xdir = 0
 
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_UP:
                snake.ydir = -1
                snake.xdir = 0
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                snake.xdir = -1
                snake.ydir = 0
 
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                snake.xdir = 1
                snake.ydir = 0
 
    pygame.draw.rect(screen, "blue" , snake.head)       
 
    for square in snake.body:
        pygame.draw.rect(screen, "blue", square )
 
    snake.update()
    screen.fill("black")
    drawgrid()
    apple.update()
 
    score = font.render(f"{len(snake.body) + 1}", True, (0,255,0))
 
    pygame.draw.rect(screen, "blue" , snake.head)       
 
 
    for square in snake.body:
        pygame.draw.rect(screen, "blue", square )
 
    screen.blit(score, score_rect)
 
    if snake.head.x == apple.x and snake.head.y == apple.y:
        snake.body.append(pygame.Rect(square.x, square.y, blocksize, blocksize))
        apple = Apple()
 
    pygame.display.update()
    clock.tick(10)

Conclusion:

In the previous article, we set up a basic PyGame window and in this one, we coded a fully functioning snake game. In the next one, we will learn how to export our game as an application.


×