Coding a Snake Game with Python's Turtle Module

Recreating a Classic


image.png

HiveDivider.png

In this article you'll find:

  • Introduction
  • The Task at Hand
  • Setting the Screen
  • Creating the Snake and the Food
  • Making the Snake Move
  • Making the food change places
  • Making the Snake's Tail Grow
  • The Game Overs
  • Adding the Scoreboard

Link to the .py file here

HiveDivider.png

The Snake Game is one of those games that is considered a pioneer of the videogame industry, giving joy to kids and adults alike for many generations. Since It was introduced to Blockade Arcades in 1976, this seemingly easy experience has been a phenomenon that gained Its most traction when Nokia Phones included It during the late 90s.

This and the fact that being able to make a videogame is really cool, are enough reasons for making this article, where I'll explain how you can create the Snake Game using Python and Its graphic module Turtle.

Now, Let's create It!

HiveDivider.png

The Task at Hand


Snakegame.jpg

Shoutout to T3

Since everyone has played the Snake Game at least once in their lifetime, this will be an easy task. However, we'll go over the rules again:

The game consists of a shape or "snake" which has to eat food placed on the screen. Once The snake eats It, Its tail gets larger and the food reappears in another spot. However, all this gets harder and harder because:

  • If the snake's head clashes with Its tail, the game's over.
  • If the snake's head clashes with the borders of the screen, GAME OVER.

And the biggest limitation: We can't stop the snake's movement, only redirect It.

Once we know this, we're ready to work.

HiveDivider.png

Setting the screen


image.png

To have our Snake Game running we have to create a screen of turtle. To do this, we create a Python file that we'll name snakegame.py. Then we open It on the editor and we begin coding.

As a first step, we should import the Turtle module. For this, we'll use:

import turtle

Then we'll have to create a window and set Its dimensions, Its title and Its background color. This we'll do with

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

First, we create an instance of the Screen Subclass, of which we'll define title, the title of the Window, with bgcolor as the background color, and using setup we'll define the width and height of this window, using tracer for smoother animations.

But we still have a long to go. Before moving on to create our snake and food, we should remember that all games work using a while loop with the true parameter. Inside this loop we'll use wn.update() to update the screen whenever an action happens.

while True:
    wn.update()

Note: If the window closes immediately when you run your program, you should use turtle.Screen().exitonclick(), this is to force the screen to close whenever you click the exit button.

Upon executing our program, we should see:


image.png

Now, we can create the main character of our game.

HiveDivider.png

Creating the Snake and the Food


image.png

To make our Snake "real", we'll create an object (instance of the Turtle class), and we'll define Its properties:

head = turtle.Turtle()
head.speed(0)
head.shape("square")
head.color("white")
head.penup()
head.goto(0,0)
head.direction = "stop"
  • With speed, we'll define how much It takes the snake to appear on the screen (instantly).
  • Shape defines the shape of the snake and color, well, the color.
  • Since turtle is a graphic module used to teach kids about the power of python, It uses a virtual pen to draw the shapes. With penup, we won't see that pen whenever we move.
  • with goto, we define the initial position of the snake.
  • Direction shows us the direction towards which the square aims, in this case none, since It's stopped.

As for the food, we'll do the same. The only difference is that since the food won't be a moving object, we won't assign It a direction.

apple = turtle.Turtle()
apple.speed(0)
apple.shape("circle")
apple.color("red")
apple.penup()
apple.goto(0,30)

We run the program and we'll see that there they are.

HiveDivider.png

Making the Snake Move


image.png

If we want to make our snake move whenever we press the arrows of our keyboards, we'll have to change the .direction attribute of our snake object. To do this we'll use the onkeypress turtle function. before this, we'll also focus on the screen with listen. Then we'll write:

wn.listen()
wn.onkeypress(go_up,"Up")
wn.onkeypress(go_down,"Down")
wn.onkeypress(go_left,"Left")
wn.onkeypress(go_right,"Right")

The parameters of the onkeypress are a function without parameters that will execute Itself whenever we type the specified keys (In this case Up, Down, Left and Right). We still have to define these functions, so we'll define what they will do. This with:

def go_up():
    head.direction = "up"
def go_down():
    head.direction = "down"
def go_left():
    head.direction = "left"
def go_right():
    head.direction = "right"

The only thing that these functions will do is change the direction of the snake's aim. However, this still isn't enough to make it move. To do this, we have to know the x and y coordinates of the square and shift them.

One thing we know after these functions is that the direction changes, so we'll use that:

def mov():
    if head.direction == "up":
        y = head.ycor()
        head.sety(y + 20)
    elif head.direction == "down":
        y = head.ycor()
        head.sety(y - 20)
    elif head.direction == "right":
        x = head.xcor()
        head.setx(x + 20)
    elif head.direction == "left":
        x = head.xcor()
        head.setx(x - 20)

We see that if the direction is "up", we'll take the y position or coordinate of the snake with ycor and then we'll set Its new direction with sety, which is Its actual position + 20. If It's down, we'll subtract It and If the movement is horizontal (Left or Right) we'll add or subtract using xcor to get the actual position of the snake and setx to set Its new movement.

Then, to make the snake move, we'll put the function inside the while loop. Then, every time the screen updates, the snake will be moving.

mov()

HiveDivider.png

Making the food change places


image.png

Now that we have our snake moving, we'll still need to make something happen whenever It goes over the red circle we designed as the food. Otherwise, there'd be no fun.

To do this, we'll have to ask ourselves this question:

How do we know when the snake clashes with the apple?

Knowing that our screen's dimensions are 600x600 pixels and that our snake moves at a rate of 20 pixels per step, we know that whenever the distance between the snake and the apple is lesser than 20 pixels, they clash.

But how do we measure this distance?

With the function .distance. This instruction measures the relative distance between two objects. In this case, we'll use It as a conditional, where we'll take random x and y coordinates and we'll use .goto to determine the new position of the food.

However, we still don't know how to generate random values. To do this, we use a Python built-in library, random. First, we import It:

import random

Then, we use randint, one of Its functions, which creates a random integer number between two parameters that we assign. Once we know this, we begin to code inside the While loop.

if head.distance(apple) < 20:
        x = random.randint(-280,280)
        y = random.randint(-280,280)
        apple.goto(x,y)

Then we save, run, and whenever our snake goes over the food, It will respawn in a different spot on the screen.

HiveDivider.png

Making The Snake's Tail Grow


image.png

We have a decent amount of the Game built, but we are forgetting what increases the difficulty of the game: making the snake's tail grow.

To do this we have to create iterable objects that can grow infinitely, and a place to store them, and what better way to save them than a list? A list that initially will be empty:

segments = []

We don't want our snake's tail to grow unless It eats the food, so we'll go back to the conditional we made in the previous section and we create a new object similar to the snake's head whenever this happens.

if head.distance(apple) < 20:
        x = random.randint(-280,280)
        y = random.randint(-280,280)
        apple.goto(x,y)

        new_segment = turtle.Turtle()
        new_segment.speed(0)
        new_segment.color("grey")
        new_segment.shape("square")
        new_segment.penup()
        segments.append(new_segment)

Where each segment is created as an instance of the Turtle class, in an identical fashion to the snake's head. But what does the list that we created have to do with this? It is with this list that we'll save each segment that's generated, hence why we use append, a function that adds Its parameters to the list It's attached to.

But we still haven't finished. If we leave it like this, whenever the snake eats the food, a grey square will appear in the center of the screen. This happens because all the segments generated will be stacked one upon the other. And since we haven't assigned any movement to them, they will stay there.

To make each segment follow the previous one, we'll take as a first step the length of the list with all the segments. This we do with the len function, which will return us the number of elements as an integer.

totalSeg = len(segments)

We'll use this in conjunction with a for cycle that will go over all the list elements, from the last segment to the first. In each one, we'll take the x and y coordinates of the list's previous member and assign these to our present segment with goto.

for segment in range(totalSeg-1,0,-1):
        x = segments[segment-1].xcor()
        y = segments[segment-1].ycor()
        segments[segment].goto(x,y)

Where we use the range function to count from one specific number to another, using the third parameter to say that the count will be backward. We also take the length of the list - 1 as the last number since in coding, we begin counting at 0 and not at 1, which will mean that, for example, If we have a list of 5 elements, the 4th element will be the last.

With xcor, we'll take the x coordinate of the element at the index next to the present one and with ycor, we'll take the y coordinate.

Then, we'll assign the new position with goto. But we still have to consider something: in the list we take all segments, except for the most important one: The Snake's head.

If we don't do something about It, the for cycle will go to the first element of the list and see that It's in the same position, the reason why It won't move. To fix this, we first ask If there's at least one element in the list. Then, If this is true, we'll take the x and y position of the snake's head and we'll assign with a goto the new coordinate of this element, and all other segments will follow.

if totalSeg > 0:
        x = head.xcor()
        y = head.ycor()
        segments[0].goto(x,y)

Now, If we run the program again, we'll see that whenever the snake eats the food, Its tail will grow, and It'll effectively follow Its head.

HiveDivider.png

The Game Overs

image.png

image.png



As we knew previously, the Game ends If:

  • The snake's head hits the border of the screen.
  • The snake's head clashes with Its tail.

To match the first condition, we need to determine when this happens. To do this, we have to consider that the screen is a cartesian plane, that's divided into negative and positive on both axis. In this case, since the screen is 600px on both sides, this means that the positive side of each axis can reach a maximum value of 300 and the negative, -300.

Knowing that the snake moves 20px per step, we know that whenever It surpasses an x or y coordinate of 280 or -280, It'll hit the border. Using xcor, ycor and a conditional sentence, we can achieve this.

if head.xcor() > 280 or head.xcor() < -280 or head.ycor() > 280 or head.ycor() < -280:
       time.sleep(1)
       head.goto(0,0)
       head.direction = "stop"

Where we use time, a built-in library of Python that we'll import the same way as random:

import time

With sleep, we'll design a delay of 1 sec signaling that the game ended. With goto we'll set the head position at the center of the screen again and with direction, we stop Its movement.

But what will happen with the segments? They'll still be following the head, meaning that they will go to the center with It. Unless we do something about It by sending them outside the screen. Thing we'll achieve with:

for segment in segments:
           segment.goto(2000,2000)

       segments.clear()

Where we send each segment outside the limits.

Now, for the second condition: When the Snake's head hits Its tail, we have to go over each segment of the list and ask If the head is at a distance lesser than 20 px from It. For this, we'll use a For Cycle and If statements.

for segment in segments:
        if segment.distance(head) < 20:
           time.sleep(1)
           head.goto(0,0)
           
           head.direction = "stop"

           for segment in segments:
               segment.goto(2000,2000)

           segments.clear()

As you can see, what happens is identical to hitting the screen.

HiveDivider.png

Adding the Scoreboard

image.png

image.png



.

The hardest part is over, now we just have to create the text that will signal the scoreboard.

To do this, we generate another object, this time called text, with the same attributes as our snake and the food. The only difference will be the use of .hideturtle, a method that hides a cursor that appears during the drawing of an object. This, and .write, with the text, the align and the font as parameters, will be the only instructions that differ from the other objects.

text = turtle.Turtle()
text.speed(0)
text.color("white")
text.penup()
text.hideturtle()
text.goto(0,260)
text.write("Score:0     High Score:0", align="center", font=("Courier", 24))

We also see that the position of the y-axis in goto is 260. This is to put the scoreboard in the upper portion of the screen and not interfere with the rest of the game.

Then, upon running, we'll see our scoreboard. However, It's still not dynamic, since It won't go up whenever the snake eats the food. To fix this, we have two create two variables: One representing the actual score and the other for the high score.

score = 0
high_score = 0

Then, we have to increase these variables when the snake goes over the food. This means that we'll have to head to the conditional that measures the distance between the snake and the apple. Then, at the end of append, we'll increase the score by 10

Then, to ensure that score becomes the high score whenever It goes over It, we create another conditional. Then, we clear the text (Because otherwise, the letters will go one over the other), and finally, we change the .write function to the value of score and high_score using format or fstrings.

if head.distance(apple) < 20:
        x = random.randint(-280,280)
        y = random.randint(-280,280)
        apple.goto(x,y)

        new_segment = turtle.Turtle()
        new_segment.speed(0)
        new_segment.color("grey")
        new_segment.shape("square")
        new_segment.penup()
        segments.append(new_segment)
        score += 10

        if score > high_score:
            high_score = score

        text.clear()
        text.write("Score:{}     High Score:{}".format(score,high_score), align="center", font=("Courier", 24))

Now, we just have to clear the score whenever a game over happens. This we do easily by going to our two events: When the Snake eats Its tail and when It hits the screen.

After we clear the list where the segments are, we'll clear the text again and write 0 in the score. However, we'll keep the High Score, applying format with only one parameter: The high score, and keeping Its bracket. For the first game over event:

if head.xcor() > 280 or head.xcor() < -280 or head.ycor() > 280 or head.ycor() < -280:
       time.sleep(1)
       head.goto(0,0)
       head.direction = "stop"

       for segment in segments:
           segment.goto(2000,2000)

       segments.clear()
       score = 0


       text.clear()
       text.write("Score:0     High Score:{}".format(high_score), align="center", font=("Courier", 24))

And for the second:

for segment in segments:
        if segment.distance(head) < 20:
           time.sleep(1)
           head.goto(0,0)
           
           head.direction = "stop"

           for segment in segments:
               segment.goto(2000,2000)

           segments.clear()
           score = 0

           text.clear()
           text.write("Score:0     High Score:{}".format(high_score), align="center", font=("Courier", 24))

With this, we can run our game and now we'll have a fully functional snake. Beware of playing too much! We still have projects to create.

HiveDivider.png

I hope this article could have been of use to you, providing you with an entertaining and simple way to recreate one of our favorite classics: The Snake Game. I encourage you to keep testing turtle's potential, improving the initial code and creating your own projects to increase your skills.

Thank you for your support and good luck!

HiveDivider.png

@jesalmofficial.png



0
0
0.000
3 comments
avatar

Congratulations @jesalmofficial! You have completed the following achievement on the Hive blockchain And have been rewarded with New badge(s)

You received more than 1750 upvotes.
Your next target is to reach 2000 upvotes.

You can view your badges on your board and compare yourself to others in the Ranking
If you no longer want to receive notifications, reply to this comment with the word STOP

Check out our last posts:

LEO Power Up Day - July 15, 2023
0
0
0.000