Learning Goals
3 minBy the end of this lesson you can:
- Model a bingo card as a set, and the words the caller has shouted as another set.
- Check for a win with one line:
card <= called. - Use
random.sample()to deal a unique card from a bigger word bank.
Warm-Up
5 minImagine a tiny bingo card with five words and a caller who's already shouted six.
card = {"satay", "milo", "cendol", "kopi", "roti"} called = {"milo", "ipoh", "satay", "kopi", "cendol", "roti"}
Has the card won? Don't loop. Use one set operator.
print(card <= called)
Show the answer
True
Every word on the card has been called. card <= called reads as "is card a subset of called?". Yes — every one of the five words is in the six that have been shouted. Bingo!
A winning bingo card is just a subset of the called words. The whole game logic for "has the player won?" is one line.
New Concept · The Game in Sets
14 minThe three sets
Word Bingo is three sets and one loop:
WORD_BANK— the big pool of words. The caller draws from here.card— the player's five-or-so words. A subset of the bank.called— every word shouted so far. Grows by one per turn.
The game ends when card <= called turns True.
Dealing a fair card · random.sample()
A bingo card must have no duplicates. random.sample(bank, k) picks k unique items from a collection. It returns a list — wrap it in set().
import random WORD_BANK = ["satay", "milo", "cendol", "kopi", "roti", "ipoh", "kuala lumpur", "penang", "bandung", "rambutan", "durian", "kuih"] card = set(random.sample(WORD_BANK, 5)) print("Your card:", card)
Run the file a few times — you'll see five fresh words each run.
random.choice(seq) picks ONE item. random.sample(seq, k) picks k unique items. random.shuffle(lst) reorders a list in place. We'll go deep on all of these in PY-L2-18.
The caller
The caller shouts one new word from the bank each turn — but never the same word twice. That's easy if we remove from a shuffled copy:
pool = list(WORD_BANK) random.shuffle(pool) # pool[0] is the first call, pool[1] the second, etc.
Putting it together — one round
Loop through the shuffled pool, adding each word to called. After every call, check the subset.
called = set() turn = 0 for word in pool: turn += 1 called.add(word) print("Call", turn, ":", word) if card <= called: print("BINGO! Won on turn", turn) break
card <= called is the entire win check. No loops, no flags, no counting.
What if you want progress?
To show how many of the card's words have come up so far — intersection counts:
hits = card & called print("Matches so far:", len(hits), "/", len(card)) print("Still need :", card - called)
Three set operators do the job that would otherwise need three loops.
Worked Example · One Full Round
12 minSave this complete file as bingo.py and run it a few times.
Code
# bingo.py — one full round of Word Bingo import random WORD_BANK = [ "satay", "milo", "cendol", "kopi", "roti canai", "ipoh", "kuala lumpur", "penang", "bandung", "rambutan", "durian", "kuih", "rendang", "asam", "nasi lemak", "teh tarik", "char kway teow", ] # Deal a 5-word card and shuffle the pool card = set(random.sample(WORD_BANK, 5)) pool = list(WORD_BANK) random.shuffle(pool) print("Your card:") for w in card: print(" ★", w) print() called = set() turn = 0 for word in pool: turn += 1 called.add(word) print("Turn", turn, "→", word) hits = card & called print(" Matches:", len(hits), "/", len(card)) if card <= called: print() print("BINGO! Won on turn", turn, "✦") break # After the loop — final report print() print("Final called list:", called) print("Words on the card you never needed beyond the win:", called - card)
Sample output (yours will vary)
Your card:
★ satay
★ kuih
★ teh tarik
★ rambutan
★ kopi
Turn 1 → durian
Matches: 0 / 5
Turn 2 → kopi
Matches: 1 / 5
Turn 3 → asam
Matches: 1 / 5
Turn 4 → satay
Matches: 2 / 5
Turn 5 → rambutan
Matches: 3 / 5
Turn 6 → ipoh
Matches: 3 / 5
Turn 7 → cendol
Matches: 3 / 5
Turn 8 → teh tarik
Matches: 4 / 5
Turn 9 → nasi lemak
Matches: 4 / 5
Turn 10 → kuih
Matches: 5 / 5
BINGO! Won on turn 10 ✦
Final called list: {'durian', 'kopi', 'asam', 'satay', 'rambutan', 'ipoh', 'cendol', 'teh tarik', 'nasi lemak', 'kuih'}
Words on the card you never needed beyond the win: {'durian', 'asam', 'ipoh', 'cendol', 'nasi lemak'}card & called for the live match count. card <= called for the win check. called - card for the "noise" words the caller shouted that you didn't need. Each line replaces a tiny loop you'd otherwise write by hand.
Try It Yourself
13 minThree short exercises. Type each in a fresh file or as additions to bingo.py.
Change the card size from 5 to 8. Run the round. How many turns did it take to win?
Hint
card = set(random.sample(WORD_BANK, 8))
Bigger card = longer game (you need every one of those 8 called). Try the round a few times — the turn count should drift up.
Deal two cards from the same bank. After every call, report which players (if any) have won. The first to win, wins the round.
Hint
card_a = set(random.sample(WORD_BANK, 5)) card_b = set(random.sample(WORD_BANK, 5)) called = set() for turn, word in enumerate(pool, start=1): called.add(word) if card_a <= called and card_b <= called: print("Tie on turn", turn) break if card_a <= called: print("A wins on turn", turn) break if card_b <= called: print("B wins on turn", turn) break
enumerate(pool, start=1) gives you the turn number and the word at the same time — handy when you don't want to track a counter by hand.
Before the round starts, print how many words the two cards have in common, and how many are unique to A.
Hint
shared = card_a & card_b unique_a = card_a - card_b print("Cards share :", len(shared), "word(s):", shared) print("Only on A :", unique_a)
Two set operators, two lines. Imagine writing those checks with nested loops over lists; you'd need ten.
Mini-Challenge · Live Bingo with Real Calls
8 minBuild live_bingo.py. Instead of Python shouting the words, you type each call yourself. Useful for actually playing at the table.
Your file must:
- Define a
WORD_BANKof at least 15 words. - Deal a 5-word card with
random.sampleand print it. - In a
while True:loop, ask the user to type the next called word. Add it tocalled. - After each call, print
Matches: N / 5andStill need: <set>. - If
card <= called, printBINGO!andbreak. - If the user types
q,breakearly.
Stretch goal. Reject calls that aren't in the word bank — if word not in WORD_BANK: print("Not in the bank — try again."); continue. This catches typos.
Show one possible solution
# live_bingo.py — you type the calls import random WORD_BANK = { "satay", "milo", "cendol", "kopi", "roti canai", "ipoh", "penang", "rambutan", "durian", "kuih", "rendang", "asam", "nasi lemak", "teh tarik", "char kway teow", "bandung", } card = set(random.sample(list(WORD_BANK), 5)) print("Your card:") for w in card: print(" ★", w) called = set() while True: print() word = input("Caller shouted (q to quit): ").lower().strip() if word == "q": print("Stopped.") break if word not in WORD_BANK: print("Not in the bank — try again.") continue called.add(word) print("Matches :", len(card & called), "/", len(card)) print("Still need :", card - called) if card <= called: print() print("BINGO! ✦ Called", len(called), "word(s).") break
Non-negotiables: card dealt with random.sample, a while True loop that reads user input, the card <= called win check, and the two helper prints using & and -. The bank-check guard makes typos non-fatal.
Recap
3 minWord Bingo is what set operators look like in a real game. The card and the called-words list are sets; the win is a subset check (card <= called); the progress line is the intersection (card & called); the "still need" line is the difference (card - called). Three operators replace what would otherwise be three small loops.
Vocabulary Card
- random.sample(seq, k)
- Picks
kunique items at random from a list. Returns a list. - card <= called
- Subset check: is every word on the card already called? The whole win condition.
- card & called
- Intersection: how many of the card's words have come up?
- card - called
- Difference: which of the card's words are still missing?
Homework
4 minBuild bingo_stats.py. Simulate 100 rounds of Bingo and report the average number of turns it takes to win.
Your file must:
- Define
WORD_BANKwith at least 20 words. - Wrap a single-round function:
play_one()that deals a 5-word card, shuffles the pool, and returns the turn number on which the player wins. - Call
play_one()100 times. Track every turn-count in a list. - Print the minimum, maximum and average — using
min,maxandsum / len.
Stretch. Try the same simulation with cards of 3, 5, 7 and 10 words. Print the averages side by side. Bigger card = longer game.
Sample · bingo_stats.py
# bingo_stats.py — simulate 100 rounds, report averages import random WORD_BANK = [ "satay", "milo", "cendol", "kopi", "roti canai", "ipoh", "kuala lumpur", "penang", "bandung", "rambutan", "durian", "kuih", "rendang", "asam", "nasi lemak", "teh tarik", "char kway teow", "laksa", "sambal", "kaya", ] def play_one(card_size): card = set(random.sample(WORD_BANK, card_size)) pool = list(WORD_BANK) random.shuffle(pool) called = set() for turn, word in enumerate(pool, start=1): called.add(word) if card <= called: return turn def average_turns(card_size, rounds=100): results = [] for _ in range(rounds): results.append(play_one(card_size)) return min(results), max(results), sum(results) / len(results) lo, hi, avg = average_turns(5) print("Card 5 → min", lo, "max", hi, "avg", round(avg, 1)) # Stretch — try several sizes for size in [3, 5, 7, 10]: lo, hi, avg = average_turns(size) print("Card", size, "→ min", lo, "max", hi, "avg", round(avg, 1))
Non-negotiables: a play_one function that returns the winning turn number, a 100-round simulation list, and the min/max/sum/len report. round(x, 1) tidies the printed average. The trend should be very clear: bigger cards take more turns.