Learning Goals
3 minBy the end of this lesson you can:
- Make multiple turtle objects with
turtle.Turtle(). - Position each one individually with
penup,gotoandpendown. - Move all of them in a turn-based loop, advancing by random amounts each tick.
- Detect who crossed the finish line first and announce the winner.
Warm-Up · Many Turtles
5 minSo far you've used the module-level turtle — turtle.forward(10) moves "the" turtle. Real games need many. Build a Turtle object explicitly:
import turtle red = turtle.Turtle(shape="turtle") red.color("red") red.penup(); red.goto(-200, 50); red.pendown() blue = turtle.Turtle(shape="turtle") blue.color("blue") blue.penup(); blue.goto(-200, -50); blue.pendown() red.forward(100) blue.forward(150) turtle.done()
Each named turtle responds to the same commands. red.forward(100) moves only red; blue.forward(150) only blue. We'll build a list of four and loop them.
Each turtle is an object with its own state — position, heading, colour, pen. Many objects = many independent characters on screen.
New Concept · Building the Race
14 minMake a list of racers
import turtle, random COLOURS = ["red", "blue", "green", "purple"] START_X = -250 racers = [] for i, colour in enumerate(COLOURS): r = turtle.Turtle(shape="turtle") r.color(colour) r.penup() r.goto(START_X, 60 - i * 40) # each lane 40 pixels apart r.pendown() racers.append(r)
Each turtle goes into a list so we can loop through them in the race code.
Draw the finish line
Use a separate "invisible" turtle to draw the finish line without affecting the racers.
marker = turtle.Turtle() marker.hideturtle() marker.penup() marker.goto(200, -100) marker.pendown() marker.setheading(90) marker.forward(200)
The marker turtle is just a one-off helper. hideturtle() removes its cursor so it doesn't show up at the finish line.
The race loop
Each tick, every racer sprints by a random amount. The first one whose x-position passes the finish line wins.
FINISH = 200 winner = None while winner is None: for r in racers: r.forward(random.randint(1, 10)) if r.xcor() >= FINISH: winner = r break # Announce marker.write(f"{winner.color()[0]} wins!", font=("Arial", 18, "bold")) turtle.done()
r.xcor() returns the turtle's current x-coordinate. We compare it to FINISH every step.
Why break?
The moment one turtle crosses, we stop the inner loop. Without the break, the other racers would keep moving and the winner could be overwritten. Always break the moment you detect a winner.
The same shape, every multi-turtle game
1. Create a list of turtle objects (the racers). 2. Position them on the start line. 3. Optional helper turtle for static drawing (finish line). 4. Loop until win condition. 5. Each tick, move each turtle a little. 6. Check the win condition after each move. 7. Break out, announce, turtle.done().
Worked Example · The Full Race
12 minSave as race.py:
# race.py — four-turtle race with a bet import turtle, random COLOURS = ["red", "blue", "green", "purple"] START_X = -250 FINISH = 200 # 1 — the bet print("Welcome to Turtle Race!") print("Racers:", ", ".join(COLOURS)) pick = "" while pick not in COLOURS: pick = input("Bet on which turtle? ").strip().lower() # 2 — set up the screen screen = turtle.Screen() screen.setup(width=600, height=400) screen.bgcolor("ivory") screen.title("Turtle Race") # 3 — finish line marker = turtle.Turtle() marker.hideturtle() marker.penup() marker.goto(FINISH, -120) marker.pendown() marker.setheading(90) marker.forward(240) marker.penup() # 4 — the racers racers = [] for i, colour in enumerate(COLOURS): r = turtle.Turtle(shape="turtle") r.color(colour) r.penup() r.goto(START_X, 75 - i * 50) r.pendown() racers.append(r) # 5 — race! winner = None while winner is None: for r in racers: r.forward(random.randint(1, 10)) if r.xcor() >= FINISH: winner = r break # 6 — announce winner_colour = winner.color()[0] marker.goto(-200, -150) marker.write(f"WINNER: {winner_colour}!", font=("Arial", 22, "bold")) marker.goto(-200, -180) verdict = "🎉 You called it!" if winner_colour == pick else f"You bet on {pick}. Better luck next time!" marker.write(verdict, font=("Arial", 14, "italic")) turtle.done()
What you'll see
A 600×400 ivory window, four lanes with coloured turtles, a black finish line on the right. The turtles sprint in random bursts. The first to cross is announced bottom-left. Your bet shows whether you called it.
Read the diff
Three new turtle concepts beyond drawing. (1) Screen object — turtle.Screen() lets you set window size, background, and title. (2) Object methods — r.forward(), r.xcor(), r.color() all act on individual turtles. (3) Multiple turtles in a list — loop with for r in racers. This is the same shape as "list of dicts" from PY-L2-11, but each item is a turtle object instead of a dict.
Try It Yourself
13 minExpand COLOURS to six. Adjust the lane spacing so they don't overlap.
Hint
COLOURS = ["red", "blue", "green", "purple", "orange", "magenta"] # In the setup loop: r.goto(START_X, 125 - i * 50)
Six lanes need bigger spread — start at y=125 and step down by 50, so the lowest lane is at y=-125.
One of your racers is the favourite — it moves by random.randint(2, 12) instead of 1-10. Pick which one and run a few races to confirm it usually wins.
Hint
def step(racer): if racer.color()[0] == "red": return random.randint(2, 12) # favourite return random.randint(1, 10) # In the race loop: for r in racers: r.forward(step(r)) ...
You've replaced one constant with a function. The same shape — many small steps — but now the range depends on which racer you ask.
Wrap the race in a function. Run it 100 times without any user input or animation (screen.tracer(0)). Tally how many times each colour wins. Print the leaderboard.
Hint
from collections import Counter screen = turtle.Screen() screen.tracer(0) # skip ALL animation — instant def race_once(): # ... build racers (and reset them between runs), race, return winner colour ... pass wins = Counter() for _ in range(100): wins[race_once()] += 1 print(wins.most_common())
screen.tracer(0) turns animation off — useful for batch simulation. Counter from collections is a special dict that counts occurrences (we'll formalise it in Level 3).
Mini-Challenge · Best-of-5 Race Series
8 minBuild race_series.py. Race five times in a row. Between races, reset the racers to the start line. Keep a tally of each colour's wins and announce the series winner at the end.
Use the "build helpers, drive with a loop" pattern. Two functions:
reset_racers(racers)— moves them back toSTART_X.race_once(racers)— runs one race, returns the winner's colour.
Show one possible solution (key snippets)
# race_series.py — best-of-5 series import turtle, random COLOURS = ["red", "blue", "green", "purple"] START_X = -250 FINISH = 200 def make_racers(): racers = [] for i, c in enumerate(COLOURS): r = turtle.Turtle(shape="turtle") r.color(c) r.penup(); r.goto(START_X, 75 - i * 50); r.pendown() racers.append(r) return racers def reset(racers): for r in racers: r.penup(); r.goto(START_X, r.ycor()); r.pendown() def race_once(racers): winner = None while winner is None: for r in racers: r.forward(random.randint(1, 10)) if r.xcor() >= FINISH: winner = r break return winner.color()[0] racers = make_racers() score = {c: 0 for c in COLOURS} for r in range(5): if r > 0: reset(racers) winner = race_once(racers) score[winner] += 1 print(f"Race {r + 1}: {winner} scores: {score}") champion = max(score, key=score.get) print(f"\n🏆 Series champion: {champion}") turtle.done()
Non-negotiables: make_racers + reset + race_once as three reusable functions, a {colour: 0} score dict, and the max(score, key=score.get) trick from PY-L2-19. Five races, no copy-paste.
Recap
3 minA turtle game with multiple characters needs multiple turtle objects. turtle.Turtle() creates one; store many in a list. Each one keeps its own position, heading, colour and pen. The race loop is the same shape every multi-actor game uses: while not finished, for each actor advance a little, then check the win condition. turtle.Screen() controls the window itself — size, background, title — and screen.tracer(0) turns off animation for batch simulation.
Vocabulary Card
- turtle.Turtle()
- Constructor for a brand-new turtle object. Each one has its own state.
- turtle.Screen()
- The window itself. Set size, background, title.
- screen.tracer(0)
- Skip animation — for simulations where you don't need to watch the turtles move.
- r.xcor() / r.ycor()
- Return the turtle's current x or y coordinate.
Homework
4 minAdd three features to your race:
- Names. Give each turtle a name ("Speedy", "Lightning", etc.) and label each lane with the name using
marker.write. - Step count. Count how many ticks the race took. Print it after.
- Replay. After the race finishes, ask "Race again? (y/n)". If yes, reset and run another. Track win counts across races.
Stretch. Save lifetime wins to turtle_wins.txt using the file-write pattern from PY-L2-22. Load on start.
Sample · key features
NAMES = ["Speedy", "Lightning", "Comet", "Dash"] # ... when building racers ... for i, (name, colour) in enumerate(zip(NAMES, COLOURS)): r = turtle.Turtle(shape="turtle") r.color(colour) r.penup(); r.goto(START_X - 50, 75 - i * 50); r.pendown() label = turtle.Turtle(); label.hideturtle(); label.color(colour) label.penup(); label.goto(START_X - 90, 65 - i * 50); label.pendown() label.write(name, font=("Arial", 12, "bold")) racers.append((name, r)) ticks = 0 winner = None while winner is None: ticks += 1 for name, r in racers: r.forward(random.randint(1, 10)) if r.xcor() >= FINISH: winner = name break print(f"{winner} wins in {ticks} ticks.")
Non-negotiables: names alongside colours (use zip for the pair), a ticks counter, and a replay loop. The label-turtle trick is a clean way to write text on screen without affecting the racers.