Creating Interactive Games with Python's Turtle Graphics Library

Introduction to Turtle Graphics Game Development

The Python turtle library provides a simple yet powerful way to create interactive graphical applications and games. This tutorial explores ten engaging games that deomnstrate various programming concepts while being fun to play and modify.

1. Classic Snake Game

Rules: Control the snake using arrow keys to eat food pellets. Each pellet consumed makes the snake longer. The game ends if the snake hits itself or the boundary.


import random
import turtle
from turtle import Turtle, Screen
from freegames import vector

food_item = vector(0, 0)
snake_body = [vector(10, 0)]
direction = vector(0, -10)


def change_direction(x, y):
    """Update snake movement direction."""
    direction.x = x
    direction.y = y


def within_bounds(position):
    """Check if position is within game boundaries."""
    return -200 < position.x < 190 and -200 < position.y < 190


def game_loop():
    """Move snake forward one segment."""
    head = snake_body[-1].copy()
    head.move(direction)

    if not within_bounds(head) or head in snake_body:
        turtle.penup()
        turtle.goto(head.x, head.y)
        turtle.pendown()
        turtle.color('red')
        turtle.dot(9)
        turtle.update()
        return

    snake_body.append(head)

    if head == food_item:
        print('Snake length:', len(snake_body))
        food_item.x = random.randrange(-15, 15) * 10
        food_item.y = random.randrange(-15, 15) * 10
    else:
        snake_body.pop(0)

    turtle.clear()

    for segment in snake_body:
        turtle.penup()
        turtle.goto(segment.x, segment.y)
        turtle.pendown()
        turtle.dot(9, 'black')

    turtle.penup()
    turtle.goto(food_item.x, food_item.y)
    turtle.pendown()
    turtle.dot(9, 'green')
    turtle.update()
    turtle.ontimer(game_loop, 100)


# Initialize game window
screen = Screen()
screen.setup(420, 420, 370, 0)
turtle.hideturtle()
turtle.tracer(False)
turtle.listen()
turtle.onkey(lambda: change_direction(10, 0), 'Right')
turtle.onkey(lambda: change_direction(-10, 0), 'Left')
turtle.onkey(lambda: change_direction(0, 10), 'Up')
turtle.onkey(lambda: change_direction(0, -10), 'Down')
game_loop()
turtle.done()

2. Pac-Man Adventure

Rules: Navigate the yellow Pac-Man character to collect all white dots while avoiding red ghosts. The game ends if a ghost catches you.


import random
import turtle
from turtle import Turtle, Screen
from freegames import floor, vector

game_state = {'score': 0}
path_drawer = Turtle(visible=False)
score_writer = Turtle(visible=False)
player_direction = vector(5, 0)
pacman_pos = vector(-40, -80)
ghosts = [
    [vector(-180, 160), vector(5, 0)],
    [vector(-180, -160), vector(0, 5)],
    [vector(100, 160), vector(0, -5)],
    [vector(100, -160), vector(-5, 0)],
]
# Maze layout
maze_tiles = [
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
    0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0,
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
    0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0,
    0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0,
    0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
    0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
    0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0,
    0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
    0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
    0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0,
    0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
    0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0,
    0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]


def draw_square(x, y):
    """Draw square at position (x, y)."""
    path_drawer.penup()
    path_drawer.goto(x, y)
    path_drawer.pendown()
    path_drawer.begin_fill()

    for _ in range(4):
        path_drawer.forward(20)
        path_drawer.left(90)

    path_drawer.end_fill()


def get_tile_index(position):
    """Convert position to maze tile index."""
    x = floor(position.x, 20) + 200
    y = 180 - floor(position.y, 20)
    return int(x / 20 + y * 20 / 20)


def is_valid_position(position):
    """Check if position is valid in the maze."""
    tile_index = get_tile_index(position)
    if maze_tiles[tile_index] == 0:
        return False

    tile_index = get_tile_index(position + 19)
    if maze_tiles[tile_index] == 0:
        return False

    return position.x % 20 == 0 or position.y % 20 == 0


def draw_maze():
    """Draw the maze layout."""
    turtle.bgcolor('black')
    path_drawer.color('blue')

    for index in range(len(maze_tiles)):
        tile = maze_tiles[index]
        if tile > 0:
            x = (index % 20) * 20 - 200
            y = 180 - (index // 20) * 20
            draw_square(x, y)

            if tile == 1:
                score_writer.penup()
                score_writer.goto(x + 10, y + 10)
                score_writer.dot(2, 'white')


def game_update():
    """Update game state and positions."""
    turtle.clear()
    score_writer.write(game_state['score'])

    if is_valid_position(pacman_pos + player_direction):
        pacman_pos.move(player_direction)

    tile_index = get_tile_index(pacman_pos)
    if maze_tiles[tile_index] == 1:
        maze_tiles[tile_index] = 2
        game_state['score'] += 1
        x = (tile_index % 20) * 20 - 200
        y = 180 - (tile_index // 20) * 20
        draw_square(x, y)

    turtle.penup()
    turtle.goto(pacman_pos.x + 10, pacman_pos.y + 10)
    turtle.dot(20, 'yellow')

    for ghost_pos, ghost_dir in ghosts:
        if is_valid_position(ghost_pos + ghost_dir):
            ghost_pos.move(ghost_dir)
        else:
            directions = [
                vector(5, 0),
                vector(-5, 0),
                vector(0, 5),
                vector(0, -5),
            ]
            new_dir = random.choice(directions)
            ghost_dir.x = new_dir.x
            ghost_dir.y = new_dir.y

        turtle.penup()
        turtle.goto(ghost_pos.x + 10, ghost_pos.y + 10)
        turtle.dot(20, 'red')

    turtle.update()

    for ghost_pos, _ in ghosts:
        if abs(pacman_pos - ghost_pos) < 20:
            return

    turtle.ontimer(game_update, 100)


def change_direction(x, y):
    """Change Pac-Man direction if valid."""
    if is_valid_position(pacman_pos + vector(x, y)):
        player_direction.x = x
        player_direction.y = y


# Initialize game
screen = Screen()
screen.setup(420, 420, 370, 0)
turtle.hideturtle()
turtle.tracer(False)
score_writer.color('white')
score_writer.write(game_state['score'])
turtle.listen()
turtle.onkey(lambda: change_direction(5, 0), 'Right')
turtle.onkey(lambda: change_direction(-5, 0), 'Left')
turtle.onkey(lambda: change_direction(0, 5), 'Up')
turtle.onkey(lambda: change_direction(0, -5), 'Down')
draw_maze()
game_update()
turtle.done()

3. Balloon Pop Challenge

Rules: Click to launch projectiles. Pop all blue balloons before they reach the other side of the screen.


import random
import turtle
from turtle import Screen
from freegames import vector

projectile = vector(-200, -200)
velocity = vector(0, 0)
balloons = []


def fire(x, y):
    """Launch projectile toward click position."""
    if not within_bounds(projectile):
        projectile.x = -199
        projectile.y = -199
        velocity.x = (x + 200) / 25
        velocity.y = (y + 200) / 25


def within_bounds(xy):
    """Check if position is within screen boundaries."""
    return -200 < xy.x < 200 and -200 < xy.y < 200


def render():
    """Draw projectile and balloons."""
    turtle.clear()

    for balloon in balloons:
        turtle.penup()
        turtle.goto(balloon.x, balloon.y)
        turtle.dot(20, 'blue')

    if within_bounds(projectile):
        turtle.penup()
        turtle.goto(projectile.x, projectile.y)
        turtle.dot(6, 'red')

    turtle.update()


def update_game():
    """Update positions of projectiles and balloons."""
    if random.randint(0, 39) == 0:
        y = random.randint(-150, 150)
        new_balloon = vector(200, y)
        balloons.append(new_balloon)

    for balloon in balloons:
        balloon.x -= 0.5

    if within_bounds(projectile):
        velocity.y -= 0.35
        projectile.move(velocity)

    balloon_copy = balloons.copy()
    balloons.clear()

    for balloon in balloon_copy:
        if abs(balloon - projectile) > 13:
            balloons.append(balloon)

    render()

    for balloon in balloons:
        if not within_bounds(balloon):
            return

    turtle.ontimer(update_game, 50)


# Initialize game
screen = Screen()
screen.setup(420, 420, 370, 0)
turtle.hideturtle()
turtle.penup()
turtle.tracer(False)
screen.onclick(fire)
update_game()
turtle.done()

4. Connect Four

Rules: Click on a column to place a disc. First player to connect four discs vertically, horizontally, or diagonally wins.


import turtle
from turtle import Screen
from freegames import line

players = {'red': 'yellow', 'yellow': 'red'}
game_state = {'current_player': 'yellow', 'column_counts': [0] * 8}


def draw_grid():
    """Draw the Connect Four game board."""
    turtle.bgcolor('light blue')

    for x in range(-150, 200, 50):
        line(x, -200, x, 200)

    for x in range(-175, 200, 50):
        for y in range(-175, 200, 50):
            turtle.penup()
            turtle.goto(x, y)
            turtle.dot(40, 'white')

    turtle.update()


def place_disc(x, y):
    """Place a disc in the selected column."""
    current_player = game_state['current_player']
    column_counts = game_state['column_counts']

    column = int((x + 200) // 50)
    disc_count = column_counts[column]

    disc_x = ((x + 200) // 50) * 50 - 200 + 25
    disc_y = disc_count * 50 - 200 + 25

    turtle.penup()
    turtle.goto(disc_x, disc_y)
    turtle.dot(40, current_player)
    turtle.update()

    column_counts[column] = disc_count + 1
    game_state['current_player'] = players[current_player]


# Initialize game
screen = Screen()
screen.setup(420, 420, 370, 0)
turtle.hideturtle()
turtle.tracer(False)
draw_grid()
screen.onclick(place_disc)
turtle.done()

5. Sky Navigator

Rules: Tap to make the bird flap its wings. Avoid black crows or the game ends.


import random
import turtle
from turtle import Screen
from freegames import vector

bird = vector(0, 0)
obstacles = []


def flap(x, y):
    """Make bird ascend in response to screen tap."""
    upward = vector(0, 30)
    bird.move(upward)


def within_bounds(point):
    """Check if point is within screen boundaries."""
    return -200 < point.x < 200 and -200 < point.y < 200


def draw_game(alive):
    """Draw all game elements."""
    turtle.clear()

    turtle.penup()
    turtle.goto(bird.x, bird.y)

    if alive:
        turtle.dot(10, 'green')
    else:
        turtle.dot(10, 'red')

    for obstacle in obstacles:
        turtle.penup()
        turtle.goto(obstacle.x, obstacle.y)
        turtle.dot(20, 'black')

    turtle.update()


def update_game():
    """Update positions of game elements."""
    bird.y -= 5

    for obstacle in obstacles:
        obstacle.x -= 3

    if random.randint(0, 9) == 0:
        y = random.randint(-199, 199)
        new_obstacle = vector(199, y)
        obstacles.append(new_obstacle)

    while len(obstacles) > 0 and not within_bounds(obstacles[0]):
        obstacles.pop(0)

    if not within_bounds(bird):
        draw_game(False)
        return

    for obstacle in obstacles:
        if abs(obstacle - bird) < 15:
            draw_game(False)
            return

    draw_game(True)
    turtle.ontimer(update_game, 50)


# Initialize game
screen = Screen()
screen.setup(420, 420, 370, 0)
turtle.hideturtle()
turtle.penup()
turtle.tracer(False)
screen.onclick(flap)
update_game()
turtle.done()

6. Memory Match Challenge

Rules: Click tiles to reveal numbers. Match pairs to keep them visible.


import random
import turtle
from turtle import Screen
from freegames import path

image_path = path('')
tile_numbers = list(range(32)) * 2
game_state = {'selected_tile': None}
tile_visibility = [True] * 64


def draw_tile(x, y):
    """Draw white square with black outline at (x, y)."""
    turtle.penup()
    turtle.goto(x, y)
    turtle.pendown()
    turtle.color('black', 'white')
    turtle.begin_fill()
    for _ in range(4):
        turtle.forward(50)
        turtle.left(90)
    turtle.end_fill()


def get_tile_index(x, y):
    """Convert (x, y) coordinates to tile index."""
    return int((x + 200) // 50 + ((y + 200) // 50) * 8)


def get_tile_coordinates(count):
    """Convert tile count to (x, y) coordinates."""
    return (count % 8) * 50 - 200, (count // 8) * 50 - 200


def handle_click(x, y):
    """Update selected tile and visibility based on click."""
    tile_index = get_tile_index(x, y)
    selected_tile = game_state['selected_tile']

    if selected_tile is None or selected_tile == tile_index or tile_numbers[selected_tile] != tile_numbers[tile_index]:
        game_state['selected_tile'] = tile_index
    else:
        tile_visibility[tile_index] = False
        tile_visibility[selected_tile] = False
        game_state['selected_tile'] = None


def render_game():
    """Draw image and tiles."""
    turtle.clear()
    turtle.penup()
    turtle.goto(0, 0)
    turtle.shape(image_path)
    turtle.stamp()

    for count in range(64):
        if tile_visibility[count]:
            x, y = get_tile_coordinates(count)
            draw_tile(x, y)

    selected_tile = game_state['selected_tile']
    if selected_tile is not None and tile_visibility[selected_tile]:
        x, y = get_tile_coordinates(selected_tile)
        turtle.penup()
        turtle.goto(x + 2, y)
        turtle.color('black')
        turtle.write(tile_numbers[selected_tile], font=('Arial', 30, 'normal'))

    turtle.update()
    turtle.ontimer(render_game, 100)


random.shuffle(tile_numbers)
screen = Screen()
screen.setup(420, 420, 370, 0)
screen.addshape(image_path)
turtle.hideturtle()
turtle.tracer(False)
screen.onclick(handle_click)
render_game()
turtle.done()

7. Paddle Battle

Rules: Use keyboard to move paddles. First player to miss the ball loses. (Left: W/S, Right: I/K)


import random
import turtle
from turtle import Screen
from freegames import vector


def random_velocity():
    """Generate random velocity between (-5, -3) or (3, 5)."""
    return (3 + random.random() * 2) * random.choice([1, -1])


ball = vector(0, 0)
ball_velocity = random_velocity()
game_state = {1: 0, 2: 0}


def move_paddle(player, change):
    """Move paddle position by specified amount."""
    game_state[player] += change


def draw_rectangle(x, y, width, height):
    """Draw rectangle at (x, y) with given width and height."""
    turtle.penup()
    turtle.goto(x, y)
    turtle.pendown()
    turtle.begin_fill()
    for _ in range(2):
        turtle.forward(width)
        turtle.left(90)
        turtle.forward(height)
        turtle.left(90)
    turtle.end_fill()


def game_loop():
    """Update game state and ball position."""
    turtle.clear()
    draw_rectangle(-200, game_state[1], 10, 50)
    draw_rectangle(190, game_state[2], 10, 50)

    ball.move(ball_velocity)
    x = ball.x
    y = ball.y

    turtle.penup()
    turtle.goto(x, y)
    turtle.dot(10)
    turtle.update()

    if y < -200 or y > 200:
        ball_velocity.y = -ball_velocity.y

    if x < -185:
        paddle_top = game_state[1] + 50
        if game_state[1] <= y <= paddle_top:
            ball_velocity.x = -ball_velocity.x
        else:
            return

    if x > 185:
        paddle_top = game_state[2] + 50
        if game_state[2] <= y <= paddle_top:
            ball_velocity.x = -ball_velocity.x
        else:
            return

    turtle.ontimer(game_loop, 50)


# Initialize game
screen = Screen()
screen.setup(420, 420, 370, 0)
turtle.hideturtle()
turtle.tracer(False)
screen.listen()
screen.onkey(lambda: move_paddle(1, 20), 'w')
screen.onkey(lambda: move_paddle(1, -20), 's')
screen.onkey(lambda: move_paddle(2, 20), 'i')
screen.onkey(lambda: move_paddle(2, -20), 'k')
game_loop()
turtle.done()

8. Tic-Tac-Toe

Rules: Click to place X or O. First to get three in a row wins!


import turtle
from turtle import Screen
from freegames import line


def draw_grid():
    """Draw tic-tac-toe grid."""
    line(-67, 200, -67, -200)
    line(67, 200, 67, -200)
    line(-200, -67, 200, -67)
    line(-200, 67, 200, 67)


def draw_x(x, y):
    """Draw X symbol."""
    line(x, y, x + 133, y + 133)
    line(x, y + 133, x + 133, y)


def draw_o(x, y):
    """Draw O symbol."""
    turtle.penup()
    turtle.goto(x + 67, y + 5)
    turtle.pendown()
    turtle.circle(62)


def snap_to_grid(value):
    """Round value to grid with square size 133."""
    return ((value + 200) // 133) * 133 - 200


game_state = {'current_player': 0}
players = [draw_x, draw_o]


def handle_click(x, y):
    """Draw X or O in clicked square."""
    x = snap_to_grid(x)
    y = snap_to_grid(y)
    current_player = game_state['current_player']
    draw_function = players[current_player]
    draw_function(x, y)
    turtle.update()
    game_state['current_player'] = not current_player


# Initialize game
screen = Screen()
screen.setup(420, 420, 370, 0)
turtle.hideturtle()
turtle.tracer(False)
draw_grid()
turtle.update()
screen.onclick(handle_click)
turtle.done()

9. Number Slide Puzzle

Rules: Click tiles adjacent to empty space to swap. Arrange numbers in order from left to right.


import random
import turtle
from turtle import Screen
from freegames import floor, vector

tile_positions = {}
possible_moves = [
    vector(100, 0),
    vector(-100, 0),
    vector(0, 100),
    vector(0, -100),
]


def initialize_puzzle():
    """Create tiles and scramble them."""
    tile_number = 1

    for y in range(-200, 200, 100):
        for x in range(-200, 200, 100):
            position = vector(x, y)
            tile_positions[position] = tile_number
            tile_number += 1

    tile_positions[position] = None  # Empty space

    # Scramble tiles
    for _ in range(1000):
        neighbor = random.choice(possible_moves)
        empty_spot = position + neighbor

        if empty_spot in tile_positions:
            tile_number = tile_positions[empty_spot]
            tile_positions[empty_spot] = None
            tile_positions[position] = tile_number
            position = empty_spot


def draw_tile(position, number):
    """Draw tile with number."""
    turtle.penup()
    turtle.goto(position.x, position.y)
    turtle.pendown()

    turtle.color('black', 'white')
    turtle.begin_fill()
    for _ in range(4):
        turtle.forward(99)
        turtle.left(90)
    turtle.end_fill()

    if number is None:
        return
    elif number < 10:
        turtle.forward(20)

    turtle.write(number, font=('Arial', 60, 'normal'))


def handle_click(x, y):
    """Swap tile with empty space if adjacent."""
    x = floor(x, 100)
    y = floor(y, 100)
    clicked_position = vector(x, y)

    for move in possible_moves:
        adjacent_position = clicked_position + move

        if adjacent_position in tile_positions and tile_positions[adjacent_position] is None:
            tile_number = tile_positions[clicked_position]
            tile_positions[adjacent_position] = tile_number
            draw_tile(adjacent_position, tile_number)
            tile_positions[clicked_position] = None
            draw_tile(clicked_position, None)


def draw_all_tiles():
    """Draw all tiles."""
    for position, number in tile_positions.items():
        draw_tile(position, number)
    turtle.update()


# Initialize game
screen = Screen()
screen.setup(420, 420, 370, 0)
turtle.hideturtle()
turtle.tracer(False)
initialize_puzzle()
draw_all_tiles()
screen.onclick(handle_click)
turtle.done()

10. Maze Explorer

Rules: Navigate from one side to the other. Click to trace your path through the maze.


import random
import turtle
from turtle import Screen
from freegames import line


def draw_maze():
    """Generate random maze pattern."""
    turtle.color('black')
    turtle.width(5)

    for x in range(-200, 200, 40):
        for y in range(-200, 200, 40):
            if random.random() > 0.5:
                line(x, y, x + 40, y + 40)
            else:
                line(x, y + 40, x + 40, y)

    turtle.update()


def handle_click(x, y):
    """Draw path line and dot for screen tap."""
    if abs(x) > 198 or abs(y) > 198:
        turtle.penup()
    else:
        turtle.pendown()

    turtle.width(2)
    turtle.color('red')
    turtle.goto(x, y)
    turtle.dot(4)


# Initialize game
screen = Screen()
screen.setup(420, 420, 370, 0)
turtle.hideturtle()
turtle.tracer(False)
draw_maze()
screen.onclick(handle_click)
turtle.done()

Tags: Turtle Graphics game development python programming interactive applications educational games

Posted on Wed, 24 Jun 2026 16:41:46 +0000 by birdie