Creating a Classic – Snake

I once owned a Nokia 3310 (showing my age a bit here). This phone was and still is class for several reasons;

  1. The battery lasted forever (one charge per music festival forever)
  2. It was indestructible – rumour has it the cases they sold were to protect the GROUND FROM THE PHONE
  3. It had the snake game

Now obviously compared to modern games Snake is rubbish, but as a kid in school in the late 90’s it was class.

How Does the Game Work

Snake is a really basic game. Imagine you have a screen and on the screen there are two items;

  1. The Snake
  2. Food

The game would look like below;

The snake head a head – which controls the direction of travel. The goal is to move the snake to the food pieces and eat them. Every time the snake eats food it grows, and the food is repositioned. For example, later on in the game you could see the following.

The game ends when the snake collides into the walls or itself.

How can you Code the Game

PyGame

Pygame is a set of Python modules designed to help create videogames. They offer methods for drawing / handling events / playing sounds /receiving inputs. For this project I will be using Python and PyGame modules to recreate this classic. Throughout this explanation I will not document all the code (it would simply take too long) – I will highlight the key components but not necessarily various refactors or boiler plate settings in pyGame. I recommend you read this alongside the source code in GitHub.

How Does This Game Work

the game mechanics are quite simple we will create a grid on which we will place a food item at a random position. the snake will always start in a fixed position. The game will receive inputs for up, down, left and right and route the snake into that direction. When the snake enters a cell containing the food it will be made larger and every move the snake makes will be evaluated to see if it has hit itself or the wall, thereby ending the game.

The Snake Class

We will create a class for the snake it we can start with it having 2 properties

  1. body (a collection of positions that make up the body)
  2. direction (A way of knowing which direction the snake is heading in (left, right, up, down)

We will also have several methods

  1. drawSnake – loops through the body, for each position in the collection draw them onto the canvas
  2. moveSnake – moves the snake onwards a position
  3. growSnake – extends the body collection

The Food Class

the Food class needs to have the ability to show its location and will have 2 methods

  1. draw – draws the Cookie to the canvas
  2. reposition – will move the food to a new position

The Game Class

The game class needs to handle the events that happen within the course of the game it will have 2 properties;

  1. An instance of the Snake class (snake)
  2. An instance of the food class (food)

It will need a variety of methods to draw, check for end states and updating the screen

Coding the Game Loop

Games work a bit like a cartoon flipbook. A loop is started every small increment of time various conditions (inputs, events etc..) are evaluated with the screen updated the next step based on these.

Firstly, let’s make a grid. We have a couple of variables to define the cell size of our grid;

using these we can then make a pyGame screen (a surface) to render graphics on. We also set a framerate and a timer to regulate the “Flick book” settings

After some other setup we create the game loop. This is a loop that constantly is iterating, in essence it is an infinite loop – but we will put some code to break out of it when the application is closed;

Here you can see we are looping constantly and listening to events – we are listening for specific pyGame events corresponding to key presses or the application being closed (to stop the look and the code). Using this we now have a framework to control the snake – currently represented by the print statements.

Coding The Snake Class

As mentioned, the snake class consists of a body which is a collection of positions. These listed are the start positions in x,y values. If we imagine our grid the snake will be initialised (and start) in columns 3,4 and 5 and row 10.

The starting direction is a vector of 1,0 – so the snake will be facing right. and the newBlock boolean is false (see growing the snake regarding this).

Why vectors?

As mentioned initially the snake needs to be a collection of positions this could be done with any collection of two values, these could be objects, tuples a collection of arrays etc.. there are many options for storing 2 dimensional data. However While doing research on a good way of coding this, I discover the Vector2 object – this object has one characteristic that is really useful in this context. Let’s compare what we would have to do using lists to move the snake right Vs what we have to do in vectors;

Snakes Head PositionData Representing the positionDirection we want to move snakeCode to move Snake
Listx=5, y=4l=[5,4]Rightl=list[0]+=1
Vectorx=5, y=4v=vector2(5,4)Rightright=Vector2(1,0)
v+=right

Put simply we can add vectors to each other 5,4 + 1,0 is 6,4. This means we can have one vector for left, right, up and down and reuse these. We don’t have to care what the current position of the head is at all, we can simply add a left vector to it to make it turn left, a right vector to make it turn right and so on.

In the same way you can add vectors – you can subtract them. This is very useful for us later on when establishing what graphics to use (see Extending the game – Graphics).

BACK TO THE SNAKE..

So we have our positions, a size (the number of vectors in the list) and a direction of right. The next thing we need to do is move the snake.

Moving the Snake

Firstly, before we can move the snake, we need to know what direction to move it. As mentioned above we have a very neat way of assigning direction changes onto the collection – so lets assign these to the relevant keys in our game loop.

WHAT?!

Ok the basic thing you would expect is included here – If you press right the snakes direction is assigned to vector2(1,0) – left is the opposite (-1,0) and so on. But there is more code. We don’t want to be able to allow the snake to go back in the same direction it came from so if you press down when the snake is traveling up we don’t want the snake to turn. To do this we only allow directional changes to take place if the snake isn’t travelling in the opposite direction already. So for example if we press down vector(0,1) – only apply this is the direction y axis isn’t -1 (up). PLEASE NOTE: “up” is a negative Y value.

Now we have a direction – we need to actually move the snake. This is fairly simple. We will remove the last item of the body collection and add a new item in the direction of travel for example;

If the snake is traveling up the y axis;

and then turns right;

the end of the list can be removed (the grey cell) and the new position can be added at the front of the list (the orange cell). This is coded below;
Growing the Snake

If you think about it growing the snake is similar to moving it except we don’t need to remove the last item in the queue – just add one onto the head in the direction of travel. Growing the snake can therefore be coded as follows;

Firstly we change the newBlock boolean to true (aha! you say)

Then we can extend the movement method;
So if the newBlock value is true (this will be set when the snake eats the food) it will just add a piece onto the snake

Drawing the Snake

The final Snake method is how to draw the snake. pyGame has a rectangle object that will help us here.

we will simply loop through the snakes body and draw rectangles for each position with a colour of 183,11,122 (RGB).

Coding the Food

The food is initialised in a random vector on the grid;

it has a reposition method (for when it is eaten) to move it to a new random position that is identical to the one above. And it has a method to draw the position it is in (in 126,116,114 RGB).

Main Class

this class will hold all the methods for playing the game. This includes the following tasks;

  1. Checking if the snake touches the food
  2. Checking for conditions that end the game (hitting a wall or the snake hitting itself)

The main class has the following constructor;

it receives an instance of snake and food. It can use their methods to control the game.

Eating Food

we check if the food position is the same as the snakes head (first position in the snakes body) – if it is move the food and grow the snake.

Checking for Collisions

Firstly we check if the head of the snake is greater than the number of cells in either the x or y direction. This would mean the head of the snake has exceeded the grid of the game and a collusion has taken place with the wall;

The other type of collision is if the snake hits itself. we check here if the head of the snake exists anywhere in its own body – is the first item of the body collection the same as any of the other items in the collection.

Game Over

the gameOver method simply quits the application;

Running the Game.

with a couple of other lines regarding updating the pyGame objects the game is in a position to run. If we execute the code we can see the folllowing;

We can eat move around with the keys and eat the green square (food) and then we have the following (note how the food has repositioned and the snake grown).
And if we hit the snake or the wall the application closes. At this point we have ourselves a working retro snake game!

but how can we improve this?

Extending the Game – Score/Game Over

One option is to keep a score of the pieces of food consumed. keeping the score is really easy – we simply add to a total (a new property in the snake class) when we grow the snake;

But we will need a way of displaying the score. Perhaps instead of exiting the game we could have a game over screen. we could add this to the Main Class

and control this in the game loop;

Here we validate if a new property in the mainGame class (gameActive) is False if it is we display the gameover screen and loop around displaying this until a user hits escape to start a new game – whereby the score and snakes are reset.

Extending the Game – Graphics

One other way we could extend the game is making the graphics better. I replaced the food rectangle with a cookie graphic (makes sense doesn’t it!) that is then wrapped in a rectangle and stole some snake images from the internet. however, the snake images vary depending on the direction of travel;

this presents us with more coding to do – as the squares we previously drew were the same in every direction. We need to update how we draw the snake. This is now spilt out into 3 methods and is probably the most complicated code in the game;
  1. updateHeadGraphics
  2. updateTailGraphics
  3. drawSnake

Head Graphics

in order to tell if the head is looking left, right, up or down we can use the vectors. the body’s first position after the head deducted by the head will give us a value that give us the relationship between the head and the body for example;

[5,10] – [6,10] = [-1,0]. the first block of the body is therefore to the left of the head (-1 being left). So the head is on the right of the body.

we can extend this logic out to all head positions;

it should be noted that a lot of new properties are added to the snake class to hold all the graphics and their starting position (head and tail).

Tail Graphics

The same approach is taken with the tail of the snake but looking at the end of the snake;

Body Graphics

This is the most complex section as the body varies in length and direction. different graphics are used for when the snake is travelling vertically, horizontally and in all the directions it can turn.

Firstly we need to loop through all the items in the body (as the snake can have multiple turns);

notice the use of the enumerate keyword – we want to know the index of the position in the collection as we are going to compare each element with its preceding element.

Establishing if the element and its preceding value is fairly straightforward;

if you have the same x value with the previous element – you are vertical (you haven’t moved along the x positions). and the same is true with the y elements if you have the same values with the preceding position, you haven’t moved vertically but have horizontally. In these positions we can easily assign a vertical position for the graphics or a horizontal one.

Corners

Let’s consider a corner in the middle of the snake;

this corner is moving from right to up, or is the head was at the other end of the snake from down to right (the graphic would be the same). we can calculate the direction using vectors but we need to consider the position before us in the collection and the position after to establish the path taken;

So to know what graphic to use in the grey cell below – we need to evaluate both orange cells;

the code for this is below.
We are evaluating previous block and next block. and looking at their x and y values and seeing their path relative to our position. for our example above we would see the following
my previous block is 1 to the right (x+1) my next block is one above (y-1) again note moving vertically is negative. this would give us the top right corner graphic. This pattern is repeated for the other corners. The or condition is because this turn shape can be repeated in the opposite direction.

Phew… hopefully that makes some sense. We can also update the food Grahics

The Cookie

Rest assured this is SO much easier than modifying the snake body. The food is one rectangle and it simply can be replaced with a graphic.

At this point our snake and cookie look much more modern, hopefully this was worth the effort.

When the game ends you can also see the Game Over screen displaying the score

Extending the Game – Sounds

pyGame makes it very easy to add sound effects to game via a mixer object. You can initialise this.

and then use it to play sounds on events – for example when the snake grows you can play an eating noise;

Conclusion

This was a really fun project, and I got a small insight into how the pyGames library works. The use of vectors particularly interesting and I’d have never consider that code before and it was something I noticed in looking on other developers pyGame projects. I don’t know if the game is as additive as it was to a 13 year old me, I guess I have better things to do with my life now! but we shall see. I did really enjoy this trip down memory lane though.

Code is in GitHub: Snake (github.com)