From c7164feab1333951bf6f557baf95a26764656be0 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Sun, 4 Oct 2026 23:48:42 +0800 Subject: [PATCH 1/3] Accept UCD-001, US-001, UC-001 and SSD-001 after review Review the use case chain for Play a Game (RC-007 to RC-010, all Go) and accept the four documents. Update the Traceability Matrix. --- docs/artifact-registry.md | 2 +- docs/sqa/reviews/rc-007-ucd-001.md | 47 +++++++++++++++++++++++++++ docs/sqa/reviews/rc-008-us-001.md | 46 +++++++++++++++++++++++++++ docs/sqa/reviews/rc-009-uc-001.md | 51 ++++++++++++++++++++++++++++++ docs/sqa/reviews/rc-010-ssd-001.md | 46 +++++++++++++++++++++++++++ docs/sqa/traceability-matrix.md | 17 ++++++---- docs/uc-001/ssd.md | 6 ++-- docs/uc-001/uc.md | 6 ++-- docs/use-case-diagram.md | 6 ++-- docs/user-stories.md | 6 ++-- 10 files changed, 217 insertions(+), 16 deletions(-) create mode 100644 docs/sqa/reviews/rc-007-ucd-001.md create mode 100644 docs/sqa/reviews/rc-008-us-001.md create mode 100644 docs/sqa/reviews/rc-009-uc-001.md create mode 100644 docs/sqa/reviews/rc-010-ssd-001.md diff --git a/docs/artifact-registry.md b/docs/artifact-registry.md index 06384b3..d62dffc 100644 --- a/docs/artifact-registry.md +++ b/docs/artifact-registry.md @@ -15,7 +15,7 @@ document of a type. `Primary File` may contain a glob (e.g. | SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 | | PP | Project Plan | docs/project-plan.md | 002 | | MIL | Milestone / Gateway | docs/milestones/*.md | 005 | -| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 007 | +| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 011 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | | UCD | Use Case Diagram | docs/use-case-diagram.md | 002 | | US | User Story | docs/user-stories.md | 002 | diff --git a/docs/sqa/reviews/rc-007-ucd-001.md b/docs/sqa/reviews/rc-007-ucd-001.md new file mode 100644 index 0000000..0901f90 --- /dev/null +++ b/docs/sqa/reviews/rc-007-ucd-001.md @@ -0,0 +1,47 @@ +# SQA Review Record: UCD-001 Use Case Diagram + +## Metadata +| Key | Value | +| --- | --- | +| ID | RC-007 | +| CrossReference | [UCD-001], [QC-UCD-001] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | pending | + +--- + +## Artifact Under Review + +- Instance reviewed: [UCD-001] +- Checklist used: [QC-UCD-001] + +## Checklist Results + +| # | Criterion | Status | Evidence/Notes | +| --- | --- | --- | --- | +| 1 | Actors are defined with correct UML stereotypes (e.g. `<>`, `<>`) | Pass | Actor <> and boundary <> stereotyped. | +| 2 | System boundary is clearly drawn and labeled | Pass | Boundary rectangle labelled Higher Lower. | +| 3 | Include/extend relationships are used correctly per UML 2.5.1, not as generic "uses" arrows | Pass | No include/extend used; the Relationships section says so. | +| 4 | Every actor participates in at least one use case (no orphan actors) | Pass | Player is in Play a Game. | +| 5 | Diagram is traceable to a documented stakeholder need | Pass | Player traces to S01 and S02 in SA-001. | +| 6 | Use case names are verb phrases describing actor goals, not internal system operations | Pass | Play a Game is an actor goal. | +| 7 | Diagram is free of implementation detail (e.g. UI widgets, database tables) (Optional) | Pass | No UI or storage detail. | +| 8 | Actor and use case naming is consistent with corresponding Use Case and User Story documents (Optional) | Pass | Names match US-001 and UC-001. | + +## Overall Verdict + +Go — all applicable criteria pass. Reviewer and author are both S01: the project has a single participant, so the framework's author/reviewer separation cannot be met. S01 accepted this deviation when requesting acceptance (2026-10-04). + +## Action Items + +| Action | Owner | Due | +| --- | --- | --- | +| None | — | — | + +--- + +[UCD-001]: ../../use-case-diagram.md +[QC-UCD-001]: ../../../framework/qc/qc-use-case-diagram.md diff --git a/docs/sqa/reviews/rc-008-us-001.md b/docs/sqa/reviews/rc-008-us-001.md new file mode 100644 index 0000000..158e74a --- /dev/null +++ b/docs/sqa/reviews/rc-008-us-001.md @@ -0,0 +1,46 @@ +# SQA Review Record: US-001 User Stories + +## Metadata +| Key | Value | +| --- | --- | +| ID | RC-008 | +| CrossReference | [US-001], [QC-US-001] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | pending | + +--- + +## Artifact Under Review + +- Instance reviewed: [US-001] +- Checklist used: [QC-US-001] + +## Checklist Results + +| # | Criterion | Status | Evidence/Notes | +| --- | --- | --- | --- | +| 1 | Follows INVEST criteria (Independent, Negotiable, Valuable, Estimable, Small, Testable) | Pass | INVEST check present, no exceptions. | +| 2 | Written in "As a / I want / So that" form | Pass | Both stories use As a / I want / so that. | +| 3 | Clear, testable acceptance criteria are included | Pass | Given/When/Then criteria on both stories. | +| 4 | Traceable to a use case or epic | Pass | Both trace to UC-001 and MIL-003. | +| 5 | Story is sized to fit within a single iteration (Optional) | Pass | Each fits one iteration. | +| 6 | Story statement avoids technical implementation detail (Optional) | Pass | Stated as player goals. | +| 7 | Role named in the story matches an actor defined in the Use Case Diagram | Pass | Role Player matches UCD-001. | + +## Overall Verdict + +Go — all applicable criteria pass. Reviewer and author are both S01: the project has a single participant, so the framework's author/reviewer separation cannot be met. S01 accepted this deviation when requesting acceptance (2026-10-04). + +## Action Items + +| Action | Owner | Due | +| --- | --- | --- | +| None | — | — | + +--- + +[US-001]: ../../user-stories.md +[QC-US-001]: ../../../framework/qc/qc-user-story.md diff --git a/docs/sqa/reviews/rc-009-uc-001.md b/docs/sqa/reviews/rc-009-uc-001.md new file mode 100644 index 0000000..11046dd --- /dev/null +++ b/docs/sqa/reviews/rc-009-uc-001.md @@ -0,0 +1,51 @@ +# SQA Review Record: UC-001 Use Case + +## Metadata +| Key | Value | +| --- | --- | +| ID | RC-009 | +| CrossReference | [UC-001], [QC-UC-001] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | pending | + +--- + +## Artifact Under Review + +- Instance reviewed: [UC-001] +- Checklist used: [QC-UC-001] + +## Checklist Results + +| # | Criterion | Status | Evidence/Notes | +| --- | --- | --- | --- | +| 1 | Consists of a single, concise paragraph summarizing only the primary success scenario | N-A | Applies to the Brief format only; this use case is Fully Dressed. | +| 2 | Written as an informal multi-paragraph narrative; may mention some alternate flows without formal structure | N-A | Applies to the Casual format only; this use case is Fully Dressed. | +| 3 | All standard sections are present: actors, preconditions, postconditions, main success scenario, alternative/exception flows | Pass | Scope, level, actor, stakeholders, pre/postconditions, scenario, extensions, rules and open issues present. | +| 4 | Preconditions and postconditions are explicitly defined | Pass | Both defined. | +| 5 | Primary actor is explicitly stated | Pass | Player. | +| 6 | Stakeholders and their interests are stated | Pass | S01 and S02 with interests. | +| 7 | Main success scenario is written as clear, numbered steps | Pass | Seven numbered steps. | +| 8 | Alternative/exception flows correctly reference `<>`/`<>` use cases where relevant, per UML 2.5.1 | Pass | No include/extend applies; stated. | +| 9 | Explicit business rules are captured per step where applicable, rather than embedded loosely in narrative text (Optional) | Pass | Rules listed for steps 2, 3b and 5. | +| 10 | Naming of actors and use case title is consistent with the corresponding Use Case Diagram and User Stories | Pass | Title and actor match UCD-001 and US-001. | +| 11 | Scope/level (e.g. summary, user-goal, subfunction) is explicitly stated (Optional) | Pass | Level: user goal. | +| 12 | Use case is written from the actor's goal perspective, free of UI or implementation detail | Pass | Goal wording, no UI or implementation detail. | + +## Overall Verdict + +Go — all applicable criteria pass. Reviewer and author are both S01: the project has a single participant, so the framework's author/reviewer separation cannot be met. S01 accepted this deviation when requesting acceptance (2026-10-04). + +## Action Items + +| Action | Owner | Due | +| --- | --- | --- | +| None | — | — | + +--- + +[UC-001]: ../../uc-001/uc.md +[QC-UC-001]: ../../../framework/qc/qc-use-case.md diff --git a/docs/sqa/reviews/rc-010-ssd-001.md b/docs/sqa/reviews/rc-010-ssd-001.md new file mode 100644 index 0000000..8be3b79 --- /dev/null +++ b/docs/sqa/reviews/rc-010-ssd-001.md @@ -0,0 +1,46 @@ +# SQA Review Record: SSD-001 System Sequence Diagram + +## Metadata +| Key | Value | +| --- | --- | +| ID | RC-010 | +| CrossReference | [SSD-001], [QC-SSD-001] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | pending | + +--- + +## Artifact Under Review + +- Instance reviewed: [SSD-001] +- Checklist used: [QC-SSD-001] + +## Checklist Results + +| # | Criterion | Status | Evidence/Notes | +| --- | --- | --- | --- | +| 1 | Input/output messages match the corresponding Use Case's main success scenario step-for-step | Pass | startGame and makeGuess cover steps 1 to 7 of UC-001. | +| 2 | Actor and System are treated strictly as black boxes (system shown as `:System`) | Pass | Only Player and :System. | +| 3 | Object creation/destruction of the System instance handled explicitly where relevant (Optional) | Pass | Lifecycle notes state when the game starts and ends. | +| 4 | Return values are shown for operations that produce one, using dashed return arrows | Pass | Dashed returns on every operation. | +| 5 | Alternate/exceptional flows are represented separately (or explicitly out of scope noted) | Pass | Alternative flows noted as out of scope. | +| 6 | Message names are verb phrases consistent with the use case's system responsibilities (Optional) | Pass | startGame, makeGuess. | +| 7 | Diagram references the specific Use Case (name and ID) it depicts | Pass | Cites UC-001 Play a Game. | + +## Overall Verdict + +Go — all applicable criteria pass. Reviewer and author are both S01: the project has a single participant, so the framework's author/reviewer separation cannot be met. S01 accepted this deviation when requesting acceptance (2026-10-04). + +## Action Items + +| Action | Owner | Due | +| --- | --- | --- | +| None | — | — | + +--- + +[SSD-001]: ../../uc-001/ssd.md +[QC-SSD-001]: ../../../framework/qc/qc-ssd.md diff --git a/docs/sqa/traceability-matrix.md b/docs/sqa/traceability-matrix.md index 46bd58f..fb38fd5 100644 --- a/docs/sqa/traceability-matrix.md +++ b/docs/sqa/traceability-matrix.md @@ -9,8 +9,8 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Added RC-005 review of MIL-002 | [ee58fcd] | | 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Added RC-006 and the UC-001 artifacts | [923dde7] | +| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Added RC-007 to RC-010 for UCD-001, US-001, UC-001 and SSD-001 | pending | --- @@ -30,14 +30,14 @@ target is measurable. A row is added or updated whenever an artifact instance is | [MIL-002] | MIL | [BC-001], [PP-001], [MIL-001] | - | [RC-005] | | [MIL-003] | MIL | [BC-001], [PP-001], [MIL-002], [US-001] | [UC-001] | [RC-006] | | [MIL-004] | MIL | [BC-001], [PP-001] | - | - | -| [UCD-001] | UCD | [SA-001], [BC-001] | [US-001], [UC-001] | - | -| [US-001] | US | [UCD-001], [BC-001], [MIL-003] | [UC-001] | - | -| [UC-001] | UC | [UCD-001], [US-001], [SA-001] | [SSD-001] | - | -| [SSD-001] | SSD | [UC-001] | - | - | +| [UCD-001] | UCD | [SA-001], [BC-001] | [US-001], [UC-001] | [RC-007] | +| [US-001] | US | [UCD-001], [BC-001], [MIL-003] | [UC-001] | [RC-008] | +| [UC-001] | UC | [UCD-001], [US-001], [SA-001] | [SSD-001] | [RC-009] | +| [SSD-001] | SSD | [UC-001] | - | [RC-010] | ## Coverage Notes -- UCD-001, US-001, UC-001 and SSD-001 exist but are not reviewed yet. No domain model or design artifacts exist yet. +- No domain model or design artifacts exist yet. - `-` in Upstream means foundational; in Downstream, nothing is built on it yet; in Last Reviewed, no `RC-*` exists yet. --- @@ -54,10 +54,13 @@ target is measurable. A row is added or updated whenever an artifact instance is [RC-003]: ./reviews/rc-003-project-plan.md [RC-004]: ./reviews/rc-004-mil-001.md [RC-005]: ./reviews/rc-005-mil-002.md -[ee58fcd]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/ee58fcdc1c9c83b0721022d623e4989591a47987 [UCD-001]: ../use-case-diagram.md [US-001]: ../user-stories.md [UC-001]: ../uc-001/uc.md [SSD-001]: ../uc-001/ssd.md [RC-006]: ./reviews/rc-006-mil-003.md [923dde7]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/923dde7dc9b59d2c77eddc88336451193147c557 +[RC-007]: ./reviews/rc-007-ucd-001.md +[RC-008]: ./reviews/rc-008-us-001.md +[RC-009]: ./reviews/rc-009-uc-001.md +[RC-010]: ./reviews/rc-010-ssd-001.md diff --git a/docs/uc-001/ssd.md b/docs/uc-001/ssd.md index 64c0576..e74588e 100644 --- a/docs/uc-001/ssd.md +++ b/docs/uc-001/ssd.md @@ -4,12 +4,13 @@ | Key | Value | | --- | --- | | ID | SSD-001 | -| CrossReference | [UC-001] | +| CrossReference | [UC-001], [RC-010] | ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] | +| 2026-10-04 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Accepted after review RC-010 | pending | --- @@ -52,3 +53,4 @@ A game starts with `startGame` and ends when `makeGuess` returns wrong; the Syst [UC-001]: ./uc.md [923dde7]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/923dde7dc9b59d2c77eddc88336451193147c557 +[RC-010]: ../sqa/reviews/rc-010-ssd-001.md diff --git a/docs/uc-001/uc.md b/docs/uc-001/uc.md index af0130f..7d73c2e 100644 --- a/docs/uc-001/uc.md +++ b/docs/uc-001/uc.md @@ -4,12 +4,13 @@ | Key | Value | | --- | --- | | ID | UC-001 | -| CrossReference | [UCD-001], [US-001], [SA-001], [SSD-001] | +| CrossReference | [UCD-001], [US-001], [SA-001], [SSD-001], [RC-009] | ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] | +| 2026-10-04 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Accepted after review RC-009 | pending | --- @@ -76,3 +77,4 @@ None. [SA-001]: ../stakeholder-analysis.md [SSD-001]: ./ssd.md [923dde7]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/923dde7dc9b59d2c77eddc88336451193147c557 +[RC-009]: ../sqa/reviews/rc-009-uc-001.md diff --git a/docs/use-case-diagram.md b/docs/use-case-diagram.md index 962a1cf..a0ae109 100644 --- a/docs/use-case-diagram.md +++ b/docs/use-case-diagram.md @@ -4,12 +4,13 @@ | Key | Value | | --- | --- | | ID | UCD-001 | -| CrossReference | [SA-001], [BC-001], [US-001], [UC-001] | +| CrossReference | [SA-001], [BC-001], [US-001], [UC-001], [RC-007] | ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] | +| 2026-10-04 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Accepted after review RC-007 | pending | --- @@ -53,3 +54,4 @@ None: there is a single use case, so no `<>` or `<>` is used. [US-001]: ./user-stories.md [UC-001]: ./uc-001/uc.md [923dde7]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/923dde7dc9b59d2c77eddc88336451193147c557 +[RC-007]: ./sqa/reviews/rc-007-ucd-001.md diff --git a/docs/user-stories.md b/docs/user-stories.md index d83b6a7..7d66dd1 100644 --- a/docs/user-stories.md +++ b/docs/user-stories.md @@ -4,12 +4,13 @@ | Key | Value | | --- | --- | | ID | US-001 | -| CrossReference | [UCD-001], [UC-001], [BC-001], [MIL-003] | +| CrossReference | [UCD-001], [UC-001], [BC-001], [MIL-003], [RC-008] | ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] | +| 2026-10-04 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Accepted after review RC-008 | pending | --- @@ -49,3 +50,4 @@ Both stories are Independent of each other in wording, Negotiable in detail, Val [BC-001]: ./business-case.md [MIL-003]: ./milestones/mil-003-console-game.md [923dde7]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/923dde7dc9b59d2c77eddc88336451193147c557 +[RC-008]: ./sqa/reviews/rc-008-us-001.md -- 2.54.0 From 0ada1588ee04a43dff925042acd6c8fd01473a26 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Sun, 4 Oct 2026 23:49:09 +0800 Subject: [PATCH 2/3] Add the console game loop and entry point Add show_round, ask_choice and play_game with injectable input and output, and make python -m higher_lower play a game. Each account is shown once per game; a wrong guess or running out of accounts ends it. Invalid input is re-asked. Ctrl+C and Ctrl+D leave with a goodbye. Cover the loop with scripted tests (36 passing). Task: MIL-003#1 Task: MIL-003#2 Task: MIL-003#3 Task: MIL-003#4 Task: MIL-003#5 Closes #12 Closes #13 Closes #14 Closes #15 Closes #16 --- src/higher_lower/__main__.py | 13 ++- src/higher_lower/constants.py | 1 + src/higher_lower/game.py | 122 ++++++++++++++++++++++++++++ tests/test_game.py | 144 ++++++++++++++++++++++++++++++++++ tests/test_smoke.py | 4 +- 5 files changed, 278 insertions(+), 6 deletions(-) create mode 100644 src/higher_lower/game.py create mode 100644 tests/test_game.py diff --git a/src/higher_lower/__main__.py b/src/higher_lower/__main__.py index 7f1c898..e0433b1 100644 --- a/src/higher_lower/__main__.py +++ b/src/higher_lower/__main__.py @@ -2,12 +2,17 @@ @brief Entry point for `python -m higher_lower`. """ +from higher_lower.constants import MSG_GOODBYE +from higher_lower.game import play_game + def main() -> None: - """@brief Start the game. - - Placeholder: the game loop is implemented in MIL-003. - """ + """@brief Play a game; leave quietly if the player quits with Ctrl+C or Ctrl+D.""" + try: + play_game() + except (EOFError, KeyboardInterrupt): + print() + print(MSG_GOODBYE) if __name__ == "__main__": diff --git a/src/higher_lower/constants.py b/src/higher_lower/constants.py index 22e9ffc..90574fc 100644 --- a/src/higher_lower/constants.py +++ b/src/higher_lower/constants.py @@ -26,3 +26,4 @@ MSG_INVALID_CHOICE = "Please type 'A' or 'B'." MSG_CORRECT = "You're right! Current score: {score}." MSG_WRONG = "Sorry, that's wrong. Final score: {score}" MSG_WIN = "You went through every account! Final score: {score}" +MSG_GOODBYE = "Goodbye!" diff --git a/src/higher_lower/game.py b/src/higher_lower/game.py new file mode 100644 index 0000000..736c49b --- /dev/null +++ b/src/higher_lower/game.py @@ -0,0 +1,122 @@ +"""@file game.py +@brief The interactive game: what the player sees, what they type, and the game loop. + +Input and output go through callables so tests can script a whole game. +""" + +import random +from collections.abc import Callable, Sequence + +from higher_lower.art import logo, vs +from higher_lower.constants import ( + CHOICE_A, + CHOICE_B, + KEY_FOLLOWER_COUNT, + KEY_NAME, + MSG_AGAINST_B, + MSG_COMPARE_A, + MSG_CORRECT, + MSG_INVALID_CHOICE, + MSG_WIN, + MSG_WRONG, + PROMPT_GUESS, + START_SCORE, +) +from higher_lower.game_data import Account, data +from higher_lower.game_logic import ( + check_answer, + format_data, + get_random_account, + pick_pair, +) + +InputFunc = Callable[[str], str] +OutputFunc = Callable[[str], None] + + +def show_round( + account_a: Account, + account_b: Account, + output: OutputFunc = print, +) -> None: + """@brief Show the two accounts the player compares. + + @param account_a The account shown as A. + @param account_b The account shown as B. + @param output Where the text goes. + """ + output(MSG_COMPARE_A.format(account=format_data(account_a))) + output(vs) + output(MSG_AGAINST_B.format(account=format_data(account_b))) + + +def ask_choice(input_func: InputFunc = input, output: OutputFunc = print) -> str: + """@brief Ask for A or B until the player gives a valid answer. + + @param input_func Reads one line of input, given the prompt. + @param output Where the invalid-answer message goes. + @return "a" or "b", in lower case. + """ + while True: + answer = input_func(PROMPT_GUESS).strip().lower() + if answer in (CHOICE_A, CHOICE_B): + return answer + output(MSG_INVALID_CHOICE) + + +def _pick_next_account( + accounts: Sequence[Account], + shown_names: set[str], + rng: random.Random | None, +) -> Account | None: + """@brief Draw an account that has not been shown in this game. + + @param accounts All accounts of the game. + @param shown_names Names of the accounts already shown. + @param rng Random generator, or None for the default. + @return A new account, or None if every account has been shown. + """ + unused = [account for account in accounts if account[KEY_NAME] not in shown_names] + if not unused: + return None + return get_random_account(unused, rng) + + +def play_game( + accounts: Sequence[Account] = data, + rng: random.Random | None = None, + input_func: InputFunc = input, + output: OutputFunc = print, +) -> int: + """@brief Play one game, from the logo to the final score. + + Each account is shown once per game. A right guess adds a point and + makes B the next A; a wrong guess, or running out of accounts, ends + the game. + + @param accounts The accounts to play with; needs at least two entries. + @param rng Random generator; pass a seeded one for a repeatable game. + @param input_func Reads one line of input, given the prompt. + @param output Where the text goes. + @return The final score. + """ + output(logo) + score = START_SCORE + account_a, account_b = pick_pair(accounts, rng) + shown_names = {account_a[KEY_NAME], account_b[KEY_NAME]} + while True: + show_round(account_a, account_b, output) + guess = ask_choice(input_func, output) + if not check_answer( + guess, account_a[KEY_FOLLOWER_COUNT], account_b[KEY_FOLLOWER_COUNT] + ): + output(MSG_WRONG.format(score=score)) + return score + score += 1 + output(MSG_CORRECT.format(score=score)) + next_account = _pick_next_account(accounts, shown_names, rng) + if next_account is None: + output(MSG_WIN.format(score=score)) + return score + account_a, account_b = account_b, next_account + shown_names.add(next_account[KEY_NAME]) diff --git a/tests/test_game.py b/tests/test_game.py new file mode 100644 index 0000000..427f9d1 --- /dev/null +++ b/tests/test_game.py @@ -0,0 +1,144 @@ +"""@file test_game.py +@brief Tests for the game loop, run with scripted input and no real console. +""" + +import io +import random +from collections.abc import Callable, Iterable, Sequence + +import pytest + +from higher_lower import __main__ as entry_point +from higher_lower.art import logo +from higher_lower.game import ask_choice, play_game, show_round +from higher_lower.game_data import Account + +ACCOUNTS: list[Account] = [ + {"name": "First", "follower_count": 30, "description": "Test one", "country": "Denmark"}, + {"name": "Second", "follower_count": 20, "description": "Test two", "country": "Norway"}, + {"name": "Third", "follower_count": 10, "description": "Test three", "country": "Sweden"}, +] + + +class FirstRng(random.Random): + """@brief Random generator that always takes the first entries, so a game is fixed.""" + + def sample(self, population: Sequence, k: int, **kwargs: object) -> list: # type: ignore[override] + """@brief Return the first k entries.""" + return list(population)[:k] + + def choice(self, seq: Sequence) -> Account: # type: ignore[override] + """@brief Return the first entry.""" + return seq[0] + + +def scripted(answers: Iterable[str]) -> Callable[[str], str]: + """@brief Make an input function that gives the answers one by one. + + @param answers The answers to give. + @return A function with the signature of input(). + """ + iterator = iter(answers) + return lambda _prompt: next(iterator) + + +def run_game(answers: Iterable[str]) -> tuple[int, str]: + """@brief Play a fixed game and return the score and everything shown. + + @param answers The answers the player gives. + @return The final score and the output joined with newlines. + """ + lines: list[str] = [] + score = play_game(ACCOUNTS, FirstRng(), scripted(answers), lines.append) + return score, "\n".join(lines) + + +def test_show_round_shows_both_accounts() -> None: + """@brief Both accounts are described, with the vs art between them.""" + lines: list[str] = [] + show_round(ACCOUNTS[0], ACCOUNTS[1], lines.append) + assert lines[0] == "Compare A: First, a Test one, from Denmark." + assert lines[2] == "Against B: Second, a Test two, from Norway." + + +@pytest.mark.parametrize("typed", ["a", "A", " a "]) +def test_ask_choice_accepts_a(typed: str) -> None: + """@brief A is accepted in any case and with spaces around it.""" + assert ask_choice(scripted([typed]), lambda _line: None) == "a" + + +def test_ask_choice_asks_again_after_an_invalid_answer() -> None: + """@brief Invalid answers are rejected and the player is asked again.""" + lines: list[str] = [] + assert ask_choice(scripted(["x", "", "b"]), lines.append) == "b" + assert len(lines) == 2 + + +def test_correct_streak_adds_a_point_per_round() -> None: + """@brief Each right guess adds one to the score and B becomes the next A.""" + score, shown = run_game(["a", "a"]) + assert score == 2 + assert "Current score: 1." in shown + assert "Compare A: Second" in shown + + +def test_wrong_first_guess_ends_the_game_with_zero() -> None: + """@brief A wrong guess ends the game and shows the final score.""" + score, shown = run_game(["b"]) + assert score == 0 + assert "Sorry, that's wrong. Final score: 0" in shown + + +def test_wrong_guess_after_a_point_keeps_the_score() -> None: + """@brief The final score is the number of right guesses.""" + score, shown = run_game(["a", "b"]) + assert score == 1 + assert "Final score: 1" in shown + + +def test_invalid_input_does_not_change_the_round() -> None: + """@brief An invalid answer is re-asked without ending the game or scoring.""" + score, shown = run_game(["x", "a", "a"]) + assert score == 2 + assert shown.count("Compare A: First") == 1 + + +def test_game_ends_with_a_win_when_the_accounts_run_out() -> None: + """@brief Using every account ends the game instead of failing.""" + score, shown = run_game(["a", "a"]) + assert score == 2 + assert "You went through every account!" in shown + + +def test_game_starts_with_the_logo() -> None: + """@brief The logo is shown first.""" + _score, shown = run_game(["b"]) + assert shown.startswith(logo) + + +def test_full_game_with_the_assignment_data_ends() -> None: + """@brief A game with the real data set ends with a final score.""" + lines: list[str] = [] + score = play_game( + rng=random.Random(5), input_func=lambda _prompt: "a", output=lines.append + ) + assert score >= 0 + assert "Final score" in lines[-1] + + +def test_main_plays_a_game( + monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + """@brief main() runs a game on the console.""" + monkeypatch.setattr("sys.stdin", io.StringIO("a\n" * 100)) + entry_point.main() + assert "Final score" in capsys.readouterr().out + + +def test_main_leaves_quietly_on_end_of_input( + monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + """@brief Ctrl+D ends the game with a goodbye, not a traceback.""" + monkeypatch.setattr("sys.stdin", io.StringIO("")) + entry_point.main() + assert "Goodbye!" in capsys.readouterr().out diff --git a/tests/test_smoke.py b/tests/test_smoke.py index c045c44..507c30c 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -12,5 +12,5 @@ def test_package_imports() -> None: def test_entry_point_is_callable() -> None: - """@brief The entry point exists and can be called.""" - assert entry_point.main() is None + """@brief The entry point exists; importing it does not start a game.""" + assert callable(entry_point.main) -- 2.54.0 From 5e4dd48e3ed84b40131f149b0f5b1e89eb45fad8 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Sun, 4 Oct 2026 23:49:52 +0800 Subject: [PATCH 3/3] Resolve pending commit links for accepted use case artifacts Co-Authored-By: Claude Sonnet 5.5 --- docs/sqa/reviews/rc-007-ucd-001.md | 3 ++- docs/sqa/reviews/rc-008-us-001.md | 3 ++- docs/sqa/reviews/rc-009-uc-001.md | 3 ++- docs/sqa/reviews/rc-010-ssd-001.md | 3 ++- docs/sqa/traceability-matrix.md | 3 ++- docs/uc-001/ssd.md | 3 ++- docs/uc-001/uc.md | 3 ++- docs/use-case-diagram.md | 3 ++- docs/user-stories.md | 3 ++- 9 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/sqa/reviews/rc-007-ucd-001.md b/docs/sqa/reviews/rc-007-ucd-001.md index 0901f90..faafa13 100644 --- a/docs/sqa/reviews/rc-007-ucd-001.md +++ b/docs/sqa/reviews/rc-007-ucd-001.md @@ -9,7 +9,7 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | pending | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [c7164fe] | --- @@ -45,3 +45,4 @@ Go — all applicable criteria pass. Reviewer and author are both S01: the proje [UCD-001]: ../../use-case-diagram.md [QC-UCD-001]: ../../../framework/qc/qc-use-case-diagram.md +[c7164fe]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/c7164feab1333951bf6f557baf95a26764656be0 diff --git a/docs/sqa/reviews/rc-008-us-001.md b/docs/sqa/reviews/rc-008-us-001.md index 158e74a..723c3e5 100644 --- a/docs/sqa/reviews/rc-008-us-001.md +++ b/docs/sqa/reviews/rc-008-us-001.md @@ -9,7 +9,7 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | pending | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [c7164fe] | --- @@ -44,3 +44,4 @@ Go — all applicable criteria pass. Reviewer and author are both S01: the proje [US-001]: ../../user-stories.md [QC-US-001]: ../../../framework/qc/qc-user-story.md +[c7164fe]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/c7164feab1333951bf6f557baf95a26764656be0 diff --git a/docs/sqa/reviews/rc-009-uc-001.md b/docs/sqa/reviews/rc-009-uc-001.md index 11046dd..791c8e4 100644 --- a/docs/sqa/reviews/rc-009-uc-001.md +++ b/docs/sqa/reviews/rc-009-uc-001.md @@ -9,7 +9,7 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | pending | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [c7164fe] | --- @@ -49,3 +49,4 @@ Go — all applicable criteria pass. Reviewer and author are both S01: the proje [UC-001]: ../../uc-001/uc.md [QC-UC-001]: ../../../framework/qc/qc-use-case.md +[c7164fe]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/c7164feab1333951bf6f557baf95a26764656be0 diff --git a/docs/sqa/reviews/rc-010-ssd-001.md b/docs/sqa/reviews/rc-010-ssd-001.md index 8be3b79..f309de8 100644 --- a/docs/sqa/reviews/rc-010-ssd-001.md +++ b/docs/sqa/reviews/rc-010-ssd-001.md @@ -9,7 +9,7 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | pending | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [c7164fe] | --- @@ -44,3 +44,4 @@ Go — all applicable criteria pass. Reviewer and author are both S01: the proje [SSD-001]: ../../uc-001/ssd.md [QC-SSD-001]: ../../../framework/qc/qc-ssd.md +[c7164fe]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/c7164feab1333951bf6f557baf95a26764656be0 diff --git a/docs/sqa/traceability-matrix.md b/docs/sqa/traceability-matrix.md index fb38fd5..fd2ea5e 100644 --- a/docs/sqa/traceability-matrix.md +++ b/docs/sqa/traceability-matrix.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Added RC-006 and the UC-001 artifacts | [923dde7] | -| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Added RC-007 to RC-010 for UCD-001, US-001, UC-001 and SSD-001 | pending | +| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Added RC-007 to RC-010 for UCD-001, US-001, UC-001 and SSD-001 | [c7164fe] | --- @@ -64,3 +64,4 @@ target is measurable. A row is added or updated whenever an artifact instance is [RC-008]: ./reviews/rc-008-us-001.md [RC-009]: ./reviews/rc-009-uc-001.md [RC-010]: ./reviews/rc-010-ssd-001.md +[c7164fe]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/c7164feab1333951bf6f557baf95a26764656be0 diff --git a/docs/uc-001/ssd.md b/docs/uc-001/ssd.md index e74588e..97a7e24 100644 --- a/docs/uc-001/ssd.md +++ b/docs/uc-001/ssd.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 2026-10-04 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] | -| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Accepted after review RC-010 | pending | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Accepted after review RC-010 | [c7164fe] | --- @@ -54,3 +54,4 @@ A game starts with `startGame` and ends when `makeGuess` returns wrong; the Syst [UC-001]: ./uc.md [923dde7]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/923dde7dc9b59d2c77eddc88336451193147c557 [RC-010]: ../sqa/reviews/rc-010-ssd-001.md +[c7164fe]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/c7164feab1333951bf6f557baf95a26764656be0 diff --git a/docs/uc-001/uc.md b/docs/uc-001/uc.md index 7d73c2e..4dbe7a1 100644 --- a/docs/uc-001/uc.md +++ b/docs/uc-001/uc.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 2026-10-04 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] | -| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Accepted after review RC-009 | pending | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Accepted after review RC-009 | [c7164fe] | --- @@ -78,3 +78,4 @@ None. [SSD-001]: ./ssd.md [923dde7]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/923dde7dc9b59d2c77eddc88336451193147c557 [RC-009]: ../sqa/reviews/rc-009-uc-001.md +[c7164fe]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/c7164feab1333951bf6f557baf95a26764656be0 diff --git a/docs/use-case-diagram.md b/docs/use-case-diagram.md index a0ae109..2e134dd 100644 --- a/docs/use-case-diagram.md +++ b/docs/use-case-diagram.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 2026-10-04 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] | -| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Accepted after review RC-007 | pending | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Accepted after review RC-007 | [c7164fe] | --- @@ -55,3 +55,4 @@ None: there is a single use case, so no `<>` or `<>` is used. [UC-001]: ./uc-001/uc.md [923dde7]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/923dde7dc9b59d2c77eddc88336451193147c557 [RC-007]: ./sqa/reviews/rc-007-ucd-001.md +[c7164fe]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/c7164feab1333951bf6f557baf95a26764656be0 diff --git a/docs/user-stories.md b/docs/user-stories.md index 7d66dd1..0b4a2f4 100644 --- a/docs/user-stories.md +++ b/docs/user-stories.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 2026-10-04 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] | -| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Accepted after review RC-008 | pending | +| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Accepted after review RC-008 | [c7164fe] | --- @@ -51,3 +51,4 @@ Both stories are Independent of each other in wording, Negotiable in detail, Val [MIL-003]: ./milestones/mil-003-console-game.md [923dde7]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/923dde7dc9b59d2c77eddc88336451193147c557 [RC-008]: ./sqa/reviews/rc-008-us-001.md +[c7164fe]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/c7164feab1333951bf6f557baf95a26764656be0 -- 2.54.0