Learning Goals
3 minBy the end of this lesson you can:
- Pick a secret integer with
random.randint(1, 100). - Convert a user's text input into a number, safely — and handle the case where they typed nonsense.
- Loop until the guess matches, counting the number of tries it took.
Warm-Up
5 minYou've met everything we need. random.randint from PY-L1-41, int() conversion from PY-L1-07, a while loop from PY-L1-12, and comparisons from PY-L1-09. Today we glue them together.
Predict the output. What does this print?
secret = 42 guess = 30 tries = 1 while guess != secret: if guess < secret: print("higher") else: print("lower") guess = guess + 5 tries = tries + 1 print("Got it in", tries, "tries.")
Show the answer
higher higher higher Got it in 4 tries.
Starting at 30, the loop adds 5 each round: 35, 40, then 45. Wait — 45 is above 42, so the loop runs once more printing "lower"... Actually no — the loop condition checks before each iteration. After guess = 45, we go back to the top, see 45 != 42, enter the body again, print "lower", set guess = 50, tries = 5...
Whoops — the prediction above was for an idealised version. The real output is:
higher higher higher lower lower lower ... (forever, because 5 doesn't hit 42)
Lesson learnt: an integer step of 5 from 30 will never land exactly on 42. The condition guess != secret never becomes false. This is exactly the bug we'll avoid today by reading the player's guess instead of computing it.
Read guess → compare → print feedback → repeat. The player chooses the next guess based on the feedback. Computers can't play the guesser's side without a smarter algorithm (binary search — that's Level 3).
New Concept · Safe Input & The Core Loop
14 minPick the secret
import random secret = random.randint(1, 100)
Both ends inclusive — the secret can be 1, 100, or anything in between. From PY-L1-41 — same function we used for dragon damage.
Read a guess, safely
Naive code:
guess = int(input("Guess (1-100): "))
One line, but it crashes if the user types banana instead of a number — Python raises ValueError and the whole program dies. We don't know try/except yet (that's Level 2), so the simplest defence is to check the input is numeric before converting:
raw = input("Guess (1-100): ").strip() if not raw.isdigit(): print("Please type a whole number.") continue guess = int(raw)
str.isdigit() returns True only if every character is a digit. It does the validation; int() does the conversion. Two steps, but the program never crashes on bad input.
.isdigit() doesn't like negatives
Small caveat: "-5".isdigit() is False because the minus sign isn't a digit. Since our valid range is 1-100, that's fine — negatives are invalid anyway. For Number Guesser, isdigit is exactly the right check.
The core loop
tries = 0 while True: raw = input("Guess (1-100): ").strip() if not raw.isdigit(): print("Whole numbers only, please.") continue guess = int(raw) tries = tries + 1 if guess < secret: print("⬆️ Higher.") elif guess > secret: print("⬇️ Lower.") else: print("🎉 Got it in", tries, "tries!") break
while True with a break on the success branch. continue on invalid input. tries counts only the valid guesses. Three little helpers from PY-L1-12 and PY-L1-32 working together.
Why count tries inside the validation block, not before it?
Look closely — tries = tries + 1 happens after we've checked the input is a digit. Typing banana shouldn't cost the player a guess. This is the same fairness principle from PY-L1-36 (Hangman): only real attempts count.
Every guess-style game has the same skeleton: pick secret, loop { read input, validate, compare, react, maybe break }, then print a final summary. Today we build it; tomorrow we parameterise it.
Worked Example · Number Guesser v1
14 minThe full file
Save as guesser.py:
Code
# guesser.py — Number Guesser, Part 1 import random LOW = 1 HIGH = 100 print("=" * 40) print(" Number Guesser") print("=" * 40) print("I'm thinking of a number between", LOW, "and", HIGH, ".") print() secret = random.randint(LOW, HIGH) tries = 0 while True: raw = input("Your guess: ").strip() if not raw.isdigit(): print("Whole numbers only, please.") continue guess = int(raw) if guess < LOW or guess > HIGH: print("Out of range. Stick between", LOW, "and", HIGH, ".") continue tries = tries + 1 if guess < secret: print("⬆️ Higher.") elif guess > secret: print("⬇️ Lower.") else: print("🎉 Correct! Got it in", tries, "tries.") break print() print("Thanks for playing!")
Sample run
========================================
Number Guesser
========================================
I'm thinking of a number between 1 and 100 .
Your guess: 50
⬆️ Higher.
Your guess: 75
⬇️ Lower.
Your guess: 62
⬇️ Lower.
Your guess: 57
⬆️ Higher.
Your guess: 60
🎉 Correct! Got it in 5 tries.
Thanks for playing!Three things to notice
- Two
continues. One for non-digit input, one for out-of-range numbers. Both bounce back to the top of the loop without consuming a try. - Constants at the top.
LOW = 1andHIGH = 100in CAPS. Changing the range to 1-50 takes one edit. Tomorrow we'll make these depend on a difficulty choice. - The hint is binary. Just "higher" or "lower" — not "ten higher" or "close!". That keeps the game easy to extend (Part 2's difficulty) and means optimal play is about 7 guesses for 1-100 (think about why — half the range each time).
Tomorrow we add difficulty levels — three constants, three ranges, a menu. Lesson 47 stores best-three scores in a list (a leaderboard). The capstone adds the play-again loop, a name prompt, a colour or two, and a satisfying ending screen. None of that needs the core loop to change.
Try It Yourself
13 minThree tasks. Build the bones, then sharpen the edges.
Set secret = 42 (no random yet — easier to test). Loop forever reading int(input()) guesses. Print higher/lower/correct. Break on correct.
Hint
secret = 42 while True: guess = int(input("Guess: ")) if guess < secret: print("higher") elif guess > secret: print("lower") else: print("got it!") break
Try typing 50 (lower), 30 (higher), 42 (got it). If you typed 30 and got "higher" — perfect, that's the direction the player should go.
Replace secret = 42 with random.randint(1, 100). Add a tries = 0 before the loop and increment it inside the loop. Show the final count in the success message.
Hint
import random secret = random.randint(1, 100) tries = 0 while True: guess = int(input("Guess: ")) tries = tries + 1 if guess < secret: print("higher") elif guess > secret: print("lower") else: print("got it in", tries, "tries!") break
Play three rounds. Notice the score varies — somewhere between 1 (lucky) and ~10 (still solid). Optimal play for 1-100 is 7 tries with halving (binary search) — we'll formalise that in Level 3.
Right now int(input()) crashes if the player types a non-number. Wrap the input read in a check using .isdigit() and a continue. Then add a range check that bounces out-of-range numbers too. Neither bad input nor out-of-range should count toward tries.
Hint
while True: raw = input("Guess: ").strip() if not raw.isdigit(): print("Whole numbers only.") continue guess = int(raw) if guess < 1 or guess > 100: print("Stick to 1-100.") continue tries = tries + 1 # ... compare and react ...
Test it. Type banana, 0, 200, then a valid guess. Only the valid guess should bump tries. That's fairness baked into the game loop.
Mini-Challenge · Hana's Crash on Letters
8 minHana's Number Guesser crashes whenever she types something other than a number. Find and fix the bug.
# hana_guesser.py — buggy
import random
secret = random.randint(1, 100)
tries = 0
while True:
guess = int(input("Guess: ")) # crashes on "banana"
tries = tries + 1
if guess < secret:
print("higher")
elif guess > secret:
print("lower")
else:
print("got it in", tries, "!")
break- Bug. Direct
int(input(...))with no validation. Any non-numeric input becomes aValueErrorand the program dies.
Show the fix
while True: raw = input("Guess: ").strip() if not raw.isdigit(): print("Numbers only — try again.") continue guess = int(raw) tries = tries + 1 if guess < secret: print("higher") elif guess > secret: print("lower") else: print("got it in", tries, "!") break
The raw.strip().isdigit() two-step is the simplest validation gate in Level 1. It loses nothing we care about — only positive integers pass through, and that's exactly the input space we want. Negatives, decimals, and gibberish all bounce back politely with a continue.
Recap
3 minThe core Number Guesser is a tight while True loop with three branches inside: continue on invalid input (don't count it), continue on out-of-range numbers (don't count those either), and a comparison that prints "higher" / "lower" / "correct". The secret comes from random.randint(1, 100). The validation gate uses str.isdigit() to dodge the crash from int("banana"). Tomorrow we add difficulty levels — the player picks Easy / Medium / Hard and the range plus try-cap change to match. The core loop you wrote today doesn't change a line.
Vocabulary Card
str.isdigit()- Returns True if a string contains only digits (and is non-empty). Excludes negatives and decimals.
- validation gate
- A check at the top of a loop that
continues on bad input, so the rest of the iteration never sees garbage. - try counter
- An integer incremented once per valid attempt. Reported in the success message.
- constant
- A value (like
LOW = 1) defined once at the top of the file in CAPS. Easy to change in one place. - binary feedback
- A response that conveys exactly one bit of information — higher or lower. Just enough to play, no more.
Homework
4 minPolish guesser.py and bring it back tomorrow.
- Add a
HISTORYlist. Append every valid guess to it. After the win, printYour guesses: <history>. - Add a friendly "already guessed!" message if the player repeats a guess (use
in HISTORY). Repeats should not count towardtries. - Wrap the whole thing in
while True:with a "play again? (y/n)" prompt at the end. Remember to resetsecret,triesandHISTORYat the start of each round. - Bonus: print the number of guesses it "should" have taken using the binary-search benchmark — about
7for 1-100. (Just hardcode the comparison for now.)
Bring guesser.py next class. PY-L1-46 adds difficulty levels.
Sample · guesser.py with history & play-again
# guesser.py — Number Guesser with history & replay import random LOW = 1 HIGH = 100 PAR = 7 # roughly optimal for 1-100 print("=" * 40) print(" Number Guesser") print("=" * 40) while True: secret = random.randint(LOW, HIGH) tries = 0 history = [] while True: raw = input("Your guess: ").strip() if not raw.isdigit(): print("Whole numbers only.") continue guess = int(raw) if guess < LOW or guess > HIGH: print("Stick to", LOW, "-", HIGH, ".") continue if guess in history: print("Already guessed that — no penalty.") continue history.append(guess) tries = tries + 1 if guess < secret: print("⬆️ Higher.") elif guess > secret: print("⬇️ Lower.") else: print("🎉 Correct! Got it in", tries, "tries.") print("Your guesses:", history) if tries <= PAR: print("At or under par (" + str(PAR) + ") — great work!") else: print("Over par. Try halving the range next time.") break again = input("Play again? (y/n) ").strip().lower() if again != "y": print("Goodbye.") break
Three quiet but worthwhile habits in the sample. (1) Constants at the top (LOW, HIGH, PAR) — change once. (2) Reset all state (secret, tries, history) at the start of each outer iteration. (3) The repeat-guess check uses in history just like Hangman's repeat-letter check — same idea, different game. Patterns transfer.