In this post we will explain how to implement a simple Snake game on Python using Pygame, and propose a very straightforward strategy for the computer to play the game on its own. In a later post, we will use Reinforcement Learning techniques!
Gameplay using our simple BFS algorithm.
Building the game logic
First we have to create the basic logic making the game work. Our main class will be the Head
of the snake. We can set up some constants first: our gameboard will consist of \(m\times n\) squares of a given size. We use m=40
, n=30
and SIZE=20
to get a screen of size \(800\times 600\).
It will have an update
method, that moves the head of the snake according to some input (either user input, or what our algorithm generates in case it’s playing on its own). To handle inputs given in some way that’s not the usual keypress event, we will need to have our personalized PyGame events.
Using these personalized events, we can simply update the direction accordingly.
We can similarly handle user input using the KEYDOWN
event in PyGame.
Now we can start the main game loop. We need to initialize the screen for the game and set the initial position of the snake and the apple. We keep track of the position of the head of the snake with snaketip
and of the whole body with snake
.
We also need to initialize the screen and direction of movement of the snake.
The advantage of keeping track of the direction as a number is that our events are consecutive and we can very concisely trigger our events as follows.
We only need to worry about the main loop of the game. We update the position of the snake by forgetting the first square and adding the snaketip
at the end (that is, we update the squares consisting of the body of the snake in a first in, first out way).
Finally, we need to make a new apple appear once the snake eats the current apple on the screen. This is simply handled by the following
This is the basic logic behind the game. The whole code can be found on my Github. We can now try to figure out how to make the computer play on its own!
The algorithm
The idea is simple: just take the shortest path from the head of the snake towards the apple. Of course, we don’t want the path to go through squares which are occupied by the body of the snake. Such a naive algorithm works decently, despite the obvious drawbacks it has: sometimes the shortest path will trap the snake and the only possible outcome will be biting its own tail.
There’s also a much simpler thing we can do. We could transverse the whole screen everytime, without even bothering about the position of the apple. This would be extremely slow. The ideal solution to this problem would involve some sort of trade-off between time taken to get the apple and unraveling the body so that the snake doesn’t trap itself. We will discuss this in terms of rewards when we approach this problem using reinforcement learning.
The idea for finding the shortest path is to model our problem in terms of graphs and use breadth-first search. Each square in the screen will consist of a node, and two nodes will be joined by an edge if the corresponding squares are adjacent and neither of them is occupied by some part of the body of the snake.
To do this, we will keep track of the occupied squares in a grid
of size \(40\times 30\) (because of the dimensions of our gamescreen) where the \((i,j)\)-th entry is \(1\) if it’s occupied and \(0\) otherwise.
We also need a way of translating from the positions we used before (that are the ones used for rendering nicely in PyGame) and the corresponding position in the grid. This is because coordinates in Pygame set the \((0,0)\) in the upper-left corner, the \(x\)-axis moves to the right in the positive direction (as usual), whereas the \(y\)-axis moves down in the positive direction. The following simple function deals with this issue.
Having all the necessary ingredients, the algorithm we use for finding the shortest path is a simple breadth-first search.
And this is it! We simply need to format the path we obtain to be able to use it in the game loop, but if you’re interested you can check those details on the Github repository.