Snake Game


Professional project
Python code solution and explanation
turtle library

Introduction

The Snake game is a classic video game where the player controls a snake that moves across a screen and grows by eating food. The objective of the game is to make the snake as long as possible without hitting the walls or running into its own tail. The controls are just arrow keys to control the direction of the snake, making it easy to learn but difficult to master. It has been popular since the 1970s and is still enjoyed by players today.

But today, we’re going to make the snake game in Python, for you to enjoy with your friends and family!

Step Breakdown

Let’s break this game down into smaller steps.

  1. Make game window
  2. Animate snake and movement
  3. Detect collision with food
  4. Detect collision with wall
  5. Detect collision with own tail
  6. Keep track of scoreboard

With this overview, we broadly know what to do and check each item while making our game. 

Solution

We’ll have 4 different files, main.py, snake.py, scoreboard.py, food.py. The last 3 files will have their own class and functions, while the main file will execute all the code.

main.py

from turtle import Screen
from snake import Snake
from food import Food
from scoreboard import Scoreboard
import time

Now we can make a screen. You can choose your own customization. Make sure to turn screen.tracer() to 0, so that there’s no delay when the game runs.

screen = Screen()
screen.setup(width=600, height=600)
screen.bgcolor("black")
screen.title("Snake Game")
screen.tracer(0)

Set each class to an object. Right now we’re just going to code and name all the functions, and later we will make them.

snake = Snake()
food = Food()
scoreboard = Scoreboard()

When the arrow keys are pressed, execute its corresponding function.

screen.listen()
screen.onkey(snake.up, "Up")
screen.onkey(snake.down, "Down")
screen.onkey(snake.left, "Left")
screen.onkey(snake.right, "Right")

While game is on, update the screen, delay 0.1 seconds, and move the snake.

game_is_on = True
while game_is_on:
    screen.update()
    time.sleep(0.1)
    snake.move()

If the snake’s head is less than 15 pixels away from the food, then refresh the food, extend the snake, and increase the score. Note, the food is 10×10 pixels.

    # Detect collision with food
    if snake.head.distance(food) < 15:
        food.refresh()
        snake.extend()
        scoreboard.increase_score()

If the snake head’s x or y axis is out of bounds, then game is over, and print the game over message.

    # Detect collision with wall
    if snake.head.xcor() > 290 or snake.head.xcor() < -290 or snake.head.ycor() > 290 or snake.head.ycor() < -290:
        game_is_on = False
        scoreboard.game_over()

If the snake head’s distance from any segment is less than 10, the game is over, and print game over message.

    # Detect collision with tail
    for segment in snake.segments[1:]:
        if snake.head.distance(segment) < 10:
            game_is_on = False
            scoreboard.game_over()
screen.exitonclick()

snake.py

Let’s initialize the variables. The starting positions for for the 3 segments, will be saved in a list. The up, down, left, and right are the degrees you turn to face the direction.

from turtle import Turtle
STARTING_POSITIONS = [(0, 0), (-20, 0), (-40, 0)]
MOVE_DISTANCE = 20
UP = 90
DOWN = 270
LEFT = 180
RIGHT = 0

All the segments of the body will be saved into self.segments. The first segment in self.segments will be self.head, so it’s easier to refer to. self.create_snake() will call a different function.

class Snake:
    def __init__(self):
        self.segments = []
        self.create_snake()
        self.head = self.segments[0]

To create the first 3 segments of the snake, we can loop through the starting positions and refer to add_segment().

    def create_snake(self):
        for position in STARTING_POSITIONS:
            self.add_segment(position)

For add_segment(), we need to pass in a position. The function will create a new segment and will go to the position you defined. Then it will append this new_segment to self.segments.

    def add_segment(self, position):
        new_segment = Turtle("square")
        new_segment.color("turquoise")
        new_segment.penup()
        new_segment.goto(position)
        self.segments.append(new_segment)

For move(), this will be the applied logic. For seg_num in range(from the last segment, go to the second segment in decreasing order), new_x and new_y will equal the x and y axis of the segment in front of the current one. Then, the current segment will go to new_x and new_y. Once that’s over, we can move the head forward by 20 pixels.

    def move(self):
        for seg_num in range(len(self.segments) - 1, 0, -1):
            new_x = self.segments[seg_num - 1].xcor()
            new_y = self.segments[seg_num - 1].ycor()
            self.segments[seg_num].goto(new_x, new_y)
        self.head.forward(MOVE_DISTANCE)

For extend(), we will simply add a segment to the same position as the last segment.

    def extend(self):
        self.add_segment(self.segments[-1].position())

If someone presses the up, down, left, or right arrow key, snake.head will rotate to the arrow key pressed, using setheading(). But this will only work if snake.head isn’t facing the opposite direction of the way you want it to go. For example, we don’t want snake.head to go up if currently, it’s going down.

    def up(self):
        if self.head.heading() != DOWN:
            self.head.setheading(UP)

    def down(self):
        if self.head.heading() != UP:
            self.head.setheading(DOWN)

    def left(self):
        if self.head.heading() != RIGHT:
            self.head.setheading(LEFT)

    def right(self):
        if self.head.heading() != LEFT:
            self.head.setheading(RIGHT)

food.py

We will need the random function to refresh the food location.

from turtle import Turtle
import random

Our Food() class will inherit from the Turtle library. The food will be a small 10×10 pixel circle. We can then call on self.refresh().

class Food(Turtle):
    def __init__(self):
        super().__init__()
        self.shape("circle")
        self.penup()
        self.shapesize(stretch_len=0.5, stretch_wid=0.5)
        self.color("white")
        self.speed("fastest")
        self.refresh()

For refresh(), we will select a random x and y axis within the boundaries of the screen. Then, the food will go to these coordinates.

    def refresh(self):
        random_x = random.randint(-280, 280)
        random_y = random.randint(-280, 280)
        self.goto(random_x, random_y)

scoreboard.py

I’m going to save the font information to a variable, to make it easier to reference.

from turtle import Turtle
FONT = ("Comic Sans MS", 20, "italic")

Our Scoreboard() class will also inherit from the Turtle library. It will originally display the score. It will call the update_scoreboard() function. Remember to hide the turtle.

class Scoreboard(Turtle):
    def __init__(self):
        super().__init__()
        self.score = 0
        self.color("white")
        self.penup()
        self.goto(0, 260)
        self.hideturtle()
        self.update_scoreboard()

For update_scoreboard(), it will simply write the score.

    def update_scoreboard(self):
        self.write(f"Score: {self.score}", align="center", font=FONT)

For increase_score(), it will simply clear the current score, increase the score by 1, and update_scoreboard().

    def increase_score(self):
        self.score += 1
        self.clear()
        self.update_scoreboard()

For game_over(), it will write a game over message in the middle of the screen.

    def game_over(self):
        self.goto(0, 0)
        self.write("GAME OVER", align="center", font=FONT)

That sums up our game.

Gameplay
Final Code

To view full code, visit my GitHub repository:
https://github.com/Gursehaj-Singh/snake-game

To view my youtube channel, visit https://youtu.be/JAoJqFDf1yU

main.py

from turtle import Screen
from snake import Snake
from food import Food
from scoreboard import Scoreboard
import time

screen = Screen()
screen.setup(width=600, height=600)
screen.bgcolor("black")
screen.title("Snake Game")
screen.tracer(0)

snake = Snake()
food = Food()
scoreboard = Scoreboard()

screen.listen()
screen.onkey(snake.up, "Up")
screen.onkey(snake.down, "Down")
screen.onkey(snake.left, "Left")
screen.onkey(snake.right, "Right")

game_is_on = True
while game_is_on:
    screen.update()
    time.sleep(0.1)
    snake.move()

    # Detect collision with food
    if snake.head.distance(food) < 15:
        food.refresh()
        snake.extend()
        scoreboard.increase_score()

    # Detect collision with wall
    if snake.head.xcor() > 290 or snake.head.xcor() < -290 or snake.head.ycor() > 290 or snake.head.ycor() < -290:
        game_is_on = False
        scoreboard.game_over()

    # Detect collision with tail
    for segment in snake.segments[1:]:
        if snake.head.distance(segment) < 10:
            game_is_on = False
            scoreboard.game_over()


screen.exitonclick()

snake.py

from turtle import Turtle
STARTING_POSITIONS = [(0, 0), (-20, 0), (-40, 0)]
MOVE_DISTANCE = 20
UP = 90
DOWN = 270
LEFT = 180
RIGHT = 0


class Snake:
    def __init__(self):
        self.segments = []
        self.create_snake()
        self.head = self.segments[0]

    def create_snake(self):
        for position in STARTING_POSITIONS:
            self.add_segment(position)

    def add_segment(self, position):
        new_segment = Turtle("square")
        new_segment.color("turquoise")
        new_segment.penup()
        new_segment.goto(position)
        self.segments.append(new_segment)

    def extend(self):
        self.add_segment(self.segments[-1].position())

    def move(self):
        for seg_num in range(len(self.segments) - 1, 0, -1):
            new_x = self.segments[seg_num - 1].xcor()
            new_y = self.segments[seg_num - 1].ycor()
            self.segments[seg_num].goto(new_x, new_y)
        self.head.forward(MOVE_DISTANCE)

    def up(self):
        if self.head.heading() != DOWN:
            self.head.setheading(UP)

    def down(self):
        if self.head.heading() != UP:
            self.head.setheading(DOWN)

    def left(self):
        if self.head.heading() != RIGHT:
            self.head.setheading(LEFT)

    def right(self):
        if self.head.heading() != LEFT:
            self.head.setheading(RIGHT)

food.py

from turtle import Turtle
import random


class Food(Turtle):
    def __init__(self):
        super().__init__()
        self.shape("circle")
        self.penup()
        self.shapesize(stretch_len=0.5, stretch_wid=0.5)
        self.color("white")
        self.speed("fastest")
        self.refresh()

    def refresh(self):
        random_x = random.randint(-280, 280)
        random_y = random.randint(-280, 280)
        self.goto(random_x, random_y)

scoreboard.py

from turtle import Turtle
FONT = ("Comic Sans MS", 20, "italic")


class Scoreboard(Turtle):
    def __init__(self):
        super().__init__()
        self.score = 0
        self.color("white")
        self.penup()
        self.goto(0, 260)
        self.hideturtle()
        self.update_scoreboard()

    def update_scoreboard(self):
        self.write(f"Score: {self.score}", align="center", font=FONT)

    def increase_score(self):
        self.score += 1
        self.clear()
        self.update_scoreboard()

    def game_over(self):
        self.goto(0, 0)
        self.write("GAME OVER", align="center", font=FONT)
Further Steps

Now that we’ve built the main functionality of snake, you may want to include other things to enhance the gameplay. This could include:

  • Power-ups such as increased speed or invincibility.
  • Obstacles, such as rocks or barriers
  • Levels with increasing difficulty
  • Multiplayer mode so 2 people can play on the same screen

Hope you enjoyed.

,