MIL-002: Adopt the game #24

Merged
Tirsvad merged 8 commits from mil-002-adopt-the-game into main 2026-10-08 12:38:27 +02:00
2 changed files with 143 additions and 0 deletions
Showing only changes of commit ca65ba7830 - Show all commits
+58
View File
@@ -0,0 +1,58 @@
"""! @file
@brief The scoreboard: the text at the top of the screen that shows the score.
The class follows the day-21 lecture on inheritance: `Scoreboard` is a `Turtle`
that writes text. Like `food`, this module needs `turtle` when it is imported;
the game imports it only when it starts, and the tests install a fake `turtle`
module first.
"""
from turtle import Turtle
from snake_game.constants import (
GAME_OVER_POSITION,
GAME_OVER_TEXT,
SCORE_LABEL,
SCOREBOARD_ALIGNMENT,
SCOREBOARD_COLOR,
SCOREBOARD_FONT,
SCOREBOARD_POSITION,
)
class Scoreboard(Turtle):
"""! @brief Shows the score as text at the top centre of the screen."""
def __init__(self) -> None:
"""! @brief Set the scoreboard up and write the score 0."""
super().__init__()
## @brief The number of foods the snake has eaten in this game.
self.score = 0
self.color(SCOREBOARD_COLOR)
self.penup()
self.hideturtle()
self.goto(SCOREBOARD_POSITION)
self.update_scoreboard()
def update_scoreboard(self) -> None:
"""! @brief Wipe the old text and write the label and the score."""
self.clear()
self.write(
f"{SCORE_LABEL}{self.score}",
align=SCOREBOARD_ALIGNMENT,
font=SCOREBOARD_FONT,
)
def increase_score(self) -> None:
"""! @brief Add 1 to the score and write it."""
self.score += 1
self.update_scoreboard()
def game_over(self) -> None:
"""! @brief Write the game-over text at the centre of the screen.
The score stays where it is: the old text is not wiped. Automatic drawing is
off in the game, so the screen must be updated after this call.
"""
self.goto(GAME_OVER_POSITION)
self.write(GAME_OVER_TEXT, align=SCOREBOARD_ALIGNMENT, font=SCOREBOARD_FONT)
+85
View File
@@ -0,0 +1,85 @@
"""Tests of the scoreboard, with a fake `Turtle` as its base class."""
import importlib
import sys
import pytest
from fakes import install_fake_turtle, make_scoreboard
from snake_game import constants
START_TEXT = ("write", ("Score: 0", "center", ("Arial", 24, "normal")))
def test_scoreboard_is_white_hidden_and_at_the_top_centre(
monkeypatch: pytest.MonkeyPatch,
) -> None:
scoreboard = make_scoreboard(monkeypatch)
assert ("color", ("white",)) in scoreboard.calls
assert "penup" in scoreboard.call_names()
assert "hideturtle" in scoreboard.call_names()
assert ("goto", constants.SCOREBOARD_POSITION) in scoreboard.calls
def test_scoreboard_inherits_from_turtle(monkeypatch: pytest.MonkeyPatch) -> None:
install_fake_turtle(monkeypatch)
module = importlib.import_module("snake_game.scoreboard")
assert issubclass(module.Scoreboard, sys.modules["turtle"].Turtle)
def test_scoreboard_starts_by_writing_score_0_centred_in_arial_24(
monkeypatch: pytest.MonkeyPatch,
) -> None:
scoreboard = make_scoreboard(monkeypatch)
assert scoreboard.calls[-1] == START_TEXT
def test_increase_score_wipes_the_old_text_and_writes_the_new_score(
monkeypatch: pytest.MonkeyPatch,
) -> None:
scoreboard = make_scoreboard(monkeypatch)
scoreboard.increase_score()
assert scoreboard.call_names()[-2:] == ["clear", "write"]
assert scoreboard.calls[-1] == (
"write",
("Score: 1", "center", ("Arial", 24, "normal")),
)
def test_every_food_adds_one_to_the_score(monkeypatch: pytest.MonkeyPatch) -> None:
scoreboard = make_scoreboard(monkeypatch)
for _ in range(3):
scoreboard.increase_score()
assert scoreboard.calls[-1][1][0] == "Score: 3"
def test_game_over_writes_the_text_at_the_centre_in_the_scoreboard_font(
monkeypatch: pytest.MonkeyPatch,
) -> None:
scoreboard = make_scoreboard(monkeypatch)
scoreboard.game_over()
assert scoreboard.calls[-2:] == [
("goto", constants.GAME_OVER_POSITION),
("write", ("GAME OVER", "center", ("Arial", 24, "normal"))),
]
def test_game_over_leaves_the_score_on_the_screen(
monkeypatch: pytest.MonkeyPatch,
) -> None:
scoreboard = make_scoreboard(monkeypatch)
scoreboard.increase_score()
calls_before = len(scoreboard.calls)
scoreboard.game_over()
assert "clear" not in scoreboard.call_names()[calls_before:]