100 Days Of Code Challenge – Level Intermediate #3.

Posted in May 15, 2022 by

Categories: Challenges Programming Python

Tags: , , ,

Reading Time: 5 minutes

This is a continuation of my series of blog articles about programming in Python and participating in the 100 Days of Code Udemy course made by Angela Yu.

If you would like to read the previous post here is a link 👇:

100 Days of Code Challenge – Day 20.

On this day I started a quite ambitious project of writing a snake game in the Turtle module. Writing a fully functional game is divided into 2 days – day 20 and day 21.

On this day I was learning about creating a window, and creating an initial snake body made from Turtle objects. Another task was to make that snake’s body move in a proper way. I think this is the most difficult concept in building a snake game. To understand how snake body moves, but when you will get this whole project seems quite easy to build.

Another challenge on this day was to refactor the whole code of the snake project on OOP, so there are two files: snake_game.py and snake.py.

The last problem was to control the snake by using keyboard arrows. This is done by event listeners provided by Turtle 🐢.

The whole code of this day is below 👇:

snake_game.py:

from turtle import Screen
import time
from snake import Snake

screen = Screen()
screen.tracer(0)

screen.setup(width=600, height=500)
screen.bgcolor("black")
screen.title("Snake Game by Mateusz Hyla")

snake = Snake()

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

game_is_on = True

while game_is_on:
    screen.update()
    time.sleep(0.1)
    snake.move_snake()

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.init_snake()
        self.head = self.segments[0]

    def init_snake(self):
        for position in STARTING_POSITIONS:
            new_segment = Turtle("square")
            new_segment.color("white")
            new_segment.penup()
            new_segment.goto(position)
            self.segments.append(new_segment)

    def move_snake(self):
        for seg_num in range(len(self.segments) - 1, 0, -1):
            x_cor = self.segments[seg_num - 1].xcor()
            y_cor = self.segments[seg_num - 1].ycor()
            self.segments[seg_num].goto(x_cor, y_cor)

        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 right(self):
        if self.head.heading() != LEFT:
            self.head.setheading(RIGHT)

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

And that is all for day 20 🙂.

100 Days of Code Challenge – Day 21.

This day is a continuation of building a console Snake Game.

I learned that I can extend my classes by a Turtle native class and that my class will inherit all Turtle class properties and methods. I learned about inheritance 😊.

In our game, we were creating a Food class and implementing it. Additionally very important was to detect collisions that snake makes with food, walls, and its tail.

I implemented a scoreboard that prints the current score and prints “Game Over” when the snake collides with a wall or its tail.

I also implemented a snake extending body when it eats the food.

I think the most important part to understand in building a snake game is the movement of the snake. If you will understand that the part of the snake body moves from the last one to the first one it is much easier to build that game.

Angela gave me the opportunity to code some parts of this app on my own but some of it was given to me by this instructor.

Additionally, I extended this game by making 3 different types of food and making the snake body more colorful.

A very important concept in this game was also list slicing. Python provides a very convenient way of slicing lists, like for example reversing it 👇:

some_list[::-1]

You can find the code of this game below 👇:

snake_game.py

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

screen = Screen()
screen.tracer(0)

screen.setup(width=600, height=600)
screen.bgcolor("black")
screen.title("Snake Game by Mateusz Hyla")

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

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

game_is_on = True


while game_is_on:
    screen.update()
    time.sleep(0.1)
    snake.move_snake()

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

    # Detecting collision with walls.
    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.segments_num = 0
        self.init_snake()
        self.head = self.segments[0]
        self.head.color("green")

    def init_snake(self):
        for position in STARTING_POSITIONS:
            if self.segments_num % 2 == 0:
                self.add_segment(position, "chartreuse")
            else:
                self.add_segment(position, "spring green")

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

    def extend(self):
        if self.segments_num % 2 == 0:
            self.add_segment(self.segments[-1].position(), "chartreuse")
        else:
            self.add_segment(self.segments[-1].position(), "spring green")

    def move_snake(self):
        for seg_num in range(len(self.segments) - 1, 0, -1):
            x_cor = self.segments[seg_num - 1].xcor()
            y_cor = self.segments[seg_num - 1].ycor()
            self.segments[seg_num].goto(x_cor, y_cor)

        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 right(self):
        if self.head.heading() != LEFT:
            self.head.setheading(RIGHT)

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

food.py

from turtle import Turtle
import random

FOOD_SHAPES = ["circle", "triangle", "square"]
FOOD_COLORS = ["red", "blue", "green"]


class Food(Turtle):

    def __init__(self):
        super().__init__()
        self.shape(random.choice(FOOD_SHAPES))
        self.color(random.choice(FOOD_COLORS))
        self.penup()
        self.shapesize(stretch_len=0.5, stretch_wid=0.5)
        self.speed("fastest")
        self.refresh()

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

        self.refresh_food_shape_color()

        # If food position is on a scoreboard we roll food position again.
        if 5 > random_x > -5 and 265 > random_y > 255:
            random_x = random.randint(-270, 270)
            random_y = random.randint(-270, 270)
        self.goto(random_x, random_y)

    def refresh_food_shape_color(self):
        self.shape(random.choice(FOOD_SHAPES))
        self.color(random.choice(FOOD_COLORS))

scoreboard.py

from turtle import Turtle

ALIGNMENT = "center"
FONT = ("Courier", 15, "bold")


class ScoreBoard(Turtle):

    def __init__(self):
        super().__init__()
        self.setx(0)
        self.goto(0, 260)
        self.score = 0
        self.color("white")
        self.hideturtle()
        self.update_scoreboard()

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

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

    def game_over(self):
        self.goto(0, 0)
        self.write("Game Over", align=ALIGNMENT, font=FONT)

And that is all for day 20 🙂.

Summary

During these two days, I was able to build a snake game with a help of the instructor 👩‍🏫.

I think building a snake game is very fun but to build it you have to understand the concept of snake movement and how to implement it in your code.

What I can’t build I don’t fully understand.

Richard Feynman

If you would like to join me in the 100 Days of Code Udemy Course by Angela Yu you can find the link below 👇:

If you have built your own snake game you can share the link to your GitHub account in a comment section.

If you like this article you can share it on social media and give a thumb up 👍.

Have a great day.

Bye 👋.


One thought on “100 Days Of Code Challenge – Level Intermediate #3.”

Leave a Reply

Your email address will not be published.

error: Content is protected !!