Book-matched resources · Chapter 09 · Challenges 081–090

Games and Simulations

Try the printed challenge before revealing a hint or the example solution. Starter files are available only where the book supplies one.

Challenge hints & worked solutions

CHALLENGE 081 · BUILD IT

Guess the Number: Random Edition

Build a number-guessing game that chooses a new secret whole number from 1 to 20 each time it runs. Give the player six attempts, with a higher-or-lower hint after every valid wrong guess.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Store secret = random.randint(1, 20). Compare the player’s number against it on each turn. Break or return after a correct guess.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

# Challenge 81 - Guess the Number: Random Edition
import random

def feedback(guess, secret):
    if guess < secret:
        return "Higher!"
    if guess > secret:
        return "Lower!"
    return "Correct!"

def play():
    secret = random.randint(1, 20)
    for turn in range(1, 7):
        try:
            guess = int(input(f"Guess {turn}/6 (1-20): "))
        except ValueError:
            print("Enter a whole number. This guess is used.")
            continue
        if not 1 <= guess <= 20:
            print("Outside 1-20. This guess is used.")
            continue
        result = feedback(guess, secret)
        print(result)
        if result == "Correct!":
            return
    print(f"Out of guesses. The number was {secret}.")

if __name__ == "__main__":
    play()
Save this Python file ↓
CHALLENGE 082 · SOLVE IT

Flip-a-Coin Experiment

Investigate simulated coin flips. Ask how many flips to make, generate that number of heads-or-tails outcomes and report both counts and the percentage that were heads.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Use a loop and random.choice(("H", "T")). Heads + tails is an important invariant: it must equal the requested flips.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

# Challenge 82 - Flip-a-Coin Experiment
import random

def simulate(trials, rng=None):
    rng = rng if rng is not None else random
    heads = sum(rng.choice(("H", "T")) == "H"
                for _ in range(trials))
    return heads, trials - heads

def main():
    try:
        trials = int(input("How many flips (1-10000)? "))
    except ValueError:
        print("Enter a whole number.")
        return
    if not 1 <= trials <= 10000:
        print("Choose between 1 and 10000.")
        return
    heads, tails = simulate(trials)
    print(f"Heads: {heads} | Tails: {tails}")
    print(f"Heads percentage: {heads / trials * 100:.1f}%")

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 083 · CHALLENGE YOURSELF

Dice Battle

Create a two-player dice contest on one computer. Each player throws two six-sided dice. The player with the higher total wins the round; equal totals produce a tie.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Put the two-dice sum into a reusable roll_total function. Compare totals and handle a tie explicitly.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

# Challenge 83 - Dice Battle
import random

def roll_total(rng=None):
    rng = rng if rng is not None else random
    return rng.randint(1, 6) + rng.randint(1, 6)

def winner(a, b):
    return "Player 1" if a > b else "Player 2" if b > a else "Tie"

def main():
    while True:
        first, second = roll_total(), roll_total()
        print(f"Player 1: {first} | Player 2: {second}")
        print(f"Result: {winner(first, second)}")
        again = input("Play again? (y/n): ").strip().lower()
        if again != "y":
            print("Thanks for playing!")
            return

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 084 · CHALLENGE YOURSELF

Rock, Paper, Scissors League

Create a short league where a human player competes against the computer for a chosen number of rounds. Randomly select the computer’s move and keep a running tally.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Test all nine move pairings. A set of winning player/computer pairs can keep the result function short.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

# Challenge 84 - Rock, Paper, Scissors League
import random

MOVES = ("rock", "paper", "scissors")

def result(player, computer):
    if player == computer:
        return "draw"
    wins = {("rock", "scissors"), ("paper", "rock"),
            ("scissors", "paper")}
    return "player" if (player, computer) in wins else "computer"

def main():
    try:
        rounds = int(input("Number of rounds (1-10): "))
    except ValueError:
        print("Enter a whole number.")
        return
    if not 1 <= rounds <= 10:
        print("Choose 1 to 10 rounds.")
        return
    scores = {"player": 0, "computer": 0, "draw": 0}
    for turn in range(1, rounds + 1):
        player = input(f"Round {turn}, move: ").strip().lower()
        while player not in MOVES:
            player = input("Choose rock, paper or scissors: ").strip().lower()
        computer = random.choice(MOVES)
        outcome = result(player, computer)
        scores[outcome] += 1
        print(f"Computer: {computer} | Result: {outcome}")
    print(f"You: {scores['player']} | Computer: {scores['computer']}"
          f" | Draws: {scores['draw']}")

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 085 · DEBUG IT

Scramble the Word

The supplied word-scrambler shuffles a list of letters but prints the original word instead. Fix the logic, then turn it into a short guessing game using the practice words planet, garden or python.

Reveal a hint (from the printed book)

random.shuffle changes a list in place. The missing step is "".join(letters); never print the unchanged word as the scramble.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

# Challenge 85 - Scramble the Word
import random

def scramble(word, rng=None):
    rng = rng if rng is not None else random
    letters = list(word)
    for _ in range(10):
        rng.shuffle(letters)
        result = "".join(letters)
        if result != word:
            return result
    # Guarantee a change for the practice words below.
    return word[1:] + word[0]

def main():
    word = random.choice(("planet", "garden", "python"))
    scrambled = scramble(word)
    print(f"Scrambled: {scrambled}")
    guess = input("Original word? ").strip().lower()
    print("Correct!" if guess == word else f"It was {word}.")

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 086 · BUILD IT

Maths Sprint

Build a ten-question mental-maths sprint. Make each question from two random whole numbers between 0 and 12 using addition, subtraction or multiplication.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Use randint for operands and choice for the operator. Check for an integer answer inside try/except ValueError.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

# Challenge 86 - Maths Sprint
import random

def make_question(rng=None):
    rng = rng if rng is not None else random
    a, b = rng.randint(0, 12), rng.randint(0, 12)
    sign = rng.choice(("+", "-", "*"))
    if sign == "-" and a < b:
        a, b = b, a
    answer = a + b if sign == "+" else a - b if sign == "-" else a * b
    return f"{a} {sign} {b}", answer

def main():
    score = 0
    for number in range(1, 11):
        question, answer = make_question()
        entered = input(f"Q{number}: {question} = ").strip()
        try:
            correct = int(entered) == answer
        except ValueError:
            correct = False
        if correct:
            score += 1
        print("Correct!" if correct else f"Answer: {answer}")
    print(f"Final score: {score}/10")

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 087 · CHALLENGE YOURSELF

Choose Your Own Story

Build an interactive branching story with at least five different places and two different endings. Every displayed menu choice must lead to an existing place or an ending.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Use a dictionary where each scene maps to a description and two destination keys. Repeat a scene after invalid input.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

# Challenge 87 - Choose Your Own Story
# Each place offers two valid destinations.
SCENES = {
    "crossroads": ("A fork in the path.", ("forest", "bridge")),
    "forest": ("A hidden woodland path.", ("tower", "cellar")),
    "bridge": ("A creaking bridge.", ("cellar", "tower")),
    "tower": ("A tower with two staircases.", ("safe", "trapped")),
    "cellar": ("Two mysterious doors.", ("trapped", "safe")),
}
ENDINGS = {"safe": "You find your way home!",
           "trapped": "You must wait for rescue."}

def follow_path(choices):
    current = "crossroads"
    for choice in choices:
        if current in ENDINGS:
            break
        if choice not in (1, 2):
            raise ValueError("Choices must be 1 or 2")
        current = SCENES[current][1][choice - 1]
    return current

def play():
    while True:
        current = "crossroads"
        while current not in ENDINGS:
            description, destinations = SCENES[current]
            print(f"\n{description}")
            print(f"1: {destinations[0]} | 2: {destinations[1]}")
            answer = input("Choose 1 or 2: ").strip()
            if answer not in ("1", "2"):
                print("Choose 1 or 2.")
                continue
            current = destinations[int(answer) - 1]
        print(ENDINGS[current])
        if input("Restart? (y/n): ").strip().lower() != "y":
            return

if __name__ == "__main__":
    play()
Save this Python file ↓
CHALLENGE 088 · IMPROVE IT

Look After a Virtual Pet

Improve the unfinished virtual-pet simulation. Feeding, playing and resting change hunger, energy and happiness, but every value must stay between 0 and 100 even after many actions.

Reveal a hint (from the printed book)

A helper clamp(value) can return max(0, min(100, value)). Apply it after every permitted action.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

# Challenge 88 - Look After a Virtual Pet

def clamp(number):
    return max(0, min(100, number))

def update(state, action):
    changed = state.copy()
    changes = {
        "feed": (-25, -5, 5),
        "play": (15, -20, 20),
        "rest": (10, 30, -5),
    }
    if action not in changes:
        return changed
    for name, change in zip(("hunger", "energy", "happiness"),
                            changes[action]):
        changed[name] = clamp(changed[name] + change)
    return changed

def main():
    pet = {"hunger": 40, "energy": 70, "happiness": 65}
    while True:
        print(" | ".join(f"{key}: {value}" for key, value in pet.items()))
        answer = input("feed/play/rest/quit: ").strip().lower()
        if answer == "quit":
            print("See you next time!")
            return
        if answer not in ("feed", "play", "rest"):
            print("Choose a listed action.")
            continue
        pet = update(pet, answer)

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 089 · BUILD IT

Escape the Puzzle Room

Create a three-clue puzzle room. The exit must remain locked until all clues are solved, even if a player chooses the exit before completing the puzzles.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Use a dictionary of three Boolean flags. Only all(solved.values()) should allow the door to open.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

# Challenge 89 - Escape the Puzzle Room
# Three small puzzles; solve every clue to unlock the exit.
CLUES = {
    "1": ("Double each time: 2, 4, 8, 16, ?", "32"),
    "2": ("What is binary 0110 in decimal?", "6"),
    "3": ("Reverse EDOC to find the secret word.", "code"),
}

def unlocked(solved):
    return all(solved.values())

def main():
    solved = {number: False for number in CLUES}
    while True:
        print("Solved:", sum(solved.values()), "/3")
        choice = input("Clue 1/2/3, exit 4, quit q: ").strip().lower()
        if choice == "q":
            return
        if choice == "4":
            if unlocked(solved):
                print("The door unlocks. You escape!")
                return
            print("The door is still locked.")
            continue
        if choice not in CLUES:
            print("Choose 1, 2, 3, 4 or q.")
            continue
        if solved[choice]:
            print("Already solved!")
            continue
        prompt, expected = CLUES[choice]
        answer = input(prompt + " ").strip().casefold()
        if answer == expected:
            solved[choice] = True
            print("Clue solved!")
        else:
            print("Not yet. Try again.")

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 090 · CHALLENGE YOURSELF

The Mini Battle Arena

Create a turn-based battle game with a player and an opponent. Every turn the player attacks, heals or defends; if the opponent survives, it strikes back. Your simulation must always finish.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Write a function that handles one action and opponent response. Stop at either zero health or the fixed 12-turn limit.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

# Challenge 90 - The Mini Battle Arena
import random

MAX_HEALTH = 28
TURN_LIMIT = 12

def apply_action(player_hp, enemy_hp, action, rng=None):
    rng = rng if rng is not None else random
    defending = action == "defend"
    if action == "attack":
        enemy_hp = max(0, enemy_hp - rng.randint(4, 8))
    elif action == "heal":
        player_hp = min(MAX_HEALTH, player_hp + rng.randint(3, 6))
    if enemy_hp > 0:
        damage = rng.randint(2, 6)
        if defending:
            damage = damage // 2
        player_hp = max(0, player_hp - damage)
    return player_hp, enemy_hp

def main():
    player_hp, enemy_hp = MAX_HEALTH, 24
    for turn in range(1, TURN_LIMIT + 1):
        print(f"Turn {turn}: You {player_hp} | Opponent {enemy_hp}")
        action = input("attack/heal/defend: ").strip().lower()
        while action not in ("attack", "heal", "defend"):
            action = input("Choose attack, heal or defend: ").strip().lower()
        player_hp, enemy_hp = apply_action(player_hp, enemy_hp, action)
        if enemy_hp == 0:
            print("You win!")
            return
        if player_hp == 0:
            print("You lose!")
            return
    print("Turn limit reached: draw.")

if __name__ == "__main__":
    main()
Save this Python file ↓