Building a classic game is one of the best ways to turn Python fundamentals into a complete project. A Snake game in Python combines variables, functions, keyboard events, lists, coordinates, collision detection, and a game loop in a result you can see and play.
In this tutorial, you will build Snake with Python’s standard turtle module. The snake moves across a grid, grows when it reaches food, earns points, and restarts when it hits a wall or its own body. You do not need to install an external package.
The project is beginner-friendly, but it also introduces patterns used in larger games: separate objects, controlled frame updates, input handlers, state management, and collision rules. After finishing it, you can compare the approach with Pygame for beginners and decide which library fits your next project.
How the Snake Game Works
The game contains five main parts:
- A window that displays the game.
- A head that moves one grid cell at a time.
- Food placed at a random grid position.
- Body segments that follow the previous segment.
- Rules for scoring, wall collisions, and self-collisions.
The coordinate system places the center of the window at (0, 0). Positive x-values move right, negative x-values move left, positive y-values move up, and negative y-values move down. We will use a grid size of 20 pixels so the snake and food always align.
Why Use Turtle for a Beginner Snake Game?
turtle is included with many Python installations and provides immediate visual feedback. It can create multiple drawable objects, listen for keyboard events, update a screen, and schedule repeated functions. That makes it suitable for learning game logic without first learning a larger framework.
The official Python turtle documentation explains the object-oriented interface used in this project. Food placement uses the standard random module.
Turtle is ideal for small educational games. For smoother animation, sound, sprites, or larger projects, the batch companion guide on building games with Pygame is the natural next step.
Project Setup
Create a file named snake_game.py. The first step imports the modules and defines constants:
import random
import turtle
WIDTH = 600
HEIGHT = 600
STEP = 20
DELAY_MS = 100Constants keep important values in one place. Changing DELAY_MS changes the game speed. Changing STEP changes the grid size, although the snake and food sizes should remain consistent with it.
Create the Game Window
screen = turtle.Screen()
screen.title("Snake Game in Python")
screen.bgcolor("#111827")
screen.setup(width=WIDTH, height=HEIGHT)
screen.tracer(0)screen.tracer(0) disables automatic drawing updates. We will call screen.update() once per frame, which avoids visible flickering and gives the program control over when the scene is redrawn.
Create the Snake Head
head = turtle.Turtle()
head.shape("square")
head.color("#22c55e")
head.penup()
head.goto(0, 0)
head.direction = "stop"penup() prevents the turtle from drawing a line while it moves. The custom direction attribute stores the current movement state.
Handle Keyboard Input
Four functions change direction. Each function prevents an immediate 180-degree turn because that would make the head collide with the first body segment.
def go_up():
if head.direction != "down":
head.direction = "up"
def go_down():
if head.direction != "up":
head.direction = "down"
def go_left():
if head.direction != "right":
head.direction = "left"
def go_right():
if head.direction != "left":
head.direction = "right"Bind the functions to the arrow keys:
screen.listen()
screen.onkeypress(go_up, "Up")
screen.onkeypress(go_down, "Down")
screen.onkeypress(go_left, "Left")
screen.onkeypress(go_right, "Right")Event-handler functions normally receive no argument here. Their job is to update state; the main frame function performs the movement. This separation keeps input and game logic easier to understand.
Move the Snake on a Grid
def move_head():
x = head.xcor()
y = head.ycor()
if head.direction == "up":
head.sety(y + STEP)
elif head.direction == "down":
head.sety(y - STEP)
elif head.direction == "left":
head.setx(x - STEP)
elif head.direction == "right":
head.setx(x + STEP)The function changes one coordinate by exactly 20 pixels. The Python conditional expressions guide can help with short decisions, but ordinary if/elif blocks are clearer for mutually exclusive directions.
Create and Reposition the Food
food = turtle.Turtle()
food.shape("circle")
food.color("#ef4444")
food.penup()
food.goto(100, 0)
def random_grid_position():
horizontal_cells = (WIDTH // 2 - STEP) // STEP
vertical_cells = (HEIGHT // 2 - STEP) // STEP
x = random.randint(-horizontal_cells, horizontal_cells) * STEP
y = random.randint(-vertical_cells, vertical_cells) * STEP
return x, yMultiplying a random integer by STEP guarantees that food appears on the same grid as the snake. The function demonstrates a useful Python return statement that sends two coordinates back as a tuple.
Grow the Snake Body
Store body segments in a list. When the snake eats food, create a new square and append it:
segments = []
def add_segment():
segment = turtle.Turtle()
segment.shape("square")
segment.color("#86efac")
segment.penup()
segment.goto(1000, 1000)
segments.append(segment)Before moving the head, move each segment to the previous segment’s old position. Iterate backward so you do not overwrite a position before the next segment can use it.
def move_segments():
for index in range(len(segments) - 1, 0, -1):
previous_x = segments[index - 1].xcor()
previous_y = segments[index - 1].ycor()
segments[index].goto(previous_x, previous_y)
if segments:
segments[0].goto(head.xcor(), head.ycor())Backward iteration is a useful pattern whenever each item depends on the state of the item before it. For other iteration tools, see the Python itertools beginner guide.
Add the Score Display
score = 0
high_score = 0
score_writer = turtle.Turtle()
score_writer.hideturtle()
score_writer.color("white")
score_writer.penup()
score_writer.goto(0, HEIGHT // 2 - 45)
def update_score():
score_writer.clear()
score_writer.write(
f"Score: {score} High score: {high_score}",
align="center",
font=("Arial", 18, "normal"),
)The score variables belong to the game state. In a larger project, you could group state inside a class or a Python dataclass.
Detect Food, Walls, and the Snake Body
Turtle’s distance() method makes food and body collisions easy. For walls, compare the head coordinates with the playable boundaries.
def hit_wall():
horizontal_limit = WIDTH // 2 - STEP
vertical_limit = HEIGHT // 2 - STEP
return (
abs(head.xcor()) > horizontal_limit
or abs(head.ycor()) > vertical_limit
)
def hit_body():
return any(head.distance(segment) < STEP for segment in segments)any() stops as soon as one segment is close enough to count as a collision. This is more expressive than managing a separate Boolean flag manually.
Reset the Game Safely
def reset_game():
global score, high_score
if score > high_score:
high_score = score
score = 0
head.goto(0, 0)
head.direction = "stop"
for segment in segments:
segment.goto(1000, 1000)
segments.clear()
update_score()The off-screen move hides old segments before the list is cleared. The high score remains available until the program closes.
Build the Frame Function
Instead of using a blocking infinite loop, schedule the next frame with screen.ontimer(). This allows Turtle’s event system to keep processing keyboard and window events.
def game_frame():
global score
move_segments()
move_head()
if head.distance(food) < STEP:
food.goto(random_grid_position())
add_segment()
score += 10
update_score()
if hit_wall() or hit_body():
reset_game()
screen.update()
screen.ontimer(game_frame, DELAY_MS)Complete Snake Game Code
import random
import turtle
WIDTH = 600
HEIGHT = 600
STEP = 20
DELAY_MS = 100
screen = turtle.Screen()
screen.title("Snake Game in Python")
screen.bgcolor("#111827")
screen.setup(width=WIDTH, height=HEIGHT)
screen.tracer(0)
head = turtle.Turtle()
head.shape("square")
head.color("#22c55e")
head.penup()
head.goto(0, 0)
head.direction = "stop"
food = turtle.Turtle()
food.shape("circle")
food.color("#ef4444")
food.penup()
food.goto(100, 0)
segments = []
score = 0
high_score = 0
score_writer = turtle.Turtle()
score_writer.hideturtle()
score_writer.color("white")
score_writer.penup()
score_writer.goto(0, HEIGHT // 2 - 45)
def update_score():
score_writer.clear()
score_writer.write(
f"Score: {score} High score: {high_score}",
align="center",
font=("Arial", 18, "normal"),
)
def go_up():
if head.direction != "down":
head.direction = "up"
def go_down():
if head.direction != "up":
head.direction = "down"
def go_left():
if head.direction != "right":
head.direction = "left"
def go_right():
if head.direction != "left":
head.direction = "right"
def move_head():
x = head.xcor()
y = head.ycor()
if head.direction == "up":
head.sety(y + STEP)
elif head.direction == "down":
head.sety(y - STEP)
elif head.direction == "left":
head.setx(x - STEP)
elif head.direction == "right":
head.setx(x + STEP)
def move_segments():
for index in range(len(segments) - 1, 0, -1):
segments[index].goto(
segments[index - 1].xcor(),
segments[index - 1].ycor(),
)
if segments:
segments[0].goto(head.xcor(), head.ycor())
def add_segment():
segment = turtle.Turtle()
segment.shape("square")
segment.color("#86efac")
segment.penup()
segment.goto(1000, 1000)
segments.append(segment)
def random_grid_position():
horizontal_cells = (WIDTH // 2 - STEP) // STEP
vertical_cells = (HEIGHT // 2 - STEP) // STEP
return (
random.randint(-horizontal_cells, horizontal_cells) * STEP,
random.randint(-vertical_cells, vertical_cells) * STEP,
)
def hit_wall():
return (
abs(head.xcor()) > WIDTH // 2 - STEP
or abs(head.ycor()) > HEIGHT // 2 - STEP
)
def hit_body():
return any(head.distance(segment) < STEP for segment in segments)
def reset_game():
global score, high_score
high_score = max(high_score, score)
score = 0
head.goto(0, 0)
head.direction = "stop"
for segment in segments:
segment.goto(1000, 1000)
segments.clear()
update_score()
def game_frame():
global score
move_segments()
move_head()
if head.distance(food) < STEP:
food.goto(random_grid_position())
add_segment()
score += 10
update_score()
if hit_wall() or hit_body():
reset_game()
screen.update()
screen.ontimer(game_frame, DELAY_MS)
screen.listen()
screen.onkeypress(go_up, "Up")
screen.onkeypress(go_down, "Down")
screen.onkeypress(go_left, "Left")
screen.onkeypress(go_right, "Right")
update_score()
game_frame()
screen.mainloop()Save the file and run it with python snake_game.py. Use the arrow keys to start moving.
Ways to Improve the Game
- Decrease
DELAY_MSas the score increases. - Prevent food from appearing on a body segment.
- Add a start screen and a game-over message.
- Save the high score to a text or JSON file.
- Add obstacles, levels, or special food.
- Record errors and sessions with Python logging.
- Move the game state into classes before expanding the project.
Common Problems
The Turtle window opens and closes immediately
Make sure the program ends with screen.mainloop(). It keeps the graphical event loop running until the window is closed.
Keyboard controls do not respond
Call screen.listen() after creating the window, then click the game window once so it has keyboard focus.
The snake moves through the food
Keep both objects aligned to the same grid and use a collision distance close to the grid size.
The snake can reverse into itself
Check the current direction before accepting the opposite direction, as the four input functions do.
Frequently Asked Questions
Do I need to install Turtle?
It is part of Python’s standard library, although some Linux distributions require the Tk interface package for graphical windows.
Can I add images instead of squares?
Yes. Turtle can register compatible image shapes, but simple built-in shapes keep the first version portable.
Why use ontimer() instead of while True?
A scheduled frame function cooperates with Turtle’s event loop, allowing keyboard and window events to remain responsive.
Can this game run in a browser?
Standard Turtle is designed for a desktop graphical environment. A browser version normally requires a different runtime or a web-focused library.
Conclusion
You now have a complete Snake game that uses only Python’s standard library. More importantly, you have practiced reusable programming skills: event handlers, state, lists, functions, coordinate calculations, collision checks, and scheduled updates.
Experiment with one improvement at a time. Changing speed, colors, scoring, food behavior, and level design will teach you more than copying a much larger project all at once. When you need sprites, audio, and more control over the rendering loop, continue with the Pygame version of game development.






