Build Bagels: instructions

Follow the steps in order. Your teacher codes each module live. Use the module map and spec to know what to build, and run it after every step.

The game and the big picture

The computer thinks of a secret number with no repeated digits. You guess, and it gives clues.

ClueMeaning
FermiA digit is correct and in the right position.
PicoA digit is in the secret, but in the wrong position.
BagelsNo digit is correct.

Example: secret 248, guess 843 gives Fermi Pico.

How the modules fit together

main() play_once() repeat until "no" make_secret()builds the secret ask_guess()reads + validates get_clues()scores a guess NUM_DIGITS, MAX_GUESSES (shared settings)

Colour key: blue = control flow, green = creates data, amber = user input, purple = pure function (no input or print).

Method

Build one small module, test it, then move on. Never write the next module until the last one works.

1Settings

Goal

Store the game settings in one place and import the tool we need.

Module spec

Needsrandom (imported at the top)
Constant 1NUM_DIGITS = 3: length of the secret
Constant 2MAX_GUESSES = 10: tries allowed

Module map

NUM_DIGITS = 3Length of the secret. Max 10, because there are only ten distinct digits. MAX_GUESSES = 10How many tries the player gets.

Change one number and the whole game adapts: no other line needs editing.

Key ideas

Check it works

Run the file. Nothing happens and there is no error.

Live-coding prompts: Why are these at the top, not inside a function? What would break if we typed 3 everywhere instead?

2Make the secret

Goal

Create a secret with no repeated digits.

Module spec

Namemake_secret
Inputn, how many digits (default: NUM_DIGITS)
Returnsa string of n different digits, e.g. '248'
Toolslist of the ten digits, shuffle, slice, join

Module map

1. digits = list('0123456789')
0
1
2
3
4
5
6
7
8
9
↓ random.shuffle(digits)
2. shuffled in place (example)
2
4
8
5
0
9
1
7
3
6
↓ digits[:n] then ''.join(...)
3. returns a string
'248'

Because each digit appears once in the shuffled list, repeats are impossible.

Key ideas

Check it works

Call it twice and print both. Expect two different 3-digit strings, such as 581 and 027, and never a repeat like 118.

Live-coding prompts: Predict the output before running. What does slicing [:3] keep? What if n were 10?

3Give clues

Goal

Compare a guess with the secret and return the right clue text.

Module spec

Nameget_clues
Inputsguess and secret (strings of the same length)
ReturnsYou got it!, Bagels, or clues joined by spaces
RulesSame digit, same place: Fermi. In the secret, wrong place: Pico. Nothing: Bagels. Clues sorted alphabetically.
Must notprint or ask for input (a pure function)

Module map

iguess[i]secret[i]Same spot?In secret?Clue
082noyesPico
144yes-Fermi
238nononone

clues = ['Pico', 'Fermi'] → clues.sort() → ['Fermi', 'Pico'] → "Fermi Pico". Sorting stops the order from revealing which position was right.

for i in range(len(guess)) guess[i]==secret[i]? Yes append 'Fermi' No guess[i] in secret? Yes append 'Pico' No add nothing

Two shortcuts around the loop: guess == secret returns "You got it!" immediately, and an empty clue list returns "Bagels".

Key ideas

Check it works
guesssecretExpected result
843248Fermi Pico
248248You got it!
159248Bagels
284248Fermi Pico Pico
Live-coding prompts: Trace 843 vs 248 on the board, position by position, before writing any code. Ask: what if there are no clues at all?

4Ask for a guess

Goal

Keep asking until the player types exactly the right number of digits.

Module spec

Nameask_guess
Inputnum_guesses, used only in the prompt text
Returnsa valid guess: exactly NUM_DIGITS digits, trimmed of spaces
RuleRepeat while the length is wrong or it is not all digits

Module map

guess = '' len ≠ NUM_DIGITSor not isdecimal() Yes print Guess #nguess = input('> ').strip() ask again No (valid) return guess

.strip() removes stray spaces, and isdecimal() rejects letters like "ab3".

Key ideas

Check it works

Type abc, then 12, then 123. The first two are rejected; 123 is returned.

Live-coding prompts: Why does the loop use or and not and? Try 12a: which half of the test catches it?

5Play one game

Goal

Connect the three helpers into one complete round.

Module spec

Nameplay_once
Inputnone
Returnsnothing (it prints)
Usesmake_secret, ask_guess, get_clues
Ends whenthe guess is correct, or guesses run out (then reveal the answer)

Module map

secret = make_secret()print intro; num_guesses = 1 num_guesses≤ MAX_GUESSES ? No return Yes guess = ask_guess(num_guesses) clues = get_clues(guess, secret)print(clues); num_guesses += 1 guess == secret ? Yes break No num_guesses> MAX_GUESSES ? Yes print "ran out"+ the answer return No next guess

Key ideas

Check it works

Play a full game twice: win once, and lose once. On the losing game the answer must be shown after guess #10.

Live-coding prompts: To test, temporarily print the secret. Where must the counter go up, before or after the check for running out?

6Main and play again

Goal

Show the rules once, replay on request, and only start when run directly.

Module spec

Namemain
Inputnone
Doesprints the rules, loops play_once(), asks to play again, says thanks
Entry guardthe game starts only when the file is run directly, not when imported

Module map

run the script __name__ == '__main__' ? No imported:nothing runs Yes main()print title + rules play_once() print 'play again?'answer = input('> ') starts with 'y' ? Yes No print 'Thanks for playing!'

The guard means importing this file (for testing) won't start a game.

Key ideas

Check it works

Finish a game, answer yes: a new game starts. Answer no: it says thanks and stops.

Live-coding prompts: What happens if we remove the guard and import this file from another one?

Common errors

Message or symptomUsual cause
IndentationErrorLines inside a function or loop need the same indent (4 spaces).
NameErrorA name is misspelled, or something was never imported or defined.
SyntaxError near a def, if, for or while lineMissing colon at the end of the line.
IndexError: string index out of rangeGuess and secret have different lengths.
Game never endsThe counter is never increased.
Secret has repeated digitsShuffle all ten digits and slice, instead of picking random digits one by one.
Debugging habit

Read the last line of the error first, then the line number above it. Add a temporary print to see what a variable really holds.

Challenges

  1. Easy: set the digits to 4, then 5. Does everything still work?
  2. Easy: set the guesses to 5 for a hard mode.
  3. Medium: print how many guesses are left after each wrong guess.
  4. Medium: keep a score of games won and print it on quit.
  5. Hard: let the player choose the number of digits at the start.
  6. Hard: reject guesses with repeated digits (hint: a set removes duplicates).

Check your understanding

  1. Why does the clue module not print or ask for input?
  2. What would happen if the clues were not sorted?
  3. What is the job of the entry guard?