Python Snake Game



Let's make a Snake game in Python (in less than 100 lines code)!

For those who don't know, the white thing is the snake. It can be controlled by the player to go up, down, left and right. Every time the snake eats one of those blue things (let's call it food), it gets bigger.

Most snake games are a bit more complex though. There are walls that kill the snake when it runs into it, there is food that kills it if the snake eats it and there are different levels and speeds. However, to keep everything nice and simple, we will only focus on the snake and its food.

Preparations

We will make this game with Python and OpenGL. Please take a look at our Default Python IDE and Python OpenGL tutorials to learn how to set it up properly.

We will start with the code from the Python OpenGL Introduction tutorial with just a few modifications:

from OpenGL. GL import *

from OpenGL. GLUT import *

from OpenGL. GLU import *



window = 0 # glut window number

width , height = 500 , 500 # window size

field_width , field_height = 50 , 50 # internal resolution



def refresh2d_custom ( width , height , internal_width , internal_height ) :

glViewport ( 0 , 0 , width , height )

glMatrixMode ( GL_PROJECTION )

glLoadIdentity ( )

glOrtho ( 0.0 , internal_width , 0.0 , internal_height , 0.0 , 1.0 )

glMatrixMode ( GL_MODELVIEW )

glLoadIdentity ( )



def draw_rect ( x , y , width , height ) :

glBegin ( GL_QUADS ) # start drawing a rectangle

glVertex2f ( x , y ) # bottom left point

glVertex2f ( x + width , y ) # bottom right point

glVertex2f ( x + width , y + height ) # top right point

glVertex2f ( x , y + height ) # top left point

glEnd ( ) # done drawing a rectangle



def draw ( ) : # draw is called all the time

glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ) # clear the screen

glLoadIdentity ( ) # reset position

refresh2d_custom ( width , height , field_width , field_height )



# TODO draw things



glutSwapBuffers ( ) # important for double buffering





# initialization

glutInit ( ) # initialize glut

glutInitDisplayMode ( GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH )

glutInitWindowSize ( width , height ) # set window size

glutInitWindowPosition ( 0 , 0 ) # set window position

window = glutCreateWindow ( "noobtuts.com" ) # create window with title

glutDisplayFunc ( draw ) # set draw function callback

glutIdleFunc ( draw ) # draw all the time

glutMainLoop ( ) # start everything

Note: don't worry, we don't see anything yet if we run the program (except a black screen).

The interesting thing about this code is the refresh2d_custom function. As a reminder, the refresh2d function in our Python OpenGL tutorial was used to tell OpenGL that we want to draw things in 2D.

The custom refresh2d function basically does the same. The difference is that it takes two more parameters: internal_width and internal_height for the internal resolution.

Please note that the internal resolution is completely independent from the window resolution.

To avoid confusion we will take a look at a few chess field pictures. They all have a window resolution of 500 x 500 pixels, but they have a different internal resolution:



50x50 internal resolution (sorry for making your eyes go crazy)



10x10 internal resolution



2x2 internal resolution

So why all this? Well the reason is that we want to make our lives easier. We want our food to be just one pixel and our snake to be one (or more depending on the length) pixels. If we would do this with the default internal resolution, our snake and our food would look like this:



No this is not just a black window, there are some tiny little pixels in there. They are so tiny because our internal resolution is 500 by 500 pixels, so obviously everything looks really small.

Now if we choose a much smaller internal resolution like 50 by 50 pixels, we get this:



Every pixel that we draw is nicely visible without the need for a magnifying glass. Much better, right?

Note: our game's internal resolution is hold in the field_width and field_height variables.

Okay, now that we talked about internal and external resolutions, we can focus on the actual game.

Creating the Snake

Let's create the main part of our game: the Snake.

Snake Variable

The snake is just a list of pixels at different positions. In the beginning it's only one pixel, after the snake eats something it's two pixels, then three and so on. The snake's head (the first element) will be in the first position in the list.

We will use two variables in order to represent our snake: the just mentioned list, and the current movement direction (as x,y coordinates). As usual, they will be placed at the top of our program (where we stored our window size):

snake = [ ] # snake list of (x, y) positions

snake_dir = ( 1 , 0 ) # snake movement direction

Note: snake dir (1, 0) means that its current movement direction is x=1 and y=0, which means it moves to the right.

But wait, at the beginning the snake already has one element (its head). So let's remove the previous definition and add the snake list with one initial element in form of a (x, y) position:

snake = [ ( 20 , 20 ) ] # snake list of (x, y) positions

snake_dir = ( 1 , 0 ) # snake movement direction

This means that the snake head is at the position (x=20, y=20) in the beginning. To make this more clear: if the snake would eat something, our snake list might look like this:

[ ( 20 , 20 ) , ( 21 , 20 ) ] # snake after eating

Drawing the Snake

We really want to see something now. Let's create a draw_snake() function that throws every pixel in our snake list onto the screen:

def draw_snake ( ) :

glColor3f ( 1.0 , 1.0 , 1.0 ) # set color to white

for x , y in snake: # go through each (x, y) entry

draw_rect ( x , y , 1 , 1 ) # draw it at (x, y) with width=1 and height=1

Now that we have a function that draws the snake, we also have to use it in our draw() function:

def draw ( ) : # draw is called all the time

glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ) # clear the screen

glLoadIdentity ( ) # reset position

refresh2d_custom ( width , height , field_width , field_height )



draw_snake ( ) # draw the snake



glutSwapBuffers ( ) # important for double buffering

Let's press the run button and see what happens:



We can see our snake's head, awesome!

Moving the Snake

We will use the W, S, A and D keys to move the snake up, down, left and right. Things like movement should be done in a update function. We don't have one yet, but we can easily add one. At first we create it:

def update ( value ) :

# TODO update things...



glutTimerFunc ( interval , update , 0 ) # trigger next update

The interval is defined at the top (where we defined the window size) again like this:

interval = 200 # update interval in milliseconds

And finally we have to tell OpenGL that we have a update function now. We will do this with glutTimerFunc in our OpenGL initialization. The new initialization part looks like this:

# initialization

glutInit ( ) # initialize glut

glutInitDisplayMode ( GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH )

glutInitWindowSize ( width , height ) # set window size

glutInitWindowPosition ( 0 , 0 ) # set window position

window = glutCreateWindow ( "noobtuts.com" ) # create window with title

glutDisplayFunc ( draw ) # set draw function callback

glutIdleFunc ( draw ) # draw all the time

glutTimerFunc ( interval , update , 0 ) # trigger next update

glutMainLoop ( ) # start everything

Explanation: in our OpenGL initialization we use glutTimerFunc to tell OpenGL that it should call our update function in 200 milliseconds (specified in interval).

After we start the program, OpenGL will call our update function after 200ms. Our update function itself doesn't do anything yet, except tell OpenGL to call it again in 200ms. Hence we created our update loop.

Okay, back to the snake's movement...

Let's assume the snake already ate something three times. Hence it looks like this (as text version):

o

o

o

o

If the user would move it to the right, it would then look like this the next time:

oo

o

o

So the obvious way would be to create an algorithm that first moves the snake's head to the new position and then let's every other entry in our snake list follow the snake's head by one step.

Since this sounds kinda complicated, we will use a little trick:

Instead of moving every single element by one step, we will just remove the last element and put it to the new position.

Here is our wonderful text snake again, this time "x" is the last element:

o

o

o

x

Now if the player wants to move it to the right, instead of moving the first one to the right and letting everything else follow, we will simply remove the x from the end and put it to the new position:

ox

o

o

This way it appears that the whole snake moved, even though we just removed the last element and made it the new head.

Note: from a performance side, this is just beautiful. It means that in each update call, instead of doing "n" calculations ("n" is the snake length) we only have to do 2 calculations. This kind of trick can make the difference between 60 fps and 30 fps in bigger games.

Anyway, let's create an algorithm that does that for us. At first we need a function that adds two positions. Example:

(3, 4) + (1, 2) = (4, 6).

We will name the function vec_add which stands for "Add Vectors":

def vec_add ( ( x1 , y1 ) , ( x2 , y2 ) ) :

return ( x1 + x2 , y1 + y2 )

Note: more about Vectors in Python can be learned in our Python Vector tutorial.

We need this function in order to move the snake. In each update function we want to move it a bit towards the snake_dir variable that we defined before. The math will look like this:

new_pos = vec_add ( snake [ 0 ] , snake_dir )

Note: snake[0] is the first entry in our snake list, which is the snake head.

Enough talking about math, let's implement our snake movement in our update function:

def update ( value ) :

snake. insert ( 0 , vec_add ( snake [ 0 ] , snake_dir ) ) # insert new position in the beginning of the snake list

snake. pop ( ) # remove the last element



glutTimerFunc ( interval , update , 0 ) # trigger next update

One call to insert and one call to pop (which removes ("pops") the last element) does all the magic.

If we run the game, we can now see the snake moving to the right all the time.

Note: obviously we don't see all the magic yet since the snake only consists of one element.

Since we want to be able to move it with the W, S, A and D keys, we will have to check if those were pressed by the player. We have to do two things to check the keys. At first, we create a keys() function:

def keyboard ( *args ) :

global snake_dir # important if we want to set it to a new value



if args [ 0 ] == 'w' :

snake_dir = ( 0 , 1 ) # up

if args [ 0 ] == 's' :

snake_dir = ( 0 , - 1 ) # down

if args [ 0 ] == 'a' :

snake_dir = ( - 1 , 0 ) # left

if args [ 0 ] == 'd' :

snake_dir = ( 1 , 0 ) # right

Note: this is just the way it's done, we won't worry about it too much.

Again we have to tell OpenGL that we have a key checking function. We will go down to our initialization code and add glutKeyboardFunc(keyboard):

# initialization

glutInit ( ) # initialize glut

glutInitDisplayMode ( GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH )

glutInitWindowSize ( width , height ) # set window size

glutInitWindowPosition ( 0 , 0 ) # set window position

window = glutCreateWindow ( "noobtuts.com" ) # create window with title

glutDisplayFunc ( draw ) # set draw function callback

glutIdleFunc ( draw ) # draw all the time

glutTimerFunc ( interval , update , 0 ) # trigger next update

glutKeyboardFunc ( keyboard ) # tell opengl that we want to check keys

glutMainLoop ( ) # start everything

That's it. If we run the game, we can now move the snake with the W, S, A and D keys. Kinda looks like Snake already...

Spawning the Food

A snake has to eat, we will have to throw some food into the game every now and then. Again we will just use a simple list to store the food. The list will be defined at the top again (where we stored our window size):

food = [ ] # food list of type (x, y)

We will use randomness in order to spawn food at some random position in a random interval. If we want to use Python's random function, we will have to import something first (at the very top of our program):

from random import randint

Alright, let's spawn the food in our update function:

def update ( value ) :

# move snake

snake. insert ( 0 , vec_add ( snake [ 0 ] , snake_dir ) ) # insert new position in the beginning of the snake list

snake. pop ( ) # remove the last element



# spawn food

r = randint ( 0 , 20 ) # spawn food with 5% chance

if r == 0 :

x , y = randint ( 0 , field_width ) , randint ( 0 , field_height ) # random spawn pos

food. append ( ( x , y ) )



glutTimerFunc ( interval , update , 0 ) # trigger next update

So what happens is that we create a random value and store it in our r variable. This value is between 0 and 20. Then we check if the value is 0 (which happens with a 5% chance because 100 divided by 20 is 5). If this happens we use the random function again to create a random x and y value and then we append it to our food list.

In order to actually see something, we have to draw our food in the same way that we used to draw our snake (just with a different color):

def draw_food ( ) :

glColor3f ( 0.5 , 0.5 , 1.0 ) # set color to blue

for x , y in food: # go through each (x, y) entry

draw_rect ( x , y , 1 , 1 ) # draw it at (x, y) with width=1 and height=1

As usual, we then use the draw_food() function in our draw function:

def draw ( ) : # draw is called all the time

glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ) # clear the screen

glLoadIdentity ( ) # reset position

refresh2d_custom ( width , height , field_width , field_height )



draw_food ( ) # draw the food

draw_snake ( ) # draw the snake



glutSwapBuffers ( ) # important for double buffering

If we run the game and wait a while, we should see food spawning all over our screen:



Eating the Food

So we created our snake, we added some food, but sadly the snake still doesn't know how to eat it.

The good news is, this is really easy to do. In each update call, we simply find out if the snake's head (at snake[0]) is at the same position as any of the food in the food list. If so, the snake will eat it (which means that the snake gets longer and that the food is removed).

The code (to be placed in the update function):

# let the snake eat the food

( hx , hy ) = snake [ 0 ] # get the snake's head x and y position

for x , y in food: # go through the food list

if hx == x and hy == y: # is the head where the food is?

snake. append ( ( x , y ) ) # make the snake longer

food. remove ( ( x , y ) ) # remove the food

If we run the game and move our snake to the food, it now gets longer and longer:



Summary

There we go, a lightweight Snake game written in less than a hundred lines of Python code. As usual, now it's your turn to make the game fun. Add different kinds of food, let the snake die if it hits the wall, add different levels and think about how to make the snake die if it collides with itself. Maybe even add some background music and a few textures. It's your game world, you can do whatever you like!