In this activity, we will be creating a Snake game, where the player controls a snake that grows every time it eats food. If the snake crosses over itself, then the game is over.

Taken and adapted from: Free Python Games

1– Setup the screen

Go to trinkiet.io and start a new Python project: http://bit.ly/pythontrinket (log into your account if you have one or create one to save your progress). Then:

from turtle import *

Beneath this (leave a few lines as a gap, as we will be putting more code here later), add some code to set up the screen size, and the turtle for drawing:

Screen().setup(420, 420, 370, 0)
hideturtle()
Screen().tracer(0, 0)

done()

The first line sets up the screen size that we will be using to play the game. We then hide the turtle so that we cannot see it while the screen is redrawing. The Screen().tracer(0, 0) means that the drawing all happens in the background, and we will need to call update() to see any changes on screen. This will make our game run faster.

2– Create the snake

We will store the positions of all the segments of our snake in an array of coordinates, and then loop through each segment and draw a square at that position.

In the gap after the import line, add the following function that draws a square at position (x, y).

snake = [[10, 0]]

def draw_square(x, y, size, name):
    up()
    goto(x, y)
    down()
    color(name)
    begin_fill()

    for count in range(4):
      forward(size)
      left(90)

    end_fill()

Then, after this, add another function that we will use to move and draw the snake:

def move():
  clear()
  
  for body in snake:
    draw_square(body[0], body[1], 9, 'black')

  update()
  Screen().ontimer(move, 100)

This function first clears the existing screen, then loops through every segment of our snake and draws a black square in the background. The call to update() then displays all of the background drawing. The final line sets a timer of 100 milliseconds before calling the move function again.

We need to make sure this function is called, so just above the done() at the bottom of the code, add the following:

move()

3– Move the snake

Our snake will continue to move in one direction until we press an arrow key. We need another variable to store the current direction of the snake.

At the top of your code, add the following code after the snake = [[10, 0]] line:

aim = [0, -10]

These are coordinates that say our snake will start moving down the screen.

Immediately after this, add a function that will change the direction of our snake:

def change(x, y):
  aim[0] = x
  aim[1] = y

We now need some code to monitor for our player pressing the arrow keys to change the direction of our snake. At the bottom of your code, just before the move() call, add the following code:

Screen().listen()
Screen().onkey(lambda: change(10, 0), 'Right')
Screen().onkey(lambda: change(-10, 0), 'Left')
Screen().onkey(lambda: change(0, 10), 'Up')
Screen().onkey(lambda: change(0, -10), 'Down')

Now our program will listen for our player pressing a key, and if it detects one of the four arrow keys, it will change the direction.

Finally, we need to update the move function to update the position of the snake depending on the current settings in the aim variable. At the beginning of the move function, before it calls clear(), add the following code:

  head = [snake[-1][0] + aim[0], snake[-1][1] + aim[1]]

  snake.append(head)
  
  snake.pop(0)

We create a new segment of the snake (head) by adding the values in our aim variable to the last item in our list of snake segments (snake[-1]). We then add this (append) to the end of our list of segments, and finally remove the end of the snake (pop). This should leave us with a snake of the same length (currently one square), but that has moved one square forward.

Try running your program. Use the arrow keys to change direction. After clicking run, you will need to click on the output window so that the program can start listening for your keypresses.

4– Check for the edge of the screen

Our game finishes if the head of our snake goes off of the screen. We can do this with a simple check in our move function. Start by creating a function that can check if a given coordinate is inside the screen. Add the following code just before the move function:

def inside(head):
    return -200 < head[0] < 190 and -200 < head[1] < 190

If we pass the head of our snake into this function, it will check whether the x and y coordinates are inside the boundaries of the screen. We need to call this within our move function. Add the following code just before the snake.append(head) call:

  if not inside(head):
    draw_square(head[0], head[1], 9, 'red')
    update()
    return

If the head of the snake is not inside the screen, we will draw a red square for the head, then update the screen, and return from the move function, ending the game.

5– Adding food

As our snake moves around the screen, we will randomly add squares of food. Every time we eat food, our snake will increase in length by one segment. We need a variable to store the position of the current item of food. At the top of our code, add the following line after aim = [0, -10]:

food = [20, 0]

Now we need to check if the head of the snake is on a food square. If it is, our snake eats it, and we create a new square at a random position. To extend our snake, we can ignore the call to snake.pop(0).

At the top of your code, add another import:

from random import randrange

In the move function, replace the call to snake.pop(0) so that it looks like this:

  if head == food:
    food[0] = randrange(-15, 15) * 10
    food[1] = randrange(-15, 15) * 10
  else:
    snake.pop(0)

We also need to draw the food on the screen. After the call to clear(), add the following line:

draw_square(food[0], food[1], 9, 'green')

Our snake should now move around the screen, and extend by one segment every time we eat some food.

6– Check we don’t cross our tail

The only thing left is to check that the head of the snake doesn’t cross the tail. We have a list of all our snake segments stored in snake, so all we need to do is check if the head is in that collection.

Change the line if not inside(head): so that it looks like this:

  if not inside(head) or head in snake:

Our game is now complete!

Full Code listing

Your final code should be similar to this:

from turtle import *
from random import randrange

snake = [[10, 0]]
aim = [0, -10]
food = [20, 0]

def change(x, y):
  aim[0] = x
  aim[1] = y
  
def draw_square(x, y, size, name):
    up()
    goto(x, y)
    down()
    color(name)
    begin_fill()

    for count in range(4):
      forward(size)
      left(90)

    end_fill()
  
def inside(head):
    return -200 < head[0] < 190 and -200 < head[1] < 190
    
def move():
  head = [snake[-1][0] + aim[0], snake[-1][1] + aim[1]]

  if not inside(head) or head in snake:
    draw_square(head[0], head[1], 9, 'red')
    update()
    return

  snake.append(head)
  
  if head == food:
    food[0] = randrange(-15, 15) * 10
    food[1] = randrange(-15, 15) * 10
  else:
    snake.pop(0)
    
  clear()
  
  for body in snake:
    draw_square(body[0], body[1], 9, 'black')
    
  draw_square(food[0], food[1], 9, 'green')
  update()
  Screen().ontimer(move, 100)

Screen().setup(420, 420, 370, 0)
hideturtle()
Screen().tracer(0, 0)

Screen().listen()
Screen().onkey(lambda: change(10, 0), 'Right')
Screen().onkey(lambda: change(-10, 0), 'Left')
Screen().onkey(lambda: change(0, 10), 'Up')
Screen().onkey(lambda: change(0, -10), 'Down')

move()

done()

A full listing is available here: https://trinket.io/python/eed9b1f740

Challenges

To extend your program, try one of the following challenges:

A full listing with challenges is available here: https://trinket.io/python/e1dbb04c95