From 15d5f7134de69358c391b739a4eee3703785fb79 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Sun, 4 Oct 2026 12:37:41 +0800 Subject: [PATCH] Step 2 Replacing blanks with guesses --- README.md | 6 +++-- src/hangman/constants.py | 48 +++++++++++++++++++++------------------- src/hangman/game.py | 35 ++++++++++++++++++++++++++++- src/hangman/main.py | 24 ++++++++++++++------ tests/test_game.py | 18 ++++++++++++++- tests/test_main.py | 15 +++++++++---- 6 files changed, 108 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 12685aa..101294c 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,16 @@ A beginner-friendly Python program that lets you play Hangman in the terminal ag Day 7 of Udemy's *100 Days of Code™: The Complete Python Pro Bootcamp*. The game is built in five steps that follow the course: -1. Picking a random word and checking answers *(this step)* -2. Replacing blanks with guesses +1. Picking a random word and checking answers +2. Replacing blanks with guesses *(this step)* 3. Checking if the player has won 4. Keeping track of the player's lives 5. Improving the user experience **Step 1:** the program picks a random word, asks for one letter and tells you whether the letter is in the word. +**Step 2:** the word is shown as blanks (`_ _ _ _ _`). After your guess every matching blank is replaced by the letter. + ## 📋 Requirements - Python 3.13 or newer diff --git a/src/hangman/constants.py b/src/hangman/constants.py index d6f803b..45b6fd7 100644 --- a/src/hangman/constants.py +++ b/src/hangman/constants.py @@ -1,23 +1,25 @@ -"""! -@file constants.py -@brief Constants shared across the Hangman game. -""" - -## Words the computer picks from. -WORD_LIST = [ - "aardvark", - "baboon", - "camel", - "dolphin", - "elephant", - "flamingo", - "giraffe", - "hedgehog", - "iguana", - "kangaroo", - "leopard", - "octopus", - "penguin", - "squirrel", - "tortoise", -] +"""! +@file constants.py +@brief Constants shared across the Hangman game. +""" + +## Character shown for a letter that has not been guessed yet. +BLANK = "_" +## Words the computer picks from. +WORD_LIST = [ + "aardvark", + "baboon", + "camel", + "dolphin", + "elephant", + "flamingo", + "giraffe", + "hedgehog", + "iguana", + "kangaroo", + "leopard", + "octopus", + "penguin", + "squirrel", + "tortoise", +] diff --git a/src/hangman/game.py b/src/hangman/game.py index bd5c017..514ffa3 100644 --- a/src/hangman/game.py +++ b/src/hangman/game.py @@ -5,7 +5,7 @@ import random -from hangman.constants import WORD_LIST +from hangman.constants import BLANK, WORD_LIST def choose_word(words: list[str] | None = None) -> str: @@ -25,3 +25,36 @@ def is_in_word(word: str, letter: str) -> bool: @return True if @p letter is in @p word. """ return letter.lower() in word.lower() + + +class Game: + """! + @brief State of one Hangman game. + """ + + def __init__(self, word: str) -> None: + """! + @brief Start a new game. + @param word The secret word. + """ + ## The secret word. + self.word = word.lower() + ## Letters guessed so far. + self.guesses: set[str] = set() + + def guess(self, letter: str) -> bool: + """! + @brief Process a guess and reveal the letter if it is in the word. + @param letter A single letter. + @return True if the letter is in the word. + """ + letter = letter.lower() + self.guesses.add(letter) + return is_in_word(self.word, letter) + + def display(self) -> str: + """! + @brief The word with unguessed letters replaced by blanks. + @return For example `"p _ n g _ i n"`. + """ + return " ".join(c if c in self.guesses else BLANK for c in self.word) diff --git a/src/hangman/main.py b/src/hangman/main.py index 12421e3..1fe1f25 100644 --- a/src/hangman/main.py +++ b/src/hangman/main.py @@ -3,19 +3,29 @@ @brief Command-line interface for Hangman. """ -from hangman.game import choose_word, is_in_word +from hangman.game import Game, choose_word + + +def say(message: str = "") -> None: + """! + @brief Show a message to the player; all output goes through here. + @param message The text to show. + """ + print(message) def main() -> None: """! - @brief Entry point: pick a random word and check one guessed letter. + @brief Entry point: show the blanks, take one guess and show the result. """ - word = choose_word() - guess = input("Guess a letter: ").strip() - if is_in_word(word, guess): - print(f"Right, '{guess.lower()}' is in the word.") + game = Game(choose_word()) + say(game.display()) + letter = input("Guess a letter: ").strip() + if game.guess(letter): + say(f"Right, '{letter.lower()}' is in the word.") else: - print(f"Wrong, '{guess.lower()}' is not in the word.") + say(f"Wrong, '{letter.lower()}' is not in the word.") + say(game.display()) if __name__ == "__main__": diff --git a/tests/test_game.py b/tests/test_game.py index d1b9108..b345956 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -1,5 +1,5 @@ from hangman.constants import WORD_LIST -from hangman.game import choose_word, is_in_word +from hangman.game import Game, choose_word, is_in_word def test_choose_word_from_list(): @@ -14,3 +14,19 @@ def test_letter_in_word(): def test_letter_not_in_word(): assert not is_in_word("camel", "z") + + +def test_display_starts_blank(): + assert Game("cat").display() == "_ _ _" + + +def test_correct_guess_reveals_letters(): + g = Game("banana") + assert g.guess("a") is True + assert g.display() == "_ a _ a _ a" + + +def test_wrong_guess_keeps_blanks(): + g = Game("cat") + assert g.guess("z") is False + assert g.display() == "_ _ _" diff --git a/tests/test_main.py b/tests/test_main.py index d787840..e7f7ff6 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -8,9 +8,16 @@ def run(monkeypatch, capsys, word, letter): return capsys.readouterr().out -def test_right_guess(monkeypatch, capsys): - assert "Right" in run(monkeypatch, capsys, "camel", "a") +def test_right_guess_fills_blanks(monkeypatch, capsys): + out = run(monkeypatch, capsys, "camel", "a") + assert out.splitlines() == [ + "_ _ _ _ _", + "Right, 'a' is in the word.", + "_ a _ _ _", + ] -def test_wrong_guess(monkeypatch, capsys): - assert "Wrong" in run(monkeypatch, capsys, "camel", "z") +def test_wrong_guess_keeps_blanks(monkeypatch, capsys): + out = run(monkeypatch, capsys, "camel", "z") + assert "Wrong" in out + assert out.splitlines()[-1] == "_ _ _ _ _"