Snake Game using Turtle in Python

Introduction

The Snake Game is a classic arcade game where the player manoeuvres a line (the snake) that grows in length with each piece of food eaten. The player loses if the snake runs into the screen border or itself. This project implements the Snake Game using Python’s turtle graphics library, which provides a simple and flexible way to draw shapes and create animations.

Prerequisites

  • Basic understanding of Python programming.
  • Familiarity with loops, conditional statements, and user input.
  • An understanding of Python libraries such as turtle and random.

Learning Objectives

  • Understand the use of the turtle module for creating graphics in Python.
  • Learn how to implement a simple game loop.
  • Practice working with lists and tuples to manage game objects.
  • Understand collision detection and game state management.

Installation Guide

To run this project on your machine, ensure you have Python installed. Follow these steps:

  1. Download and Install Python: Visit the official Python website to download and install Python if you haven’t already.
  2. Set Up a Python Environment: Use a code editor like Visual Studio Code or an IDE like PyCharm to write and run your code.
  3. Clone or Copy the Code: Copy the project code provided below into a new Python file in your development environment.

Project Steps

  1. Initialize the Game:
    • Set up the game window using turtle.
    • Initialize the snake’s starting position and direction.
    • Create the food object for the snake to “eat.”
  2. Game Loop:
    • Move the snake based on the current direction.
    • Check for collisions with the food and snake body.
    • Update the game state and redraw the screen.
  3. Handle User Input:
    • Use keyboard listeners to change the snake’s direction.
  4. Restart Game:
    • Reset the game when the snake collides with itself.

Code

				
					import turtle
import random

w = 500
h = 500
fs = 10
d = 100  # milliseconds

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

def r():
    global saap, kata, khanaT, pen
    saap = [[0, 0], [0, 20], [0, 40], [0, 60], [0, 80]]
    kata = "up"
    khanaT = nun()
    food.goto(khanaT)
    hall()

def hall():
    global kata

    new_head = saap[-1].copy()
    new_head[0] = saap[-1][0] + offsets[kata][0]
    new_head[1] = saap[-1][1] + offsets[kata][1]

    if new_head in saap[:-1]:  
        r()
    else:
        saap.append(new_head)

        if not khana():
            saap.pop(0)  

        if saap[-1][0] > w / 2:
            saap[-1][0] -= w
        elif saap[-1][0] < - w / 2:
            saap[-1][0] += w
        elif saap[-1][1] > h / 2:
            saap[-1][1] -= h
        elif saap[-1][1] < -h / 2:
            saap[-1][1] += h

        pen.clearstamps()
 #clears all the stamps

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

        screen.update()
 #updates the turtle.screen screen

        turtle.ontimer(hall, d)

def khana():
    global khanaT
    if dist(saap[-1], khanaT) < 20:
        khanaT = nun()
        food.goto(khanaT)
        return True
    return False

def nun():
    x = random.randint(- w / 2 + fs, w / 2 - fs)
    y = random.randint(- h / 2 + fs, h / 2 - fs)
    return (x, y)

def dist(poos1, poos2):
    x1, y1 = poos1
    x2, y2 = poos2
    distance = ((y2 - y1) * 2 + (x2 - x1) * 2) ** 0.5
    return distance

def mathi():
    global kata
    if kata != "down":
        kata = "up"

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

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

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

screen = turtle.Screen()
screen.setup(w, h)
screen.title("saap")
screen.bgcolor("green")
screen.setup(500, 500)
screen.tracer(0)

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

food = turtle.Turtle()
food.shape("circle")
food.color("white")
food.shapesize(fs / 20) 
food.penup()

screen.listen()
screen.onkey(mathi, "Up")
screen.onkey(go_right, "Right")
screen.onkey(go_down, "Down")
screen.onkey(go_left, "Left")

r()
turtle.done()
				
			

Code

				
					import turtle
import random

w = 500
h = 500
fs = 10
d = 100  # milliseconds

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

def r():
    global saap, kata, khanaT, pen
    saap = [[0, 0], [0, 20], [0, 40], [0, 60], [0, 80]]
    kata = "up"
    khanaT = nun()
    food.goto(khanaT)
    hall()

def hall():
    global kata

    new_head = saap[-1].copy()
    new_head[0] = saap[-1][0] + offsets[kata][0]
    new_head[1] = saap[-1][1] + offsets[kata][1]

    if new_head in saap[:-1]:  
        r()
    else:
        saap.append(new_head)

        if not khana():
            saap.pop(0)  

        if saap[-1][0] > w / 2:
            saap[-1][0] -= w
        elif saap[-1][0] < - w / 2:
            saap[-1][0] += w
        elif saap[-1][1] > h / 2:
            saap[-1][1] -= h
        elif saap[-1][1] < -h / 2:
            saap[-1][1] += h

        pen.clearstamps()
 #clears all the stamps

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

        screen.update()
 #updates the turtle.screen screen

        turtle.ontimer(hall, d)

def khana():
    global khanaT
    if dist(saap[-1], khanaT) < 20:
        khanaT = nun()
        food.goto(khanaT)
        return True
    return False

def nun():
    x = random.randint(- w / 2 + fs, w / 2 - fs)
    y = random.randint(- h / 2 + fs, h / 2 - fs)
    return (x, y)

def dist(poos1, poos2):
    x1, y1 = poos1
    x2, y2 = poos2
    distance = ((y2 - y1) * 2 + (x2 - x1) * 2) ** 0.5
    return distance

def mathi():
    global kata
    if kata != "down":
        kata = "up"

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

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

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

screen = turtle.Screen()
screen.setup(w, h)
screen.title("saap")
screen.bgcolor("green")
screen.setup(500, 500)
screen.tracer(0)

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

food = turtle.Turtle()
food.shape("circle")
food.color("white")
food.shapesize(fs / 20) 
food.penup()

screen.listen()
screen.onkey(mathi, "Up")
screen.onkey(go_right, "Right")
screen.onkey(go_down, "Down")
screen.onkey(go_left, "Left")

r()
turtle.done()
				
			

Pseudo Code explaining this Python Project

				
					Initialize screen with dimensions and settings
Initialize snake starting position and direction
Create food object

Function to reset the game:
    Set initial snake position
    Set initial direction
    Place food at random position
    Start the main loop

Main loop function:
    Calculate new head position based on current direction
    Check if the new head position collides with the snake's body:
        If collision occurs, reset the game
    Otherwise:
        Add new head position to the snake
        If the snake eats food:
            Generate new food position
        Else:
            Remove the tail segment of the snake

    If the snake goes out of bounds, wrap it to the opposite side
    Clear the screen and redraw the snake
    Set a timer to repeat the main loop

Function to check if the snake eats food:
    Calculate the distance between snake's head and food
    If distance is less than threshold:
        Return true
    Else:
        Return false

Function to generate new food position:
    Choose random x and y within screen bounds
    Return position

Function to calculate distance between two points:
    Use the Pythagorean theorem to calculate the distance
    Return distance

Functions to change snake direction based on user input:
    Change direction if the new direction is not opposite to the current direction

Initialize the game

				
			

Pseudo Code explaining this Python Project

				
					Initialize screen with dimensions and settings
Initialize snake starting position and direction
Create food object

Function to reset the game:
    Set initial snake position
    Set initial direction
    Place food at random position
    Start the main loop

Main loop function:
    Calculate new head position based on current direction
    Check if the new head position collides with the snake's body:
        If collision occurs, reset the game
    Otherwise:
        Add new head position to the snake
        If the snake eats food:
            Generate new food position
        Else:
            Remove the tail segment of the snake

    If the snake goes out of bounds, wrap it to the opposite side
    Clear the screen and redraw the snake
    Set a timer to repeat the main loop

Function to check if the snake eats food:
    Calculate the distance between snake's head and food
    If distance is less than threshold:
        Return true
    Else:
        Return false

Function to generate new food position:
    Choose random x and y within screen bounds
    Return position

Function to calculate distance between two points:
    Use the Pythagorean theorem to calculate the distance
    Return distance

Functions to change snake direction based on user input:
    Change direction if the new direction is not opposite to the current direction

Initialize the game

				
			

Flow Chart

Code Explanation

  • Global Variables:
    • saap: A list representing the snake’s body as a list of coordinates.
    • kata: The current direction of the snake (up, down, left, right).
    • khanaT: The position of the food.
  • Functions:
    • r(): Resets the game state and starts the main game loop.
    • hall(): The main game loop that updates the snake’s position, checks for collisions, and redraws the screen.
    • khana(): Checks if the snake’s head is on the food, updates the food’s position if eaten, and returns whether the snake has eaten the food.
    • nun(): Generates a new random position for the food within the screen bounds.
    • dist(poos1, poos2): Calculates the distance between two points.
    • mathi(), go_right(), go_down(), go_left(): Change the direction of the snake based on user input.

Challenges and Solutions

  • Snake Collisions:
    • Challenge: Detecting when the snake runs into itself.
    • Solution: Check if the new head position is already in the snake’s body.
  • Food Generation:
    • Challenge: Ensuring food appears within screen bounds and not on the snake.
    • Solution: Use random coordinates within the screen bounds and reposition the food if it overlaps with the snake.
  • Smooth Movement:
    • Challenge: Making the snake move smoothly without flickering.
    • Solution: Use turtle.tracer(0) to prevent automatic screen updates and manually update the screen using screen.update().
  •  

Testing

  • Run the game and verify the snake moves in the correct direction based on key presses.
  • Check that the snake grows when it eats the food and the game resets upon collision with itself.

Additional Resources

FAQs

Why is my snake not moving?

  • Ensure you have focused on the turtle graphics window and are using the arrow keys to control the snake.
  1. How can I change the speed of the snake?
  • Adjust the d variable to increase or decrease the delay between each frame.
  1. How can I add more features to the game?
  • You can add features like scoring, sound effects, or more obstacles by expanding the existing code.

Project by Nimisha Agrawal and Documented by Aakarsh Pandey, Team edSlash.