Learning Goals
3 minBy the end of this lesson you can:
- Build a list of ten cat Actors, each with a random starting x position and speed.
- Move all cats left-to-right and wrap them using a single
update(dt)loop. - Extend the game by adding a new mechanic (vertical bobbing or a score counter).
Warm-Up · Random Speeds
5 minIn Level 1 you used random.randint to pick a number. What does this print?
import random speeds = [] for i in range(3): speeds.append(random.randint(80, 200)) print(speeds)
Show the answer
A list of three random integers between 80 and 200, for example [134, 97, 188]. You will get different numbers every run — that is the point.
New Concept · Storing Per-Actor Data
12 minEach cat needs its own speed. An Actor object lets you attach any custom attribute you like — just assign it:
cat = Actor("alien") cat.speed = random.randint(80, 250) # custom attribute
Now cat.speed is stored on that specific Actor and nowhere else. In the update loop you use c.speed to move each cat by its own value:
def update(dt): for c in cats: c.x += c.speed * dt if c.x > WIDTH: c.x = 0
No second list needed — the data lives on the Actor itself.
Scatter the starting positions
If all cats start at x = 0 they form a clump. Scatter them with a random starting x:
cat.x = random.randint(0, WIDTH) cat.y = random.randint(50, HEIGHT - 50)
Why it matters
Attaching custom data to an Actor is a lightweight alternative to a full Python class. For simple games this is all you need. When games grow more complex (Pygame module), we promote these attributes into a proper class.
Worked Example · Cat Parade
12 minBuilding the full game
Save this as cat_parade.py. We use "alien" as a stand-in — rename the file to cat.png in your images/ folder to use a real cat sprite.
# cat_parade.py — ten cats, random speeds import pgzrun import random WIDTH = 600 HEIGHT = 400 TITLE = "Cat Parade" cats = [] for i in range(10): c = Actor("alien") c.x = random.randint(0, WIDTH) c.y = random.randint(50, HEIGHT - 50) c.speed = random.randint(60, 220) cats.append(c) def draw(): screen.fill("lemonchiffon") for c in cats: c.draw() screen.draw.text( f"{len(cats)} cats", topleft=(10, 10), fontsize=24, color="black", ) def update(dt): for c in cats: c.x += c.speed * dt if c.x > WIDTH: c.x = 0 pgzrun.go()
What you will see
Every run looks slightly different because random.randint picks new values each time. That is what makes it feel alive.
Try It Yourself
13 minChange the number of cats to 20. Then change the speed range to randint(30, 100) for a slower parade. Run it and compare the feel.
Hint
for i in range(20): # was 10 c = Actor("alien") c.x = random.randint(0, WIDTH) c.y = random.randint(50, HEIGHT - 50) c.speed = random.randint(30, 100) # slower cats.append(c)
Give each cat a c.base_y equal to its starting y. In update(dt), use import math and set c.y = c.base_y + math.sin(c.x / 40) * 15. The cats will wave up and down as they walk.
Hint
import math # in setup: c.base_y = c.y # in update(dt): c.y = c.base_y + math.sin(c.x / 40) * 15
Mini-Challenge — Add a Mechanic
8 minPick one mechanic to add to your Cat Parade. Each combines today's list / per-actor data with an earlier concept:
- Score counter — add a global
score = 0. Each time a cat wraps (its x exceeds WIDTH), add 1 to the score. Display it withscreen.draw.text. - Colour change on wrap — give each cat a
c.colourattribute set to one of a list of colour names. On wrap, cycle to the next colour and draw a filled circle at the cat's position instead of the sprite. - Speed up over time — use
clock.schedule_intervalto call a function every 5 seconds that increases every cat's speed by 20.
It works if…
the chosen mechanic is visible and responds to the cats' movement
Show the score-counter solution
# cat_parade_score.py — cats + score counter import pgzrun import random WIDTH = 600 HEIGHT = 400 TITLE = "Cat Parade" score = 0 cats = [] for i in range(10): c = Actor("alien") c.x = random.randint(0, WIDTH) c.y = random.randint(50, HEIGHT - 50) c.speed = random.randint(60, 220) cats.append(c) def draw(): screen.fill("lemonchiffon") for c in cats: c.draw() screen.draw.text(f"Score: {score}", topleft=(10, 10), fontsize=28, color="black") def update(dt): global score for c in cats: c.x += c.speed * dt if c.x > WIDTH: c.x = 0 score += 1 pgzrun.go()
The score increments every time any cat wraps. Your mechanic choice and implementation details will vary.
Recap
3 minYou built a ten-sprite game in under 30 lines by combining lists, random numbers, and time-based movement. Attaching custom attributes (like c.speed) directly to an Actor keeps each sprite's data in one place. One loop in update and one in draw handle them all.
Vocabulary Card
- custom Actor attribute
- Any variable attached to an Actor object with dot notation:
actor.speed = 120. Useful for per-sprite data. - random.randint(a, b)
- Returns a random integer between a and b (inclusive). Used to scatter positions and speeds.
- Parade pattern
- Build a list → attach per-actor data → one update loop → one draw loop. The backbone of most mini-games.
Homework
4 minSwap the cats for a different theme. Create roti_race.py with eight roti canai sprites (or any stand-in image), each at a random y position and random speed between 50 and 300 px/s. They should wrap when they pass the right edge. Display the count on screen. Bring a screenshot to the next class.
Stretch. Add the score counter from the Mini-Challenge so each wrap adds a point. Display the score in a contrasting colour at the top right.
Sample · roti_race.py
# roti_race.py — eight roti sprites, random speeds import pgzrun import random WIDTH = 600 HEIGHT = 400 TITLE = "Roti Race" rotis = [] for i in range(8): r = Actor("alien") r.x = random.randint(0, WIDTH) r.y = random.randint(40, HEIGHT - 40) r.speed = random.randint(50, 300) rotis.append(r) def draw(): screen.fill("saddlebrown") for r in rotis: r.draw() screen.draw.text( f"{len(rotis)} rotis", topleft=(10, 10), fontsize=24, color="white", ) def update(dt): for r in rotis: r.x += r.speed * dt if r.x > WIDTH: r.x = 0 pgzrun.go()
Your theme, image name, and colours will differ. The key: eight actors in a list, each with a .speed attribute, updated with dt.