step 4
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 4s

Keeping track of the player's lives
This commit is contained in:
2026-10-04 12:41:43 +08:00
parent ce87f23314
commit a76df46b79
6 changed files with 91 additions and 38 deletions
+4 -2
View File
@@ -18,8 +18,8 @@ Day 7 of Udemy's *100 Days of Code™: The Complete Python Pro Bootcamp*. The ga
1. Picking a random word and checking answers 1. Picking a random word and checking answers
2. Replacing blanks with guesses 2. Replacing blanks with guesses
3. Checking if the player has won *(this step)* 3. Checking if the player has won
4. Keeping track of the player's lives 4. Keeping track of the player's lives *(this step)*
5. Improving the user experience 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 1:** the program picks a random word, asks for one letter and tells you whether the letter is in the word.
@@ -28,6 +28,8 @@ Day 7 of Udemy's *100 Days of Code™: The Complete Python Pro Bootcamp*. The ga
**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.) **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.)
**Step 4:** you start with 6 lives. Every wrong guess costs one and shows the lives left. At 0 lives the game ends with "You lose" and reveals the word.
## 📋 Requirements ## 📋 Requirements
- Python 3.13 or newer - Python 3.13 or newer
+29 -25
View File
@@ -1,28 +1,32 @@
"""! """!
@file constants.py @file constants.py
@brief Constants shared across the Hangman game. @brief Constants shared across the Hangman game.
""" """
## Character shown for a letter that has not been guessed yet. ## Number of wrong guesses the player may make before losing.
BLANK = "_" MAX_LIVES = 6
## Words the computer picks from. ## Character shown for a letter that has not been guessed yet.
WORD_LIST = [ BLANK = "_"
"aardvark", ## Words the computer picks from.
"baboon", WORD_LIST = [
"camel", "aardvark",
"dolphin", "baboon",
"elephant", "camel",
"flamingo", "dolphin",
"giraffe", "elephant",
"hedgehog", "flamingo",
"iguana", "giraffe",
"kangaroo", "hedgehog",
"leopard", "iguana",
"octopus", "kangaroo",
"penguin", "leopard",
"squirrel", "octopus",
"tortoise", "penguin",
] "squirrel",
"tortoise",
]
## Message shown when the player wins. ## Message shown when the player wins.
MSG_WIN = "You win!" MSG_WIN = "You win!"
## Message shown when the player loses (the word is appended).
MSG_LOSE = "You lose. The word was: "
+24 -4
View File
@@ -5,7 +5,7 @@
import random import random
from hangman.constants import BLANK, WORD_LIST from hangman.constants import BLANK, MAX_LIVES, WORD_LIST
def choose_word(words: list[str] | None = None) -> str: def choose_word(words: list[str] | None = None) -> str:
@@ -32,25 +32,31 @@ class Game:
@brief State of one Hangman game. @brief State of one Hangman game.
""" """
def __init__(self, word: str) -> None: def __init__(self, word: str, lives: int = MAX_LIVES) -> None:
"""! """!
@brief Start a new game. @brief Start a new game.
@param word The secret word. @param word The secret word.
@param lives Wrong guesses allowed before losing.
""" """
## The secret word. ## The secret word.
self.word = word.lower() self.word = word.lower()
## Lives left.
self.lives = lives
## Letters guessed so far. ## Letters guessed so far.
self.guesses: set[str] = set() self.guesses: set[str] = set()
def guess(self, letter: str) -> bool: def guess(self, letter: str) -> bool:
"""! """!
@brief Process a guess and reveal the letter if it is in the word. @brief Process a guess; a wrong guess costs a life.
@param letter A single letter. @param letter A single letter.
@return True if the letter is in the word. @return True if the letter is in the word.
""" """
letter = letter.lower() letter = letter.lower()
self.guesses.add(letter) self.guesses.add(letter)
return is_in_word(self.word, letter) correct = is_in_word(self.word, letter)
if not correct:
self.lives -= 1
return correct
def display(self) -> str: def display(self) -> str:
"""! """!
@@ -65,3 +71,17 @@ class Game:
@brief True when every letter of the word has been guessed. @brief True when every letter of the word has been guessed.
""" """
return all(c in self.guesses for c in self.word) return all(c in self.guesses for c in self.word)
@property
def lost(self) -> bool:
"""!
@brief True when no lives are left.
"""
return self.lives <= 0
@property
def over(self) -> bool:
"""!
@brief True when the game has been won or lost.
"""
return self.won or self.lost
+5 -5
View File
@@ -3,7 +3,7 @@
@brief Command-line interface for Hangman. @brief Command-line interface for Hangman.
""" """
from hangman.constants import MSG_WIN from hangman.constants import MSG_LOSE, MSG_WIN
from hangman.game import Game, choose_word from hangman.game import Game, choose_word
@@ -17,18 +17,18 @@ def say(message: str = "") -> None:
def play(game: Game) -> None: def play(game: Game) -> None:
"""! """!
@brief Ask for letters until every blank is filled. @brief Ask for letters until the word is guessed or the lives run out.
@param game The game to play. @param game The game to play.
""" """
say(game.display()) say(game.display())
while not game.won: while not game.over:
letter = input("Guess a letter: ").strip() letter = input("Guess a letter: ").strip()
if game.guess(letter): if game.guess(letter):
say(f"Right, '{letter.lower()}' is in the word.") say(f"Right, '{letter.lower()}' is in the word.")
else: else:
say(f"Wrong, '{letter.lower()}' is not in the word.") say(f"Wrong, '{letter.lower()}' is not in the word. Lives left: {game.lives}")
say(game.display()) say(game.display())
say(MSG_WIN) say(MSG_WIN if game.won else MSG_LOSE + game.word)
def main() -> None: def main() -> None:
+22 -1
View File
@@ -1,4 +1,4 @@
from hangman.constants import WORD_LIST from hangman.constants import MAX_LIVES, WORD_LIST
from hangman.game import Game, choose_word, is_in_word from hangman.game import Game, choose_word, is_in_word
@@ -45,3 +45,24 @@ def test_wrong_guesses_do_not_win():
g = Game("hi") g = Game("hi")
g.guess("x") g.guess("x")
assert not g.won assert not g.won
def test_wrong_guess_loses_life():
g = Game("cat")
assert g.lives == MAX_LIVES
g.guess("z")
assert g.lives == MAX_LIVES - 1
def test_correct_guess_keeps_lives():
g = Game("cat")
g.guess("c")
assert g.lives == MAX_LIVES
def test_lost_when_out_of_lives():
g = Game("hi", lives=2)
g.guess("x")
assert not g.lost
g.guess("y")
assert g.lost and g.over and not g.won
+7 -1
View File
@@ -13,7 +13,7 @@ def test_play_until_won(monkeypatch, capsys):
out = run(monkeypatch, capsys, "hi", ["z", "h", "i"]) out = run(monkeypatch, capsys, "hi", ["z", "h", "i"])
assert out.splitlines() == [ assert out.splitlines() == [
"_ _", "_ _",
"Wrong, 'z' is not in the word.", "Wrong, 'z' is not in the word. Lives left: 5",
"_ _", "_ _",
"Right, 'h' is in the word.", "Right, 'h' is in the word.",
"h _", "h _",
@@ -26,3 +26,9 @@ def test_play_until_won(monkeypatch, capsys):
def test_repeated_letter_fills_all_blanks(monkeypatch, capsys): def test_repeated_letter_fills_all_blanks(monkeypatch, capsys):
out = run(monkeypatch, capsys, "aa", ["a"]) out = run(monkeypatch, capsys, "aa", ["a"])
assert out.splitlines()[-2:] == ["a a", "You win!"] assert out.splitlines()[-2:] == ["a a", "You win!"]
def test_play_until_lost(monkeypatch, capsys):
out = run(monkeypatch, capsys, "a", list("bcdefg"))
assert "Lives left: 0" in out
assert out.splitlines()[-1] == "You lose. The word was: a"