I once owned a Nokia 3310 (showing my age a bit here). This phone was and still is class for several reasons;
- The battery lasted forever (one charge per music festival forever)
- It was indestructible – rumour has it the cases they sold were to protect the GROUND FROM THE PHONE
- 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;
- The Snake
- Food
The game would look like below;
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
- body (a collection of positions that make up the body)
- direction (A way of knowing which direction the snake is heading in (left, right, up, down)
We will also have several methods
- drawSnake – loops through the body, for each position in the collection draw them onto the canvas
- moveSnake – moves the snake onwards a position
- 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
- draw – draws the Cookie to the canvas
- 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;
- An instance of the Snake class (snake)
- 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;
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
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 Position | Data Representing the position | Direction we want to move snake | Code to move Snake | |
List | x=5, y=4 | l=[5,4] | Right | l=list[0]+=1 |
Vector | x=5, y=4 | v=vector2(5,4) | Right | right=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.
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;
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)
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;
- Checking if the snake touches the food
- Checking for conditions that end the game (hitting a wall or the snake hitting itself)
The main class has the following constructor;
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;
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;
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;
- updateHeadGraphics
- updateTailGraphics
- 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;
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);
Establishing if the element and its preceding value is fairly straightforward;
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;
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)