diff --git a/README.md b/README.md index 101294c..47af33c 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ 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 -2. Replacing blanks with guesses *(this step)* -3. Checking if the player has won +2. Replacing blanks with guesses +3. Checking if the player has won *(this step)* 4. Keeping track of the player's lives 5. Improving the user experience @@ -26,6 +26,8 @@ Day 7 of Udemy's *100 Days of Code™: The Complete Python Pro Bootcamp*. The ga **Step 2:** the word is shown as blanks (`_ _ _ _ _`). After your guess every matching blank is replaced by the letter. +**Step 3:** the game keeps asking for letters until every blank is filled, then prints "You win!". (There are no lives yet, so wrong guesses cost nothing.) + ## 📋 Requirements - Python 3.13 or newer diff --git a/src/hangman/constants.py b/src/hangman/constants.py index 45b6fd7..ba6fb20 100644 --- a/src/hangman/constants.py +++ b/src/hangman/constants.py @@ -23,3 +23,6 @@ WORD_LIST = [ "squirrel", "tortoise", ] + +## Message shown when the player wins. +MSG_WIN = "You win!" diff --git a/src/hangman/game.py b/src/hangman/game.py index 514ffa3..f8b43f9 100644 --- a/src/hangman/game.py +++ b/src/hangman/game.py @@ -58,3 +58,10 @@ class Game: @return For example `"p _ n g _ i n"`. """ return " ".join(c if c in self.guesses else BLANK for c in self.word) + + @property + def won(self) -> bool: + """! + @brief True when every letter of the word has been guessed. + """ + return all(c in self.guesses for c in self.word) diff --git a/src/hangman/main.py b/src/hangman/main.py index 1fe1f25..7d3ca49 100644 --- a/src/hangman/main.py +++ b/src/hangman/main.py @@ -3,6 +3,7 @@ @brief Command-line interface for Hangman. """ +from hangman.constants import MSG_WIN from hangman.game import Game, choose_word @@ -14,18 +15,27 @@ def say(message: str = "") -> None: print(message) +def play(game: Game) -> None: + """! + @brief Ask for letters until every blank is filled. + @param game The game to play. + """ + say(game.display()) + while not game.won: + letter = input("Guess a letter: ").strip() + if game.guess(letter): + say(f"Right, '{letter.lower()}' is in the word.") + else: + say(f"Wrong, '{letter.lower()}' is not in the word.") + say(game.display()) + say(MSG_WIN) + + def main() -> None: """! - @brief Entry point: show the blanks, take one guess and show the result. + @brief Entry point: start a game with a random 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: - say(f"Wrong, '{letter.lower()}' is not in the word.") - say(game.display()) + play(Game(choose_word())) if __name__ == "__main__": diff --git a/tests/test_game.py b/tests/test_game.py index b345956..9837fa7 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -30,3 +30,18 @@ def test_wrong_guess_keeps_blanks(): g = Game("cat") assert g.guess("z") is False assert g.display() == "_ _ _" + + +def test_not_won_until_all_letters_guessed(): + g = Game("hi") + assert not g.won + g.guess("h") + assert not g.won + g.guess("i") + assert g.won + + +def test_wrong_guesses_do_not_win(): + g = Game("hi") + g.guess("x") + assert not g.won diff --git a/tests/test_main.py b/tests/test_main.py index e7f7ff6..be33707 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,23 +1,28 @@ from hangman import main as cli +from hangman.game import Game -def run(monkeypatch, capsys, word, letter): - monkeypatch.setattr(cli, "choose_word", lambda: word) - monkeypatch.setattr("builtins.input", lambda _="": letter) - cli.main() +def run(monkeypatch, capsys, word, inputs): + it = iter(inputs) + monkeypatch.setattr("builtins.input", lambda _="": next(it)) + cli.play(Game(word)) return capsys.readouterr().out -def test_right_guess_fills_blanks(monkeypatch, capsys): - out = run(monkeypatch, capsys, "camel", "a") +def test_play_until_won(monkeypatch, capsys): + out = run(monkeypatch, capsys, "hi", ["z", "h", "i"]) assert out.splitlines() == [ - "_ _ _ _ _", - "Right, 'a' is in the word.", - "_ a _ _ _", + "_ _", + "Wrong, 'z' is not in the word.", + "_ _", + "Right, 'h' is in the word.", + "h _", + "Right, 'i' is in the word.", + "h i", + "You win!", ] -def test_wrong_guess_keeps_blanks(monkeypatch, capsys): - out = run(monkeypatch, capsys, "camel", "z") - assert "Wrong" in out - assert out.splitlines()[-1] == "_ _ _ _ _" +def test_repeated_letter_fills_all_blanks(monkeypatch, capsys): + out = run(monkeypatch, capsys, "aa", ["a"]) + assert out.splitlines()[-2:] == ["a a", "You win!"]