Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Build a playable Snake game in Python with the standard-library turtle module. This version includes arrow-key controls, grid-aligned food, body growth, score and session high-score tracking, wall and self-collision detection, a game-over screen, and an R-key restart.

You do not need a separate game engine. However, Turtle depends on Tk graphical support, so it may not work in every Python installation or browser-based coding environment. See the official Turtle documentation for the module’s current API and Tk requirements.

What you will build

  • A 600×600 Turtle graphics window
  • Arrow-key movement in four directions
  • Continuous movement on a 20-pixel grid
  • Randomly placed food that never appears on the snake
  • Snake growth and score tracking
  • A high score retained while the program is open
  • Wall and self-collision detection
  • A game-over message with restart using the R key

This implementation ends the game when the snake hits a wall. It does not wrap the snake around to the opposite edge.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What you need

  • Python 3
  • A text editor or Python IDE
  • A desktop environment that can open a graphical window
  • Basic knowledge of variables, functions, lists, and if statements
  • A Python installation with Tk support

Turtle is documented as part of Python’s standard library, but it relies on Tk for its window. You should not install Turtle with pip as though it were an ordinary third-party graphics package.

Check Python from a terminal:

python --version

On systems where Python 3 is invoked as python3:

python3 --version

Test Tk support with:

python -m tkinter

If that command cannot open a small Tk window, install Python or the operating system’s Tk package with Tk enabled. The official reference is docs.python.org.

How the game works

The window uses Turtle’s coordinate system. The origin (0, 0) is in the center; positive x values go right, and positive y values go up. The snake moves 20 pixels per game tick, so every head and food position is aligned to the same grid.

The snake has one Turtle object for its head and a Python list containing its body segments. Each segment is a square Turtle object. On every tick, the body moves backward through the list, the first segment moves to the head’s previous position, and the head advances one grid step.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Moving the body from the tail toward the head is essential. If you move the list from the front, each segment can copy the same newly changed position and the body collapses.

Set up the game window

The code uses tracer(0) and update() so the screen is redrawn once per game tick instead of after every Turtle movement. The playable boundary is approximately ±280 rather than the full ±300 window radius, leaving room for the visible square Turtle shape.

Controls and timing

Click the Turtle window before pressing the arrow keys. screen.listen() gives the window keyboard focus, and onkeypress() connects key presses to direction functions. The direction functions also reject an immediate 180-degree turn.

The game loop uses screen.ontimer(), which schedules the next callback without blocking the graphical event loop. The delay is measured in milliseconds: 100 means the callback is requested every 100 milliseconds, though operating-system scheduling is not a precision frame clock.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Complete code

Save the following as snake_game.py. It is designed to run as one complete file.

import random
import turtle

# Recommended game settings
WIDTH = 600
HEIGHT = 600
STEP = 20
BOUNDARY = 280
DELAY = 100  # milliseconds

# Game state
head = None
food = None
score_pen = None
message_pen = None
segments = []
direction = "stop"
score = 0
high_score = 0
game_running = True


def setup_screen():
    screen = turtle.Screen()
    screen.title("Snake Game")
    screen.bgcolor("black")
    screen.setup(width=WIDTH, height=HEIGHT)
    screen.tracer(0)
    return screen


def create_head():
    global head
    head = turtle.Turtle("square")
    head.penup()
    head.color("lime")
    head.goto(0, 0)


def create_food():
    global food
    food = turtle.Turtle("circle")
    food.penup()
    food.color("red")
    place_food()


def create_scoreboard():
    global score_pen, message_pen

    score_pen = turtle.Turtle()
    score_pen.hideturtle()
    score_pen.penup()
    score_pen.color("white")
    score_pen.goto(0, 260)

    message_pen = turtle.Turtle()
    message_pen.hideturtle()
    message_pen.penup()
    message_pen.color("white")


def occupied_positions():
    positions = {(round(head.xcor()), round(head.ycor()))}
    positions.update(
        (round(segment.xcor()), round(segment.ycor()))
        for segment in segments
    )
    return positions


def place_food():
    """Put food on an unoccupied position on the movement grid."""
    available = [
        (x, y)
        for x in range(-BOUNDARY, BOUNDARY + STEP, STEP)
        for y in range(-BOUNDARY, BOUNDARY + STEP, STEP)
        if (x, y) not in occupied_positions()
    ]

    if available:
        food.goto(random.choice(available))


def update_scoreboard():
    score_pen.clear()
    score_pen.write(
        f"Score: {score}  High Score: {high_score}",
        align="center",
        font=("Arial", 20, "bold"),
    )


def set_direction(new_direction):
    global direction

    opposite = {
        "up": "down",
        "down": "up",
        "left": "right",
        "right": "left",
    }

    if direction == "stop" or opposite.get(direction) != new_direction:
        direction = new_direction


def go_up():
    set_direction("up")


def go_down():
    set_direction("down")


def go_left():
    set_direction("left")


def go_right():
    set_direction("right")


def move_body():
    """Move each body segment into the previous segment's old position."""
    for index in range(len(segments) - 1, 0, -1):
        segments[index].goto(segments[index - 1].position())

    if segments:
        segments[0].goto(head.position())


def move_head():
    x = head.xcor()
    y = head.ycor()

    if direction == "up":
        head.sety(y + STEP)
    elif direction == "down":
        head.sety(y - STEP)
    elif direction == "left":
        head.setx(x - STEP)
    elif direction == "right":
        head.setx(x + STEP)


def grow_snake():
    segment = turtle.Turtle("square")
    segment.penup()
    segment.color("green")

    if segments:
        segment.goto(segments[-1].position())
    else:
        segment.goto(head.position())

    segments.append(segment)


def end_game():
    global game_running

    game_running = False
    message_pen.clear()
    message_pen.goto(0, 0)
    message_pen.write(
        "GAME OVER",
        align="center",
        font=("Arial", 28, "bold"),
    )
    message_pen.goto(0, -40)
    message_pen.write(
        "Press R to restart",
        align="center",
        font=("Arial", 16, "normal"),
    )


def check_collisions():
    global score, high_score

    head_position = (round(head.xcor()), round(head.ycor()))

    # Wall collision
    if abs(head_position[0]) > BOUNDARY or abs(head_position[1]) > BOUNDARY:
        end_game()
        return

    # Self-collision
    body_positions = {
        (round(segment.xcor()), round(segment.ycor()))
        for segment in segments
    }
    if head_position in body_positions:
        end_game()
        return

    # Food collision
    food_position = (round(food.xcor()), round(food.ycor()))
    if head_position == food_position:
        grow_snake()
        score += 1
        high_score = max(high_score, score)
        update_scoreboard()
        place_food()


def reset_game():
    global direction, score, game_running

    for segment in segments:
        segment.hideturtle()
    segments.clear()

    head.goto(0, 0)
    direction = "stop"
    score = 0
    game_running = True
    message_pen.clear()
    place_food()
    update_scoreboard()
    screen.ontimer(game_loop, DELAY)


def game_loop():
    if not game_running:
        return

    move_body()
    move_head()
    check_collisions()
    screen.update()

    if game_running:
        screen.ontimer(game_loop, DELAY)


screen = setup_screen()
create_head()
create_food()
create_scoreboard()
update_scoreboard()

screen.listen()
screen.onkeypress(go_up, "Up")
screen.onkeypress(go_down, "Down")
screen.onkeypress(go_left, "Left")
screen.onkeypress(go_right, "Right")
screen.onkeypress(reset_game, "r")
screen.onkeypress(reset_game, "R")

game_loop()
screen.mainloop()

Run the game

Open a terminal in the directory containing snake_game.py and run:

python snake_game.py

Or, on systems that use the python3 command:

python3 snake_game.py

A black 600×600 window should open. The snake initially stays still until you press an arrow key. Eating food increases the score and adds a green body segment. Hitting a wall or the snake’s body displays the game-over message. Press R to reset the board and score.

Important implementation details

Why food is grid-aligned

The snake moves in 20-pixel increments, so food should use the same coordinate grid. Generating arbitrary random coordinates can place food between reachable movement cells. The place_food() function builds a list of valid grid positions and chooses one that is not occupied by the snake.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why food placement checks the body

Food is not merely placed inside the window. It is compared with the head and every body segment first, preventing food from appearing underneath the snake.

Why the body moves backward

Suppose the head is at (40, 0) and the first body segment is at (20, 0). The first segment must move to the head’s old position, while the tail must move into the position previously occupied by the segment before it. Updating from the last list item toward the first preserves all of those old positions.

Why immediate reversals are blocked

When a snake has a body, turning directly from left to right or up to down would make the head enter the segment immediately behind it. The direction map rejects those changes.

What the high score means

This code keeps high_score in memory only. It survives a game reset during the current program session, but it is lost when the program closes. Saving it permanently would require writing the value to a file.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting

Arrow keys do nothing

  • Click the Turtle window so it has focus.
  • Confirm that screen.listen() is present.
  • Use the exact key names "Up", "Down", "Left", and "Right".
  • Pass the function itself, not the result of calling it.
# Correct
screen.onkeypress(go_up, "Up")

# Incorrect
screen.onkeypress(go_up(), "Up")

Turtle’s keyboard documentation notes that the TurtleScreen must have focus to receive key events. See the official reference.

No module named '_tkinter'

Your Python installation lacks Tk support or the operating system’s Tk package is missing. Install a Python distribution with Tk enabled, or install the appropriate Tk package for your operating system. Then retry:

python -m tkinter

Installing an unrelated package named turtle is not the correct fix for a missing _tkinter module.

The window closes immediately

Run the program from a terminal rather than double-clicking it so that any traceback remains visible. Also make sure the file reaches screen.mainloop() and that the code was copied as one complete block.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The game is too fast or too slow

Change:

DELAY = 100

A smaller value requests faster movement; a larger value requests slower movement. With ontimer(), the value is in milliseconds. It is not the same as a time.sleep() value in seconds.

The scoreboard duplicates or flickers

The scoreboard is cleared before each rewrite:

score_pen.clear()

The code also uses controlled rendering through tracer(0) and screen.update().

The window crashes when closed

A permanently blocking loop can continue trying to update a destroyed Tk window. This version uses Turtle’s event timer and a game_running flag, which provides a cleaner shutdown path. If you adapt it, avoid adding an unconditional while True loop around the window.

Possible extensions

Once the basic version works, you can add:

  • Pause and resume controls
  • A start screen and a dedicated game-over screen
  • Persistent high-score storage in high_score.txt
  • Increasing speed as the score rises
  • Wraparound walls instead of wall collisions
  • Obstacles and multiple food types
  • Sound effects
  • A Pygame version if you want a larger, more scalable game architecture

Turtle is well suited to learning movement, coordinates, lists, callbacks, and collision rules. A larger game may eventually benefit from a framework with more control over rendering, input, audio, and asset management.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.