Add number guessing game project scaffold
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s

- Add guess_the_number package with set_difficulty, check_answer and
  play_game, plus constants.py (range, attempts, ASCII logo)
- Add pytest tests for difficulty, feedback and full game flow
- Add pyproject.toml (Python >= 3.13, no runtime dependencies)
- Add docs/Doxyfile and Doxygen comments in the source
- Rewrite README with venv setup, run, test and docs instructions
- Ignore docs/build/ in .gitignore
This commit is contained in:
2026-10-04 22:28:35 +08:00
parent 3c058e90a6
commit 9a08450354
9 changed files with 3292 additions and 1 deletions
+1
View File
@@ -174,3 +174,4 @@ cython_debug/
# PyPI configuration file # PyPI configuration file
.pypirc .pypirc
docs/build/
+76 -1
View File
@@ -1,2 +1,77 @@
# 012-guess_the_number # Guess The Number
A terminal Number Guessing Game, built as the day 12 project of Udemy's
*100 Days of Code™: The Complete Python Pro Bootcamp*.
The computer picks a number between 1 and 100. Choose a difficulty and guess it:
| Level | Attempts |
|-------|----------|
| easy | 10 |
| hard | 5 |
After each guess you are told whether it was too high or too low.
## Requirements
- Python 3.13 or newer
- No runtime dependencies (`pytest` is only needed for the tests)
## Setup
Create and activate a local virtual environment, then upgrade pip.
Windows (PowerShell):
```powershell
python -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
```
Linux / macOS:
```bash
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
```
## Run the game
```bash
python -m pip install -e .
python -m guess_the_number
```
## Run the tests
```bash
python -m pip install -e ".[dev]"
python -m pytest
```
## Documentation
Source comments use Doxygen. With [Doxygen](https://www.doxygen.nl/) installed:
```bash
doxygen docs/Doxyfile
```
The HTML output is written to `docs/build/html`.
## Project layout
```
src/guess_the_number/ game code (constants.py, game.py)
tests/ pytest tests
docs/ Doxyfile
```
The functions `set_difficulty`, `check_answer` and `play_game` follow the
course assignment's naming.
## License
See [LICENSE](LICENSE).
+3016
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "guess-the-number"
version = "0.1.0"
description = "Number Guessing Game - Udemy 100 Days of Code, day 12."
readme = "README.md"
requires-python = ">=3.13"
license = { file = "LICENSE" }
dependencies = []
[project.optional-dependencies]
dev = ["pytest>=8"]
[project.scripts]
guess-the-number = "guess_the_number.game:play_game"
[tool.setuptools.packages.find]
where = ["src"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
+6
View File
@@ -0,0 +1,6 @@
## @file __init__.py
# @brief Number Guessing Game package.
from guess_the_number.game import check_answer, play_game, set_difficulty
__all__ = ["check_answer", "play_game", "set_difficulty"]
+7
View File
@@ -0,0 +1,7 @@
## @file __main__.py
# @brief Entry point: `python -m guess_the_number`.
from guess_the_number.game import play_game
if __name__ == "__main__":
play_game()
+27
View File
@@ -0,0 +1,27 @@
## @file constants.py
# @brief Constants used by the Number Guessing Game.
## Lowest number that can be picked.
MIN_NUMBER = 1
## Highest number that can be picked.
MAX_NUMBER = 100
## Attempts available on the easy level.
EASY_ATTEMPTS = 10
## Attempts available on the hard level.
HARD_ATTEMPTS = 5
## Accepted answers for the easy level.
EASY_CHOICE = "easy"
## Accepted answers for the hard level.
HARD_CHOICE = "hard"
## ASCII art title shown when the game starts.
LOGO = r"""
________ ___________.__ _______ ___.
/ _____/ __ __ ____ ______ ______ \__ ___/| |__ ____ \ \ __ __ _____\_ |__ ___________
/ \ ___| | \_/ __ \ / ___// ___/ | | | | \_/ __ \ / | \| | \/ \| __ \_/ __ \_ __ \
\ \_\ \ | /\ ___/ \___ \ \___ \ | | | Y \ ___/ / | \ | / Y Y \ \_\ \ ___/| | \/
\______ /____/ \___ >____ >____ > |____| |___| /\___ > \____|__ /____/|__|_| /___ /\___ >__|
\/ \/ \/ \/ \/ \/ \/ \/ \/ \/
"""
+94
View File
@@ -0,0 +1,94 @@
## @file game.py
# @brief Game logic for the Number Guessing Game.
import random
from guess_the_number.constants import (
EASY_ATTEMPTS,
EASY_CHOICE,
HARD_ATTEMPTS,
HARD_CHOICE,
LOGO,
MAX_NUMBER,
MIN_NUMBER,
)
def set_difficulty(level: str) -> int:
"""!
@brief Convert a difficulty level into a number of attempts.
@param level Either "easy" or "hard" (case-insensitive).
@return 10 attempts for easy, 5 for hard.
@throws ValueError If the level is not recognised.
"""
level = level.strip().lower()
if level == EASY_CHOICE:
return EASY_ATTEMPTS
if level == HARD_CHOICE:
return HARD_ATTEMPTS
raise ValueError(f"Unknown difficulty: {level!r}")
def check_answer(guess: int, answer: int, attempts: int) -> int:
"""!
@brief Compare a guess with the answer and print feedback.
@param guess The player's guess.
@param answer The number to find.
@param attempts Attempts left before this guess.
@return Attempts left after this guess (unchanged if the guess is correct).
"""
if guess > answer:
print("Too high.")
return attempts - 1
if guess < answer:
print("Too low.")
return attempts - 1
print(f"You got it! The answer was {answer}.")
return attempts
def _ask_difficulty() -> int:
"""!
@brief Prompt until the player picks a valid difficulty.
@return The number of attempts for the chosen level.
"""
while True:
try:
return set_difficulty(input(f"Choose a difficulty. Type '{EASY_CHOICE}' or '{HARD_CHOICE}': "))
except ValueError:
print("Please type 'easy' or 'hard'.")
def _ask_guess() -> int:
"""!
@brief Prompt until the player enters a whole number in range.
@return The guess.
"""
while True:
raw = input("Make a guess: ").strip()
if raw.lstrip("-").isdigit() and MIN_NUMBER <= int(raw) <= MAX_NUMBER:
return int(raw)
print(f"Enter a whole number between {MIN_NUMBER} and {MAX_NUMBER}.")
def play_game() -> None:
"""!
@brief Run one full game in the terminal.
"""
print(LOGO)
print("Welcome to the Number Guessing Game!")
print(f"I'm thinking of a number between {MIN_NUMBER} and {MAX_NUMBER}.")
answer = random.randint(MIN_NUMBER, MAX_NUMBER)
attempts = _ask_difficulty()
while attempts > 0:
print(f"You have {attempts} attempts remaining to guess the number.")
guess = _ask_guess()
if guess == answer:
check_answer(guess, answer, attempts)
return
attempts = check_answer(guess, answer, attempts)
if attempts > 0:
print("Guess again.")
print(f"You've run out of guesses. The answer was {answer}. You lose.")
+40
View File
@@ -0,0 +1,40 @@
import pytest
from guess_the_number import game
from guess_the_number.constants import EASY_ATTEMPTS, HARD_ATTEMPTS
def test_set_difficulty():
assert game.set_difficulty("easy") == EASY_ATTEMPTS
assert game.set_difficulty(" HARD ") == HARD_ATTEMPTS
def test_set_difficulty_invalid():
with pytest.raises(ValueError):
game.set_difficulty("medium")
def test_check_answer_high_low_correct(capsys):
assert game.check_answer(50, 10, 5) == 4
assert "Too high" in capsys.readouterr().out
assert game.check_answer(5, 10, 4) == 3
assert "Too low" in capsys.readouterr().out
assert game.check_answer(10, 10, 3) == 3
assert "You got it" in capsys.readouterr().out
def _run(monkeypatch, inputs, answer=42):
monkeypatch.setattr(game.random, "randint", lambda a, b: answer)
it = iter(inputs)
monkeypatch.setattr("builtins.input", lambda _="": next(it))
game.play_game()
def test_play_game_win(monkeypatch, capsys):
_run(monkeypatch, ["easy", "abc", "500", "10", "42"])
assert "You got it" in capsys.readouterr().out
def test_play_game_lose(monkeypatch, capsys):
_run(monkeypatch, ["bogus", "hard"] + ["1"] * HARD_ATTEMPTS)
assert "You lose" in capsys.readouterr().out