Objective

In this final unit, you will apply all the concepts learned throughout the course to create the classic game "Snake." By the end of this unit, you'll have a complete, functional version of the Snake game, showcasing your understanding of game design, development, testing, and debugging.

Game Design and Planning

Before starting the development of the Snake game, it's essential to plan the game's design and structure. Here's an outline of the Snake game:

Game Elements

  • Snake: A series of connected segments that move in unison. The player controls the head of the snake, and the body follows.
  • Food: Items that the snake must collect to grow longer.
  • Walls/Obstacles: Optional barriers that the snake must avoid.
  • Score: A counter that keeps track of the number of food items collected.

Game Rules

  • The snake moves continuously in one of four directions: up, down, left, or right.
  • The player controls the direction of the snake's head.
  • When the snake eats food, it grows longer, and the player's score increases.
  • If the snake runs into the wall or itself, the game is over.

Game Components

  • Game Window: The main window where the game is displayed.
  • Snake Class: A class to manage the snake's segments, movement, and growth.
  • Food Class: A class to manage the food's position and appearance.
  • Game State: Variables to manage the game's state, such as the score and game over condition.

Game Development

Now, let's dive into the development of the Snake game. We'll break down the process into several key steps:

1. Set Up the Game Window

Create a window with Turtle and set the background color, title, and size.

2. Create the Snake Class

Define a class to manage the snake's segments, movement, and growth. The class should include methods to:

  • Initialize the snake with a given length.
  • Move the snake in the current direction.
  • Change the snake's direction based on user input.
  • Check for collisions with the walls or itself.
  • Grow the snake when it eats food.

3. Create the Food Class

Define a class to manage the food's position and appearance. The class should include methods to:

  • Initialize the food at a random position.
  • Check for collisions with the snake.
  • Move the food to a new random position when eaten.

4. Handle User Input

Allow the player to change the snake's direction using the arrow keys.

5. Implement Collision Detection

Check for collisions between the snake and the food, walls, or itself.

6. Manage Game State

Keep track of the score and update it when the snake eats food. Implement a game over condition if the snake collides with the walls or itself.

7. Build the Game Loop

Create a loop that continuously updates the game, checks for collisions, and redraws the screen.

8. Enhance Visuals

Add visual effects, such as changing colors or shapes, to make the game more engaging.

9. Add Game Over Logic

Implement a game over screen with the final score and an option to restart the game.

Testing and Debugging the Game

As you develop the game, it's essential to test each component and the overall functionality. Here are some tips for testing and debugging:

  • Test Individual Components: Test each class and function separately to ensure they work as expected.
  • Use Print Statements: Print statements can help you understand what's happening in your code and identify issues.
  • Handle Errors Gracefully: Use try-except blocks to handle potential errors and provide helpful error messages.
  • Play the Game: Play the game multiple times to identify any bugs or areas for improvement.

Project: Create the Snake Game

Now it's time to put everything together and create the Snake game.

The code below includes the basic functionality of the game, where the player controls the snake to collect food, growing longer with each piece collected. If the snake runs into the wall or itself, the game ends.

import turtle
import time
import random

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

    def create_snake(self):
        self.add_segment((0, 0), "green")
        for i in range(2):
            self.add_segment((-(i+1) * 20, 0))

    def add_segment(self, position, color="grey"):
        segment = turtle.Turtle("square")
        segment.color(color)
        segment.penup()
        segment.goto(position)
        self.segments.append(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):
            x = self.segments[seg_num - 1].xcor()
            y = self.segments[seg_num - 1].ycor()
            self.segments[seg_num].goto(x, y)
        self.segments[0].forward(20)

    def up(self):
        if self.head.heading() != 270:
            self.head.setheading(90)

    def down(self):
        if self.head.heading() != 90:
            self.head.setheading(270)

    def left(self):
        if self.head.heading() != 0:
            self.head.setheading(180)

    def right(self):
        if self.head.heading() != 180:
            self.head.setheading(0)

# Food class definition
# NOTE: The Food class directly extends turtle.Turtle
class Food(turtle.Turtle):
    def __init__(self):
        super().__init__()
        self.shape("circle")
        self.penup()
        self.shapesize(stretch_len=0.5, stretch_wid=0.5)
        self.color("blue")
        self.speed("fastest")
        self.refresh()

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

# Set up the game window
screen = turtle.Screen()
screen.bgcolor("black")
screen.title("Snake Game")
screen.setup(width=600, height=600)

# Set the tracer to update the screen every 1/60th of a second
turtle.tracer(n=1, delay=17)  # 1000ms / 60fps = ~17ms

# Create the snake and food objects
snake = Snake()
food = Food()

# Use this variable to control how fast the snake is moving.
# The shorter the delay, the faster the snake moves.
delay = 0.3

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

# Score
score = 0
score_display = turtle.Turtle()
score_display.color("white")
score_display.penup()
score_display.goto(0, 260)
score_display.hideturtle()
score_display.write(f"Score: {score}", align="center", font=("Arial", 24, "normal"))

# Define a function to update the game state
def update_game():
    # Update the game state here (e.g., move the player)
    global score
    global delay

    snake.move()

    # Check for collision with food
    if snake.head.distance(food) < 15:
        food.refresh()
        snake.extend()
        score += 10
        score_display.clear()
        score_display.write(f"Score: {score}", align="center", font=("Arial", 24, "normal"))
         # Shorten the delay
        if delay >= 0.05:
            delay -= 0.05

    # Check for collision with wall
    if snake.head.xcor() > 280 or snake.head.xcor() < -280 or snake.head.ycor() > 280 or snake.head.ycor() < -280:
        running = False
        score_display.goto(0, 0)
        score_display.color("red")
        score_display.write("Game Over", align="center", font=("Arial", 36, "bold"))

    # Check for collision with tail
    for segment in snake.segments[1:]:
        if snake.head.distance(segment) < 10:
            running = False
            score_display.goto(0, 0)
            score_display.color("red")
            score_display.write("Game Over", align="center", font=("Arial", 36, "bold"))

# Game loop
running = True
while running:
    update_game()
    screen.update()
    if delay > 0:
        time.sleep(delay)

# Wait until the window is closed
screen.mainloop()

Use the arrow keys to control the snake, collect the blue food items to grow longer, and avoid running into the walls or yourself. The game displays the current score at the top of the window and shows a "Game Over" message when the game ends.

Feel free to add your creativity to the game by introducing new features, levels, or visual effects. To make the code easier to develop, can you refactor it into several files? For example, try moving the Snake and Food classes into their own files. What other improvements would make the code easier to manage?

Congratulations on reaching the end of this course! You've gained valuable skills in programming, graphics, and game development that you'll be able to apply to future projects.

Happy coding!