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()