Making a simple Snake game in python (part 1)

Arpit Omprakash
Byte-Sized-Code
Published in
9 min readFeb 2, 2020

I’ve been programming for a long time now, and I use it in my day to day life to analyze data and automate specific routine processes. Still, I had never previously attempted something like this before. It was quite an enlightening experience and made me learn a lot about different packages that python offers e.g., turtle, Tkinter, and pygame (this tutorial uses pygame, check my articles on Pygame if you want to learn more about Pygame).

Before jumping into coding, first, we have to establish some rules/logic that the players in the game follow.

Rules of the game:

  1. The game screen has a fixed width and height.
  2. The game starts with the snake and food fixed at certain positions.
  3. The snake changes direction as the user presses specific keys, e.g., W, A, S, D or Up, Left, Down, Right.
  4. Once in motion (after the user presses a key), the snake keeps on moving in the said direction till the user presses another key.
  5. The score (points) starts at zero and increases as the snake eats food.

The paragraph above generally encapsulates the game where the snake and food are objects in the game and follow the above rules. When we have objects, they can interact, and that leads us to events (times when an object interacts with itself or other objects). There are two significant events in the game:

  • Eating: This happens when the snake interacts with the food (eats it).
  • Collision: This happens when the snake interacts with itself or the boundaries of the game display (or screen).

There are certain things/changes that follow each event. When the Eating event occurs, the snake grows in size, and the food disappears from its current location and appears at a different location. When the Collision event occurs, the game resets (score = 0, snake, and food move to the initial position and the snake resizes to initial size), and we can play again.

Now that we know how the game works, we can try to code it. I prefer writing object-oriented code (that way, I can later use my code in other places where it may be helpful), so I would write the code in a specific way that many people may not like/understand. A non-OOPS version of the program can be found here.

Here’s the plan, first, we write the primary loop for displaying stuff and a kind of intro to pygame. Next, we write classes for the objects (snake and food). Concerning functions for the given classes are written inside the class. Then we write the whole game loop (where the basic logic/rules of the game are programmed). I’ll also include scoring somewhere in the game loop part. Part 2 of the article will cover the pieces after the objects (scoring and the game loop).

Familiarising with pygame

The code above is how you initialize the game. It looks pretty simple, and all it will do is give you a blank screen when you run it, like this:

The game screen

Now, let’s understand the code a little. Pygame uses RGB tuples for color; thus, the colors are entered as a tuple (red value, green value, blue value). The initial block after the import is a representation of some of the most common colors that I found out online. In the variables section, we set the screen_width and the screen_height (I chose 400,400). You can have different screen sizes.

# The screen
wn = pg.display.set_mode((screen_width, screen_height))
pg.display.set_caption('A simple game of Snake')

Here, we initialize the screen/window for the game. ‘wn’ is our screen object, also known as a surface. Pygame is basically like old cartoons, you draw an image and then another picture and then the next one at fast rates so that it appears like a movie. A surface in pygame is what we draw on. The second line here sets the ‘title’ of the window to “A simple game of snake.”

# The mainloop
run = True
while run:
# Catches all the events that have happened since the game started
for event in pg.event.get():
# When you press the close window button
if event.type == pg.QUIT:
run = False
# Fills the screen with black color
wn.fill(black)

Here lies the crux of the code where which makes things “come alive.” When we open a new window using pygame, it starts recording every key that the user presses or every mouse movement that the user makes inside the game window. The run variable is set to ‘True,’ and thus, the program enters the while loop and stays in it till the variable run has the value ‘True.’ In the first line of the while loop, we are going through all the events that pygame has recorded. If the event.type is QUIT, i.e., the user clicks on the close window button, then the run variable is set to ‘False’ and the while loop breaks. The last line of the while loop fills the screen with a black color, and the final line of the programs exits pygame when we close the window.

One important thing to keep in mind, pygame treats the upper-left corner as the origin of its coordinate system. Thus every value of x and y is written accordingly. Here is a simple diagram of the game screen with the coordinates of the corners to help you visualize this:

The coordinate system in pygame. The origin (0,0) is located at the top left corner.

The objects

Food Class

The above code is for the food class. We can initialize the food using this class, and the class contains the following functions

  • __init__
# Initialization
def __init__(self):
self.x = screen_width/2
self.y = screen_height/4
self.color = red
self.width = 10
self.height = 10

The above function initializes the food variables, including the location, color (red), width, and height (10, 10). The food starts at the given coordinates, i.e., in the top-mid region of the game screen.

  • draw_food
# Makes the food visible
def draw_food(self, surface):
self.food = pg.Rect(self.x, self.y, self.width, self.height)
pg.draw.rect(surface, self.color, self.food)

The first line creates a rectangle (py.Rect object) with the provided width and height at the provided locations. The second line draws the rectangle on the provided surface.

  • is_eaten
# Is the food eaten?
def is_eaten(self, head):
return self.food.colliderect(head)

The is_eaten function checks if the food is eaten. We can code it in a lot of different ways. We can check if the distance between the food and snakehead is 0 or check using the coordinates if they overlap. Here we take the easy path; we use a pygame inbuilt function that checks if two rectangles collide (this is the reason that food and the snakehead are initialized as py.Rect objects). If the two rectangles collide (snakehead and food), it returns ‘True’ else it returns ‘False.’

  • new_pos
# Returns a new position for the food after it is eaten
def new_pos(self):
self.x = random.randint(0,screen_width-self.width)
self.y = random.randint(0,screen_height-self.height)

The final function uses the random package in python to return a random location for the food when we ask for it. The code is pretty self-explanatory. We make sure that the food stays inside the screen by generating a position that is inside the screen (x between 0 and screen_width and y between 0 and screen_height).

Player class

As the name of the game is “snake” and have a ‘snake’ class inside would be a bit confusing, I have named the snake object as the ‘Player’ class. Below is the code for the whole class. It’s a bit long, but don’t worry, we will go in-depth with all the functions inside the class.

  • __init__
# Initialization
def __init__(self):
self.x = screen_width/2
self.y = screen_height/2
self.width = 10
self.height = 10
self.velocity = 10
self.direction = 'stop'
self.body = []
self.head_color = green
self.body_color = brown

The above function initializes the different variables of the snake, including location, width, height, velocity, direction, color, and information about the body.

  • draw_player
# Draws the snake on the given surface
def draw_player(self, surface):
self.seg = []
self.head = pg.Rect(self.x, self.y, self.width, self.height)
pg.draw.rect(surface, self.head_color, self.head)
if len(self.body) > 0:
for unit in self.body:
segment = pg.Rect(unit[0], unit[1], self.width, self.height)
pg.draw.rect(surface, self.body_color, segment)
self.seg.append(segment)

The draw_player function initializes the snake as a py.Rect object (as it provides an easy way to check for collision) and draws it on the provided ‘surface.’ Notice, however, we initialize the snakehead separately from the body. We do this as we consider them as two separate types of entities (the snake doesn’t eat via its whole body, only via its head). Also, we store the body segments as a different variable in player class (self.seg), so that later we can use it to check for collisions of the snake with its body easily. All the segments of the snake body are square-shaped with width = height = 10 (same as the snakehead).

  • add_unit
# Increases length of snake
def add_unit(self):
if len(self.body) != 0:
index = len(self.body)-1
x = self.body[index][0]
y = self.body[index][1]
self.body.append([x,y])
else:
self.body.append([1000,1000])

The add_unit function serves to increase the size of the snake. It checks first if the self.body list is empty, i.e., the snake has just the head. If that is the case, it just adds a new segment located at (1000,1000), which is outside the screen. Else, it adds a new segment which at the position of the last body segment. As the snake increases in size only when moving and after it has eaten the food, soon, the second body segment comes to where the head was present a moment ago. It all happens so fast that the user can’t see it. One can, in theory, initialize the body segment at the last position of the head, but for that, one cannot use the self.x self.y and one has to keep track of the last known position of the head, which is okay but increases the code, so I chose this shortcut, which is not noticeable (thanks to the fast computing power of today’s computers).

  • is_collision
# Checks if there is any collision
def is_collision(self):
# Collision with itself
for segment in self.seg:
if self.head.colliderect(segment):
return True
# Collision with the boundaries
if self.y < 0 or self.y > screen_height - self.height or self.x < 0 or self.x > screen_width - self.width:
return True

The is_collision function checks if the snake has had any collisions with itself or the boundaries of the game (i.e., screen edges). The first part checks for collision with itself using the colliderect() function of pygame. The second part checks if the snakehead has moved to coordinates out of the game screen.

  • move
# Moves the snake in the direction of head
def move(self):
for index in range(len(self.body)-1, 0, -1):
x = self.body[index-1][0]
y = self.body[index-1][1]
self.body[index] = [x,y]
if len(self.body) > 0:
self.body[0] = [self.x, self.y]
if self.direction == 'up':
self.y -= self.velocity
if self.direction == 'down':
self.y += self.velocity
if self.direction == 'left':
self.x -= self.velocity
if self.direction == 'right':
self.x += self.velocity

The move function moves the snake in two steps. First, it moves the body segments to the positions of the body segments before them, i.e., the last moves to second last’s position, second last moves to third last’s position, and so on, till the first body segment moves to the position of the head (taken care of by the first if statement). Then it moves the head in the direction of the snake with the initialized velocity.

  • change_direction
# Changes direction of head
def change_direction(self, direction):
if self.direction != 'down' and direction == 'up':
self.direction = 'up'
if self.direction != 'right' and direction == 'left':
self.direction = 'left'
if self.direction != 'up' and direction == 'down':
self.direction = 'down'
if self.direction != 'left' and direction == 'right':
self.direction = 'right'

The change_direction function takes in a direction as input and changes the direction of the snake accordingly. However, it does not change in the opposite direction, i.e., if the snake is moving downwards, then the direction can change to left or right but not upwards. The above characteristics make the game a bit more realistic and prevent body collisions due to inadvertent key presses in the wrong direction.

So we have just made the basic framework of our game, and in part 2, we will make it come alive. Rest a bit, the next post will be up soon, and then we can continue coding!

--

--

Arpit Omprakash
Byte-Sized-Code

I'm a Programming and Statistics enthusiast studying Biology. To find out if we have common interests, have a look at my thoughts: https://aceking007.github.io/