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 = {"hot dog", "cocoa", "ice cream", "coffee", "pancake"} called = {"cocoa", "Lima", "hot dog", "coffee", "ice cream", "pancake"}
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 = ["hot dog", "cocoa", "ice cream", "coffee", "pancake", "Lima", "Tokyo", "Cairo", "strawberry milk", "plum", "melon", "muffin"] 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 = [ "hot dog", "cocoa", "ice cream", "coffee", "pancake", "Lima", "Tokyo", "Cairo", "strawberry milk", "plum", "melon", "muffin", "beef stew", "lemon", "pizza", "hot chocolate", "fried rice", ] # 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:
★ hot dog
★ muffin
★ hot chocolate
★ plum
★ coffee
Turn 1 → melon
Matches: 0 / 5
Turn 2 → coffee
Matches: 1 / 5
Turn 3 → lemon
Matches: 1 / 5
Turn 4 → hot dog
Matches: 2 / 5
Turn 5 → plum
Matches: 3 / 5
Turn 6 → Lima
Matches: 3 / 5
Turn 7 → ice cream
Matches: 3 / 5
Turn 8 → hot chocolate
Matches: 4 / 5
Turn 9 → pizza
Matches: 4 / 5
Turn 10 → muffin
Matches: 5 / 5
BINGO! Won on turn 10 ✦
Final called list: {'melon', 'coffee', 'lemon', 'hot dog', 'plum', 'Lima', 'ice cream', 'hot chocolate', 'pizza', 'muffin'}
Words on the card you never needed beyond the win: {'melon', 'lemon', 'Lima', 'ice cream', 'pizza'}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.
Basic
5 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.
Challenge 1
4 minDeal 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.
Challenge 2
4 minBefore 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.
Challenge 3 · 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 = { "hot dog", "cocoa", "ice cream", "coffee", "pancake", "Lima", "Cairo", "plum", "melon", "muffin", "beef stew", "lemon", "pizza", "hot chocolate", "fried rice", "strawberry milk", } 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?
Extra Mission
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 = [ "hot dog", "cocoa", "ice cream", "coffee", "pancake", "Lima", "Tokyo", "Cairo", "strawberry milk", "plum", "melon", "muffin", "beef stew", "lemon", "pizza", "hot chocolate", "fried rice", "soup", "hot sauce", "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.