The random Module in Python: controlled unpredictability
Randomness makes a game feel alive and a simulation realistic. The random module supplies it — and seed() lets you make that randomness repeatable when you need to.
The big idea
random produces pseudo-random values; seeding it with random.seed(n) makes the exact sequence reproducible.
See it in code
random.randint(a, b) returns an integer in [a, b]. The catch that makes it usable: random.seed(n) pins the sequence, so a 'random' run becomes exactly reproducible:
import random
random.seed(42)
print(random.randint(1, 100))
print(random.randint(1, 100))82 15
Seed with 42 and you'll always get 82 then 15. That reproducibility is what lets you debug something 'random' at all.
Now use randomness to perturb a value. Each enemy sits on a clean grid at 60 + col * 90; adding a random jitter nudges it off that perfect line:
import random
random.seed(42)
for col in range(5):
jitter = random.randint(-15, 15)
print("enemy x:", 60 + col * 90 + jitter)enemy x: 65 enemy x: 138 enemy x: 225 enemy x: 338 enemy x: 413
The grid x's would be 60, 150, 240, 330, 420; each is nudged by a jitter of +5, -12, -15, +8, -7. Seeded with 42, these are the exact numbers the formation below uses for its first row.
Now apply that jitter to a real formation. A perfect grid looks robotic; a random nudge on each enemy makes it feel hand-placed. We seed first — same as the warm-up — so the run is reproducible, which matters when a random bug only shows up sometimes:
import random
from game import Stage, Enemy
screen = Stage.create()
Stage.clear(screen)
random.seed(42)
for row in range(2):
for col in range(5):
jitter = random.randint(-15, 15)
Enemy(60 + col * 90 + jitter, 80 + row * 70).draw(screen)
It's the warm-up's jitter, now across two rows of sprites. Because we seeded with 42, the 'random' offsets are identical every run. That's the trick: seed during development so you can reproduce and debug, then remove the seed (or seed from the clock) to ship real variety.
Reproducible randomness is core to serious work: shuffle a dataset, sample rows, run a Monte Carlo simulation, generate test data. random.choice, random.shuffle, and random.random() cover most needs — and a fixed seed turns a flaky, non-repeatable run into one you can study step by step.
Try it yourself
Change the seed to 7 and note the whole formation shifts, but stays fixed across runs. Then remove random.seed(42) entirely and run twice — now each run differs. Try random.choice([...]) to pick a random enemy type.
The common mistake
Expecting cryptographic-grade secrecy from random. It's pseudo-random — fine for games and simulations, but predictable enough that you must never use it for passwords or keys. For security, Python has the separate secrets module.
What it unlocks
Controlled randomness builds on imports and feeds generative art and reproducible backtesting.