Learning Goals
3 minBy the end of this lesson you can:
- Stitch
input(), a list of strings, andrandom.choice()into a tiny interactive game. - Use a
while Trueloop withbreakto keep asking until the player wants to stop. - Recognise the "menu of replies" pattern — replace one list and the whole feel of the game changes.
Warm-Up
5 minYou've probably seen a Magic 8-Ball at a pasar malam toy stall — a plastic black ball with a little window. Shake it, ask a yes/no question, and a floating triangle gives you "It is certain", "Reply hazy try again", or "Don't count on it". We're going to build that ball in Python.
Predict-the-output. What might this print?
import random answers = ["yes", "no", "maybe"] print(random.choice(answers))
Show one possible answer
maybe
Could be any of the three. Run it again and it'll usually be different. That single line — random.choice(answers) — is the heart of today's game. The rest is dressing.
random.choice(list) (from PY-L1-19) picks one item from a list. while True: (from PY-L1-12) repeats forever. break jumps us out the moment a condition is met. That's the whole engine.
New Concept · Forever Loops with an Escape Hatch
10 minThe pattern · ask, answer, ask again
Most games we've built so far run once — ask one question, print one result, finished. A fortune-teller is different. You want to keep shaking the ball. The pattern is:
- Start a forever loop with
while True:. - Ask the player something inside the loop.
- If their answer is a magic stop word (like
"quit"),breakout. - Otherwise, do the game thing and loop back to step 2.
Skeleton in five lines
while True: question = input("Ask the ball: ") if question == "quit": break print("the answer goes here")
Read it aloud: "Forever: ask the ball; if they typed quit, stop; otherwise print an answer." That's the entire shape of any "keep going until they say stop" program.
while True over a counter?We don't know how many questions the player will ask — they decide, not us. A counted for i in range(10) would force exactly ten rounds, no more, no less. while True + break hands the steering wheel to the player.
Picking one answer at random
We met random.choice() last lesson. Give it a list, get back one item:
import random fortunes = ["Yes!", "No way lah", "Ask again later"] reply = random.choice(fortunes) print(reply)
Three answers is fine for a demo. A real Magic 8-Ball has 20. We'll use 15 in our version — enough variety that the game doesn't feel repetitive on the third question.
Worked Example · Build the 8-Ball
18 minWe'll grow this game in five phases. Type each phase, run it, then bolt on the next bit. Save the file as magic_8_ball.py.
Phase 1 · A single shake
Code
# magic_8_ball.py — Magic 8-Ball (Phase 1 of 5) import random fortunes = ["Definitely yes", "No way lah", "Ask again later"] question = input("Ask the magic ball: ") print(random.choice(fortunes))
Run it. Type any question. You should get one of three replies. Notice we never use question for anything yet — we just collect it so the player feels heard. The ball doesn't actually read minds, after all.
Phase 2 · A proper menu of 15 fortunes
Three replies gets old fast. Let's grow the list — five "yes"-ish, five "no"-ish, five "ask again later"-ish, with a Malaysian flavour.
Code (replace the old fortunes list)
fortunes = [ "Definitely yes", "Without a doubt, lah", "You can count on it", "Yes, absolutely", "Most likely yes", "No way lah", "Don't even think about it", "Very doubtful", "My sources say no", "Outlook not so good", "Ask again later", "Reply hazy, try later", "Cannot say for sure", "Concentrate and ask again", "The teh tarik is still brewing... wait a bit", ]
Fifteen items, one per line, with a trailing comma after each. The trailing comma after the last item is legal Python and makes adding a sixteenth fortune later painless. Run the program a few times and you should see different answers from across the list.
Phase 3 · Loop forever, until they say quit
Right now the program runs once and exits. Wrap the question-and-answer pair in a while True loop, and let quit be the escape hatch.
Code
# magic_8_ball.py — Magic 8-Ball (Phase 3 of 5) import random fortunes = [ "Definitely yes", "Without a doubt, lah", "You can count on it", "Yes, absolutely", "Most likely yes", "No way lah", "Don't even think about it", "Very doubtful", "My sources say no", "Outlook not so good", "Ask again later", "Reply hazy, try later", "Cannot say for sure", "Concentrate and ask again", "The teh tarik is still brewing... wait a bit", ] print("Welcome to the Magic 8-Ball. Type 'quit' to leave.") while True: question = input("\nAsk the ball: ") if question == "quit": break print("The ball says:", random.choice(fortunes)) print("Goodbye, seeker of truth.")
The \n inside the input prompt is just a hidden newline so the question stands apart from the previous answer — it makes the conversation easier to read. Run it. Ask three or four questions. Type quit to leave.
Phase 4 · A friendlier opening
Most real games greet you by name. Ask once, at the top, then use the name in every reply.
Code (add at the top, just under the imports)
name = input("What's your name? ") print("Welcome,", name + "! Ask the Magic 8-Ball a yes/no question.") print("Type 'quit' when you've heard enough.")
And tweak the reply line to use the name:
print(name + ", the ball says:", random.choice(fortunes))
Run it. The greeting and every reply should now use the player's name — much more personal.
Phase 5 · Count the questions asked
Let's show the player how many questions they asked before quitting. This is the running-counter pattern from PY-L1-12.
Full file
# magic_8_ball.py — Magic 8-Ball (Phase 5 of 5) import random fortunes = [ "Definitely yes", "Without a doubt, lah", "You can count on it", "Yes, absolutely", "Most likely yes", "No way lah", "Don't even think about it", "Very doubtful", "My sources say no", "Outlook not so good", "Ask again later", "Reply hazy, try later", "Cannot say for sure", "Concentrate and ask again", "The teh tarik is still brewing... wait a bit", ] name = input("What's your name? ") print("Welcome,", name + "! Ask the Magic 8-Ball a yes/no question.") print("Type 'quit' when you've heard enough.") asked = 0 while True: question = input("\nAsk the ball: ") if question == "quit": break asked = asked + 1 print(name + ", the ball says:", random.choice(fortunes)) print() print("You asked", asked, "questions tonight.") print("Goodbye,", name + " — may your fortunes be kind.")
One sample run (lines you type are bold):
Output
What's your name? <strong>Hafiz</strong> Welcome, Hafiz! Ask the Magic 8-Ball a yes/no question. Type 'quit' when you've heard enough. Ask the ball: <strong>Will I pass my Bahasa Melayu test?</strong> Hafiz, the ball says: Most likely yes Ask the ball: <strong>Should I eat nasi lemak for breakfast?</strong> Hafiz, the ball says: Without a doubt, lah Ask the ball: <strong>quit</strong> You asked 2 questions tonight. Goodbye, Hafiz — may your fortunes be kind.
What every earlier lesson contributed
- L1-07 ·
input— collecting the player's name and questions. - L1-08 ·
if— checking whether they typed the magic"quit"word. - L1-12 ·
while+ counter — the forever loop and theaskedtally. - L1-15 · lists — the 15 fortunes live in
fortunes. - L1-19 ·
random.choice— picks one fortune from the list each turn.
Try It Yourself · Extend the Ball
13 minPick at least two of these and add them to magic_8_ball.py. Run after each change.
Right now, typing QUIT or Quit doesn't end the game — only the lowercase quit does. Use the string method .lower() when checking the quit condition so any capitalisation works.
Hint
question = input("\nAsk the ball: ") if question.lower() == "quit": break
question.lower() returns a new lowercased copy — it doesn't change question itself. Compare the lowered copy to "quit" and you cover quit, QUIT, Quit, even qUiT.
If the player just presses Enter without typing anything, question becomes the empty string "". Skip that turn — print "The ball needs a question, lah." and loop back to the top without picking a fortune or counting it.
Hint
Add an extra elif before the fortune print:
if question.lower() == "quit": break elif question == "": print("The ball needs a question, lah.") else: asked = asked + 1 print(name + ", the ball says:", random.choice(fortunes))
The empty branch doesn't increment asked — only real questions count toward the tally.
Split fortunes into three separate lists: yes_replies, no_replies, maybe_replies. Each turn, first pick a category with random.randint(1, 3), then pick a fortune from that category's list. The ball will feel more balanced — exactly one-third yes, one-third no, one-third maybe.
Hint
yes_replies = ["Definitely yes", "Without a doubt, lah", "You can count on it", "Yes, absolutely", "Most likely yes"] no_replies = ["No way lah", "Don't even think about it", "Very doubtful", "My sources say no", "Outlook not so good"] maybe_replies = ["Ask again later", "Reply hazy, try later", "Cannot say for sure", "Concentrate and ask again", "The teh tarik is still brewing... wait a bit"] mood = random.randint(1, 3) if mood == 1: reply = random.choice(yes_replies) elif mood == 2: reply = random.choice(no_replies) else: reply = random.choice(maybe_replies) print(name + ", the ball says:", reply)
This is "random within random" — first the category, then the line. It guarantees a fair mix instead of leaving everything up to one big shuffled list.
Mini-Challenge · The Mood Ring
8 minPriya wants to turn the 8-Ball into a mood ring for her younger sister. Same shape — ask, answer, repeat — but the answers describe a mood, not a fortune. Build a new file mood_ring.py based on the 8-ball:
- Replace the
fortuneslist with at least 10 moods like"curious","cheeky","chill like an ice kacang","hungry for satay","sleepy","buzzing", etc. Name the listmoods. - Change the prompt to:
"Touch the ring (press Enter), or type 'quit': ". - If the player types
quit,break. Otherwise — even if they typed something weird — pick a mood withrandom.choice(moods)and print"Your mood right now: <mood>". - Count the touches and print the total when they quit.
Stretch. Use random.randint(1, 3) to also pick a colour for the ring on each touch ("blue", "green", "purple"). Print both: "The ring glows <colour> — your mood is <mood>."
Show one possible solution
# mood_ring.py — a Magic 8-Ball-shaped mood ring import random moods = [ "curious", "cheeky", "chill like an ice kacang", "hungry for satay", "sleepy", "buzzing", "kepoh and ready for gossip", "moody", "warm like teh tarik", "ready for adventure", ] print("Welcome to the mood ring. Touch it as often as you like.") touches = 0 while True: answer = input("\nTouch the ring (press Enter), or type 'quit': ") if answer.lower() == "quit": break touches = touches + 1 mood = random.choice(moods) print("Your mood right now:", mood) print() print("You touched the ring", touches, "times.")
Your moods can vary — ten or more, all strings. The non-negotiables are the while True loop, the break on quit, random.choice(moods) inside the loop, and a counter that only goes up when they don't quit. Don't put random.choice outside the loop or you'll get the same mood every touch.
Recap
3 minOne list of strings, one random.choice(), and a while True loop with a break — that's an entire category of game. Fortune-teller, mood ring, decision-maker, prompt-of-the-day: same skeleton, different list. The list is the personality of the game.
Vocabulary Card
while True:- A loop with no built-in stop condition. Repeats forever — you have to
breakout yourself. break- Jumps out of the nearest loop immediately. The line just after the loop runs next.
random.choice(list)- Returns one item from a list, picked at random. Different (probably) every call.
- menu-of-replies pattern
- Storing all possible outputs in a list, then picking one with
random.choice. Swap the list, swap the game.
Homework
4 minCreate a new file called study_buddy.py — a tiny "what should I do right now?" helper for revision week.
Rules:
- Build a list called
tipswith at least 12 study tips, e.g."Re-read the last chapter","Do five past-year questions","Take a 10-minute walk around the taman","Drink a glass of water","Teach the topic to your cat". import randomat the top.- Ask the player for their name once, then enter a
while Trueloop. Each turn, ask"Press Enter for a tip, or type 'done': ". - If they type
done(any capitalisation — use.lower()),break. - Otherwise, pick a tip with
random.choice(tips)and print it as"Try this, <name>: <tip>". - Track how many tips you gave with a counter; print the total when they leave.
Bring study_buddy.py next class — we'll meet our second random-list game in PY-L1-21.
Sample · study_buddy.py
# study_buddy.py — random revision-week tips import random tips = [ "Re-read the last chapter slowly.", "Do five past-year questions on the hardest topic.", "Take a 10-minute walk around the taman.", "Drink a glass of water and stretch your back.", "Teach the topic to your cat (or your little brother).", "Make a one-page mind map from memory.", "Set a 25-minute timer and just start.", "Quiz yourself with flashcards, no peeking.", "Eat a piece of fruit, not kuih.", "Write three exam-style questions and answer them.", "Sleep 30 minutes earlier tonight.", "Explain today's lesson out loud to a mirror.", ] name = input("What's your name? ") print("Hi", name + "! I'm your study buddy. I'll give you a tip whenever you want.") given = 0 while True: answer = input("\nPress Enter for a tip, or type 'done': ") if answer.lower() == "done": break given = given + 1 print("Try this,", name + ":", random.choice(tips)) print() print("Good work,", name + ". I gave you", given, "tips today.")
Your tips can be wholly different — 12 or more is the only rule. The non-negotiables are: import random, a tips list of strings, a while True loop, a case-insensitive done check with break, random.choice(tips) inside the loop, and a counter that ticks up only when a tip is actually given. random.choice must live inside the loop — outside it, you'd get the same tip every time.