Codementor Events

Coding the Classic Snake Game with Python Turtle Graphics

Published Sep 01, 2020Last updated Sep 03, 2020
Coding the Classic Snake Game with Python Turtle Graphics

Python Turtle Graphics is awesome! It can be used to learn and teach Python programming and Computer Science from elementary to advanced level. There is a [post on my blog][1] about the Turtle Graphics demos which come with IDLE (the development environment that ships with Python) - check them out to get an idea of some of the fun stuff you can do!

You can play to a version of [the Classic Snake Game on repl.it here.][2]

Click on the Turtle window to enable keyboard control using the arrow keys.

python-classic-snake-game-400w.png

Python Snake Game Program Explained

Snake Representation

We represent our snake as a list of pairs of coordinates:

snake = [[0, 0], [20, 0], [40, 0]]

We could use sn to notate the nth segment:

[s1, s2, s3]

How does the snake move?

There are several ways to approach programming the Classic Snake Game in Python (or other languages for that matter). The main challenge is how to get the snake to move.

Here are two ways to conceptualize what is basically the same effect:

    1. Chop off the last segment, and add it to the front of the snake each time the snake "moves".
    1. Create a copy of the head, add it to the front of the snake and then chop off the last segment.

These are the steps for the second version:

NB For this demonstration, consider the leftmost list item to be the tail and the rightmost to be the head.

  • create a new list item for the new head position:

new_head = snake[-1].copy() # snake[-1] means the rightmost item. Must be copied or original would be modified by next step.

That is,

new_head = s3

or

new_head = [40, 0]

  • Increment the x coordinate of new_head, giving [60, 0].

  • Append new head to the snake:

snake.append(new_head)

snake = [[0, 0], [20, 0], [40, 0], [60, 0]] now.

or

snake = [s1, s2, s3, H]

  • Finally, remove the leftmost item (s1, or [0, 0]), using snake.pop(0).

Ta da the snake has moved forward one position!

Moving the Snake with Python Turtle Graphics

The basic movement of the snake can be implemented in a simple program as shown here:

import turtle


def move_snake():
    pen.clearstamps()
    new_head = snake[-1].copy()
    new_head[0] += 20
    snake.append(new_head)
    snake.pop(0)
    for segment in snake:
        pen.goto(segment[0], segment[1])
        pen.stamp()
    screen.update()
    turtle.ontimer(move_snake, 200)


snake = [[0, 0], [20, 0], [40, 0]]
screen = turtle.Screen()
screen.tracer(0)
pen = turtle.Turtle("square")
pen.penup()

for segment in snake:
    pen.goto(segment[0], segment[1])
    pen.stamp()
move_snake()

turtle.done()

For info on using the super-handy stamp() function of Python Turtle Graphics, check out [my video on Youtube][3]

Python Classic Snake Game Code Listing

The listing for our Snake Game is below. Depending on your level of experiece, you may be able to understand exactly how it works or maybe just some of it. That's all fine.

Whatever your level, you should experiment with the code, play with it. For example you could change some colours, or the speed of the snake, or the controls etc.

For more experienced programmers, why not improve upon the basic idea by adding scoring and other features?

Happy coding,

Robin Andrews

""" A simple snake game using Turtle Graphics. """
import turtle
import random

WIDTH = 500
HEIGHT = 500
FOOD_SIZE = 10
DELAY = 100  # milliseconds

offsets = {
    "up": (0, 20),
    "down": (0, -20),
    "left": (-20, 0),
    "right": (20, 0)
}

def reset():
    global snake, snake_direction, food_pos, pen
    snake = [[0, 0], [0, 20], [0, 40], [0, 60], [0, 80]]
    snake_direction = "up"
    food_pos = get_random_food_pos()
    food.goto(food_pos)
    # screen.update() Only needed if we are fussed about drawing food before next call to `draw_snake()`.
    move_snake()

def move_snake():
    global snake_direction

    #  Next position for head of snake.
    new_head = snake[-1].copy()
    new_head[0] = snake[-1][0] + offsets[snake_direction][0]
    new_head[1] = snake[-1][1] + offsets[snake_direction][1]

    # Check self-collision
    if new_head in snake[:-1]:  # Or collision with walls?
        reset()
    else:
        # No self-collision so we can continue moving the snake.
        snake.append(new_head)

        # Check food collision
        if not food_collision():
            snake.pop(0)  # Keep the snake the same length unless fed.

        #  Allow screen wrapping
        if snake[-1][0] > WIDTH / 2:
            snake[-1][0] -= WIDTH
        elif snake[-1][0] < - WIDTH / 2:
            snake[-1][0] += WIDTH
        elif snake[-1][1] > HEIGHT / 2:
            snake[-1][1] -= HEIGHT
        elif snake[-1][1] < -HEIGHT / 2:
            snake[-1][1] += HEIGHT

        # Clear previous snake stamps
        pen.clearstamps()

        # Draw snake
        for segment in snake:
            pen.goto(segment[0], segment[1])
            pen.stamp()

        # Refresh screen
        screen.update()

        # Rinse and repeat
        turtle.ontimer(move_snake, DELAY)

def food_collision():
    global food_pos
    if get_distance(snake[-1], food_pos) < 20:
        food_pos = get_random_food_pos()
        food.goto(food_pos)
        return True
    return False

def get_random_food_pos():
    x = random.randint(- WIDTH / 2 + FOOD_SIZE, WIDTH / 2 - FOOD_SIZE)
    y = random.randint(- HEIGHT / 2 + FOOD_SIZE, HEIGHT / 2 - FOOD_SIZE)
    return (x, y)

def get_distance(pos1, pos2):
    x1, y1 = pos1
    x2, y2 = pos2
    distance = ((y2 - y1) ** 2 + (x2 - x1) ** 2) ** 0.5
    return distance

def go_up():
    global snake_direction
    if snake_direction != "down":
        snake_direction = "up"

def go_right():
    global snake_direction
    if snake_direction != "left":
        snake_direction = "right"

def go_down():
    global snake_direction
    if snake_direction != "up":
        snake_direction = "down"

def go_left():
    global snake_direction
    if snake_direction != "right":
        snake_direction = "left"

# Screen
screen = turtle.Screen()
screen.setup(WIDTH, HEIGHT)
screen.title("Snake")
screen.bgcolor("green")
screen.setup(500, 500)
screen.tracer(0)

# Pen
pen = turtle.Turtle("square")
pen.penup()

# Food
food = turtle.Turtle()
food.shape("circle")
food.color("red")
food.shapesize(FOOD_SIZE / 20)  # Default size of turtle "square" shape is 20.
food.penup()

# Event handlers
screen.listen()
screen.onkey(go_up, "Up")
screen.onkey(go_right, "Right")
screen.onkey(go_down, "Down")
screen.onkey(go_left, "Left")

# Let's go
reset()
turtle.done()

For info on using the super-handy `stamp()` function of Python Turtle Graphics, check out [my video on Youtube][3]

### Python Classic Snake Game Code Listing

The listing for our Snake Game is below. Depending on your level of experiece, you may be able to understand exactly how it works or maybe just some of it. That's all fine.

Whatever your level, you should experiment with the code, play with it. For example you could change some colours, or the speed of the snake, or the controls etc.

For more experienced programmers, why not improve upon the basic idea by adding scoring and other features?

    """ A simple snake game using Turtle Graphics. """
    import turtle
    import random
    
    WIDTH = 500
    HEIGHT = 500
    FOOD_SIZE = 10
    DELAY = 100  # milliseconds
    
    offsets = {
        "up": (0, 20),
        "down": (0, -20),
        "left": (-20, 0),
        "right": (20, 0)
    }
    
    def reset():
        global snake, snake_direction, food_pos, pen
        snake = [[0, 0], [0, 20], [0, 40], [0, 50], [0, 60]]
        snake_direction = "up"
        food_pos = get_random_food_pos()
        food.goto(food_pos)
        # screen.update() Only needed if we are fussed about drawing food before next call to `draw_snake()`.
        move_snake()
    
    def move_snake():
        global snake_direction
    
        #  Next position for head of snake.
        new_head = snake[-1].copy()
        new_head[0] = snake[-1][0] + offsets[snake_direction][0]
        new_head[1] = snake[-1][1] + offsets[snake_direction][1]
    
        # Check self-collision
        if new_head in snake[:-1]:  # Or collision with walls?
            reset()
        else:
            # No self-collision so we can continue moving the snake.
            snake.append(new_head)
    
            # Check food collision
            if not food_collision():
                snake.pop(0)  # Keep the snake the same length unless fed.
    
            #  Allow screen wrapping
            if snake[-1][0] > WIDTH / 2:
                snake[-1][0] -= WIDTH
            elif snake[-1][0] < - WIDTH / 2:
                snake[-1][0] += WIDTH
            elif snake[-1][1] > HEIGHT / 2:
                snake[-1][1] -= HEIGHT
            elif snake[-1][1] < -HEIGHT / 2:
                snake[-1][1] += HEIGHT
    
            # Clear previous snake stamps
            pen.clearstamps()
    
            # Draw snake
            for segment in snake:
                pen.goto(segment[0], segment[1])
                pen.stamp()
    
            # Refresh screen
            screen.update()
    
            # Rinse and repeat
            turtle.ontimer(move_snake, DELAY)
    
    def food_collision():
        global food_pos
        if get_distance(snake[-1], food_pos) < 20:
            food_pos = get_random_food_pos()
            food.goto(food_pos)
            return True
        return False
    
    def get_random_food_pos():
        x = random.randint(- WIDTH / 2 + FOOD_SIZE, WIDTH / 2 - FOOD_SIZE)
        y = random.randint(- HEIGHT / 2 + FOOD_SIZE, HEIGHT / 2 - FOOD_SIZE)
        return (x, y)
    
    def get_distance(pos1, pos2):
        x1, y1 = pos1
        x2, y2 = pos2
        distance = ((y2 - y1) ** 2 + (x2 - x1) ** 2) ** 0.5
        return distance
    
    def go_up():
        global snake_direction
        if snake_direction != "down":
            snake_direction = "up"
    
    def go_right():
        global snake_direction
        if snake_direction != "left":
            snake_direction = "right"
    
    def go_down():
        global snake_direction
        if snake_direction != "up":
            snake_direction = "down"
    
    def go_left():
        global snake_direction
        if snake_direction != "right":
            snake_direction = "left"
    
    # Screen
    screen = turtle.Screen()
    screen.setup(WIDTH, HEIGHT)
    screen.title("Snake")
    screen.bgcolor("green")
    screen.setup(500, 500)
    screen.tracer(0)
    
    # Pen
    pen = turtle.Turtle("square")
    pen.penup()
    
    # Food
    food = turtle.Turtle()
    food.shape("circle")
    food.color("red")
    food.shapesize(FOOD_SIZE / 20)  # Default size of turtle "square" shape is 20.
    food.penup()
    
    # Event handlers
    screen.listen()
    screen.onkey(go_up, "Up")
    screen.onkey(go_right, "Right")
    screen.onkey(go_down, "Down")
    screen.onkey(go_left, "Left")
    
    # Let's go
    reset()
    turtle.done()
    
  *This article is based on a [post][5] on the [Compucademy blog][4]*
  
  Happy coding!

*Robin Andrews*

 [1]: http://compucademy.net/python-turtle-graphics-demos/
 [2]: https://repl.it/@Compucademy/Snake-Game
 [3]: https://www.youtube.com/watch?v=j9Nu08Jtrmw
 [5]: https://compucademy.net/classic-snake-game-with-python-turtle-graphics/
 [4]: https://compucademy.net/blog/
Discover and read more posts from Robin Andrews
get started