Author SHA1 Message Date
TirsvadandClaude Sonnet 5.5 0a74e22a43 Resolve pending commit links for MIL-003 acceptance
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 23:42:10 +08:00
Tirsvad 923dde7dc9 Accept MIL-003 and add the Play a Game use case
Create the use case chain for the game loop task: Use Case Diagram
UCD-001, User Stories US-001 (two stories), Use Case UC-001 and System
Sequence Diagram SSD-001, all Proposed. Link them from MIL-003.

Review MIL-003 (RC-006, Go) and accept it. Add the new artifacts to the
Traceability Matrix and register UCD, US, UC and SSD in the artifact
registry.

Refs #13
2026-10-04 23:41:35 +08:00
Tirsvad 64e3f04c6a Merge pull request 'MIL-002 Game logic: data, art, constants and pure functions' (#24) from mil-002-game-logic into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 4s
Reviewed-on: #24
2026-10-04 17:36:11 +02:00
TirsvadandClaude Sonnet 5.5 037900df94 Resolve pending commit links for MIL-002 acceptance
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 23:35:18 +08:00
Tirsvad ee58fcdc1c Add game data, art, constants and game logic
Add the assignment data set (50 accounts), the logo and vs art, the
constants module, and the pure functions get_random_account,
pick_pair, format_data and check_answer, all with Doxygen comments.
pick_pair never returns the same account twice; check_answer accepts
either choice on a tie. Cover them with pytest tests (22 passing).

The art is built from concatenated raw lines so the backslashes survive
and Doxygen does not read the backticks as markup.

Accept MIL-002 after review RC-005 and add it to the Traceability
Matrix.

Task: MIL-002#1
Task: MIL-002#2
Task: MIL-002#3
Task: MIL-002#4
Task: MIL-002#5
Task: MIL-002#6
Closes #6
Closes #7
Closes #8
Closes #9
Closes #10
Closes #11
2026-10-04 23:34:48 +08:00
16 changed files with 960 additions and 14 deletions
+5 -1
View File
@@ -15,8 +15,12 @@ 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 | 005 |
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 007 |
| 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 |
| UC | Use Case | docs/uc-*/uc.md | 002 |
| SSD | System Sequence Diagram | docs/uc-*/ssd.md | 002 |
## Languages
+5 -2
View File
@@ -4,12 +4,13 @@
| Key | Value |
| --- | --- |
| ID | MIL-002 |
| CrossReference | [BC-001], [PP-001] |
| CrossReference | [BC-001], [PP-001], [RC-005] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | [ddfe96f] |
| 2026-10-04 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [ddfe96f] |
| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Accepted after review RC-005 | [ee58fcd] |
---
@@ -72,3 +73,5 @@ The assignment data set and art as modules, constants in `constants.py`, and the
[PP-001]: ../project-plan.md
[MIL-001]: ./mil-001-project-setup.md
[ddfe96f]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/ddfe96ffa459d5e4b2e30d71bd53878aba2cfc5d
[RC-005]: ../sqa/reviews/rc-005-mil-002.md
[ee58fcd]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/ee58fcdc1c9c83b0721022d623e4989591a47987
+9 -3
View File
@@ -4,12 +4,13 @@
| Key | Value |
| --- | --- |
| ID | MIL-003 |
| CrossReference | [BC-001], [PP-001] |
| CrossReference | [BC-001], [PP-001], [US-001], [RC-006] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | [ddfe96f] |
| 2026-10-04 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [ddfe96f] |
| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Linked UC-001 and the user stories<br>Accepted after review RC-006 | [923dde7] |
---
@@ -43,6 +44,7 @@ An interactive console game started with `python -m higher_lower`, built on the
| Business Case objective / KPI / user story | Reference |
| --- | --- |
| O1, O3 | [BC-001] |
| US-001.01, US-001.02 | [US-001] |
## Ownership
@@ -60,7 +62,7 @@ An interactive console game started with `python -m higher_lower`, built on the
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- |
| 1 | Implement display and input helpers | A function that prints the logo, "Compare A: ...", the `vs` art and "Against B: ..." using `format_data`, and one that asks for "A" or "B" and re-asks until valid (case-insensitive). Input and output go through injectable callables so tests need no real console. | No | |
| 2 | Implement the game loop | `play_game()` keeps the score, starts with a random pair, compares guesses with `check_answer`, moves B to A after a correct guess and picks a new B that differs from A. Prints the score after each round and "Sorry, that's wrong. Final score" at the end. | Yes | UC-001 Play a game (to be created with the SSD before this task starts) |
| 2 | Implement the game loop | `play_game()` keeps the score, starts with a random pair, compares guesses with `check_answer`, moves B to A after a correct guess and picks a new B that differs from A. Prints the score after each round and "Sorry, that's wrong. Final score" at the end. | Yes | [UC-001], [US-001] |
| 3 | Handle exhausted data | If every account has been used, end the game with a win message instead of failing to find a new B. | No | |
| 4 | Add entry point | `python -m higher_lower` runs `main()`, which calls `play_game()`; no side effects on import. | No | |
| 5 | Write game loop tests | pytest tests with scripted input and a seeded random generator: correct streak, wrong first guess, invalid input then valid, data exhausted, and the score printed. | No | |
@@ -71,3 +73,7 @@ An interactive console game started with `python -m higher_lower`, built on the
[PP-001]: ../project-plan.md
[MIL-002]: ./mil-002-game-logic.md
[ddfe96f]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/ddfe96ffa459d5e4b2e30d71bd53878aba2cfc5d
[US-001]: ../user-stories.md
[UC-001]: ../uc-001/uc.md
[RC-006]: ../sqa/reviews/rc-006-mil-003.md
[923dde7]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/923dde7dc9b59d2c77eddc88336451193147c557
+46
View File
@@ -0,0 +1,46 @@
# SQA Review Record: MIL-002 Game logic
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-005 |
| CrossReference | [MIL-002], [QC-MIL-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [ee58fcd] |
---
## Artifact Under Review
- Instance reviewed: [MIL-002]
- Checklist used: [QC-MIL-001]
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | Data and art modules, constants, four named functions, Doxygen comments, pytest tests. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Six objectively checkable criteria. |
| 3 | Dependencies on other milestones are explicitly mapped (Optional) | Pass | Depends on MIL-001, with the reason. |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | O1, O2, O3. |
| 5 | Milestone owner and approving reviewer are identified | Pass | S01 and S01. |
| 6 | Milestone has a target date consistent with project constraints | Pass | 2026-10-09, inside the 2026-10-14 constraint in BC-001. |
## Overall Verdict
Go — all criteria pass. Code under `src/` and `tests/` for MIL-002 may start once the MIL-001 deliverables exist (its dependency). MIL-003 and MIL-004 stay Proposed. 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 |
| --- | --- | --- |
| Deliver MIL-001 (issues #1-#4) before starting MIL-002 tasks | S01 | 2026-10-06 |
---
[MIL-002]: ../../milestones/mil-002-game-logic.md
[QC-MIL-001]: ../../../framework/qc/qc-milestones-gateways.md
[ee58fcd]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/ee58fcdc1c9c83b0721022d623e4989591a47987
+47
View File
@@ -0,0 +1,47 @@
# SQA Review Record: MIL-003 Console game
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-006 |
| CrossReference | [MIL-003], [QC-MIL-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-04 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] |
---
## Artifact Under Review
- Instance reviewed: [MIL-003]
- Checklist used: [QC-MIL-001]
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | A playable console game started with python -m higher_lower, with loop tests. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Six objectively checkable criteria. |
| 3 | Dependencies on other milestones are explicitly mapped (Optional) | Pass | Depends on MIL-002, with the reason. |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | O1, O3; stories US-001.01 and US-001.02. |
| 5 | Milestone owner and approving reviewer are identified | Pass | S01 and S01. |
| 6 | Milestone has a target date consistent with project constraints | Pass | 2026-10-12, inside the 2026-10-14 constraint in BC-001. |
## Overall Verdict
Go — all criteria pass. UC-001 and its SSD were created for task 2 (the game loop); UCD-001, US-001, UC-001 and SSD-001 stay Proposed until they are reviewed. MIL-004 stays Proposed. 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 |
| --- | --- | --- |
| Review UCD-001, US-001, UC-001 and SSD-001 against their QC checklists | S01 | 2026-10-12 |
| Add UCD-001 and US-001 to the CrossReference of BC-001 and PP-001 when those documents are next edited | S01 | 2026-10-14 |
---
[MIL-003]: ../../milestones/mil-003-console-game.md
[QC-MIL-001]: ../../../framework/qc/qc-milestones-gateways.md
[923dde7]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/923dde7dc9b59d2c77eddc88336451193147c557
+17 -5
View File
@@ -9,7 +9,8 @@
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | [a1ff735] |
| 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] |
---
@@ -26,13 +27,17 @@ target is measurable. A row is added or updated whenever an artifact instance is
| [SA-001] | SA | [BC-001] | [PP-001] | [RC-002] |
| [PP-001] | PP | [BC-001], [SA-001] | [MIL-001], [MIL-002], [MIL-003], [MIL-004] | [RC-003] |
| [MIL-001] | MIL | [BC-001], [PP-001] | - | [RC-004] |
| [MIL-002] | MIL | [BC-001], [PP-001] | - | - |
| [MIL-003] | MIL | [BC-001], [PP-001] | - | - |
| [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] | - | - |
## Coverage Notes
- No use cases, user stories or design artifacts exist yet (UC-001 is planned for MIL-003).
- UCD-001, US-001, UC-001 and SSD-001 exist but are not reviewed 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.
---
@@ -48,4 +53,11 @@ target is measurable. A row is added or updated whenever an artifact instance is
[RC-002]: ./reviews/rc-002-stakeholder-analysis.md
[RC-003]: ./reviews/rc-003-project-plan.md
[RC-004]: ./reviews/rc-004-mil-001.md
[a1ff735]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/a1ff73580fdcfad5bbb3854298fdfa0c16ada853
[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
+54
View File
@@ -0,0 +1,54 @@
# System Sequence Diagram: Play a Game
## Metadata
| Key | Value |
| --- | --- |
| ID | SSD-001 |
| CrossReference | [UC-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] |
---
## Source Use Case
[UC-001] Play a Game, main success scenario. The alternative flows (invalid answer, tie, no account left) are out of scope for this diagram.
## Diagram
```plantuml
@startuml
actor Player
participant ":System" as System
Player -> System : startGame()
System --> Player : accounts A and B
loop while the guess is right
Player -> System : makeGuess(choice)
System --> Player : right, score, new accounts A and B
end
Player -> System : makeGuess(choice)
System --> Player : wrong, final score
@enduml
```
## System Operations Table
| Step | Message (verb phrase) | Parameters | Return | Use case step |
| --- | --- | --- | --- | --- |
| 1 | startGame | none | accounts A and B | 1, 2 |
| 2 | makeGuess | choice (A or B) | right, new score, new accounts A and B | 3, 4, 5, 6 |
| 3 | makeGuess | choice (A or B) | wrong, final score | 3, 7 |
Steps 2 and 3 are the same operation; the loop shows the right-guess outcome repeating, and the last call shows the wrong-guess outcome.
## Lifecycle Notes
A game starts with `startGame` and ends when `makeGuess` returns wrong; the System keeps the score and the current accounts in between.
---
[UC-001]: ./uc.md
[923dde7]: https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/014-higher_lower/commit/923dde7dc9b59d2c77eddc88336451193147c557
+78
View File
@@ -0,0 +1,78 @@
# UC-001 Play a Game
## Metadata
| Key | Value |
| --- | --- |
| ID | UC-001 |
| CrossReference | [UCD-001], [US-001], [SA-001], [SSD-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] |
---
Format: Fully Dressed
## Scope
The Higher Lower console game.
## Level
User goal.
## Primary Actor
Player.
## Stakeholders and Interests
| Stakeholder | Interest |
| --- | --- |
| S01 | A correct game that shows good practice ([SA-001]) |
| S02 | Readable behaviour that matches the assignment ([SA-001]) |
## Preconditions
The Player has started the game.
## Postconditions (Success Guarantee)
The Player has seen the final score of the game.
## Main Success Scenario
1. The Player starts a game.
2. The System shows two different accounts, A and B.
3. The Player guesses which account has more followers.
4. The System tells the Player the guess was right, adds one to the score, and shows the new score.
5. The System makes the account the Player compared against the new A and shows a new B.
6. The Player repeats from step 3 until a guess is wrong.
7. The System tells the Player the guess was wrong and shows the final score.
## Extensions / Alternative Flows
- 3a. The Player gives an answer that is not a valid choice: the System asks again; the round does not change.
- 3b. Both accounts have the same number of followers: either choice counts as right.
- 5a. No account is left that differs from the current A: the System ends the game and shows the final score.
No `<<include>>` or `<<extend>>` use cases apply.
## Special Requirements / Business Rules
- Step 2 and 5: A and B are never the same account.
- Step 3b: equal follower counts make both choices right.
## Open Issues
None.
---
[UCD-001]: ../use-case-diagram.md
[US-001]: ../user-stories.md
[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
+55
View File
@@ -0,0 +1,55 @@
# Use Case Diagram
## Metadata
| Key | Value |
| --- | --- |
| ID | UCD-001 |
| CrossReference | [SA-001], [BC-001], [US-001], [UC-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] |
---
## Purpose and Scope
The system boundary is the Higher Lower console game. One actor, the Player, uses it to play a game. Traces to [BC-001] objective O1 and to stakeholders S01 and S02 ([SA-001]), who play the game; S03 only reads the repository.
## Diagram
```plantuml
@startuml
left to right direction
actor Player <<Actor>>
rectangle "Higher Lower <<System>>" {
usecase "Play a Game" as PlayAGame
}
Player --> PlayAGame
@enduml
```
## Actor Table
| Actor | Stereotype | Stakeholder ID (SA) | Goals (use cases) |
| --- | --- | --- | --- |
| Player | Actor | S01, S02 | Play a Game |
## Use Case Table
| Use Case | Actor(s) | Goal |
| --- | --- | --- |
| Play a Game ([UC-001]) | Player | Guess, round after round, which of two accounts has more followers, and see the final score |
## Relationships
None: there is a single use case, so no `<<include>>` or `<<extend>>` is used.
---
[SA-001]: ./stakeholder-analysis.md
[BC-001]: ./business-case.md
[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
+51
View File
@@ -0,0 +1,51 @@
# User Stories
## Metadata
| Key | Value |
| --- | --- |
| ID | US-001 |
| CrossReference | [UCD-001], [UC-001], [BC-001], [MIL-003] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-04 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | [923dde7] |
---
## Purpose and Scope
The stories for epic [MIL-003] (Console game): playing rounds and seeing the result. They support [BC-001] objective O1.
## Story List
### US-001.01 Guess which account has more followers
- **As a** Player, **I want** to guess which of two accounts has more followers and keep playing while I am right, **so that** I can build a streak.
- **Acceptance Criteria:**
- Given a new game, when it starts, then two different accounts A and B are shown.
- Given two accounts, when I guess the one with more followers, then my score goes up by one and the next round starts with that account as A and a new account as B.
- Given an invalid answer, when I submit it, then I am asked again and the round does not change.
- **Traces to:** [UC-001], [MIL-003]
- **Size:** one iteration.
### US-001.02 See my final score
- **As a** Player, **I want** to see my final score when the game ends, **so that** I know how well I did.
- **Acceptance Criteria:**
- Given a round, when I guess the account with fewer followers, then the game ends and shows my final score.
- Given every account has been used, when no new account can be shown, then the game ends and shows my final score.
- **Traces to:** [UC-001], [MIL-003]
- **Size:** one iteration.
## INVEST Check
Both stories are Independent of each other in wording, Negotiable in detail, Valuable to the Player, Estimable, Small (one iteration) and Testable through the acceptance criteria. No exceptions.
---
[UCD-001]: ./use-case-diagram.md
[UC-001]: ./uc-001/uc.md
[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
+31
View File
@@ -0,0 +1,31 @@
"""@file art.py
@brief ASCII art shown by the game, as given by the assignment.
The lines are raw strings so the backslashes in the art survive unchanged.
They are concatenated line by line, rather than written as one multi-line
string, so Doxygen does not read the backticks in the art as markup.
"""
## The game logo.
logo = (
"\n"
r" __ ___ __ " "\n"
r" / / / (_)___ _/ /_ ___ _____" "\n"
r" / /_/ / / __ `/ __ \/ _ \/ ___/" "\n"
r" / __ / / /_/ / / / / __/ / " "\n"
r"/_/ ///_/\__, /_/ /_/\___/_/ " "\n"
r" / / /____/_ _____ _____" "\n"
r" / / / __ \ | /| / / _ \/ ___/" "\n"
r" / /___/ /_/ / |/ |/ / __/ / " "\n"
r"/_____/\____/|__/|__/\___/_/ " "\n"
)
## The "vs" shown between the two accounts.
vs = (
"\n"
r" _ __ " "\n"
r"| | / /____" "\n"
r"| | / / ___/" "\n"
r"| |/ (__ ) " "\n"
r"|___/____(_)" "\n"
)
+25 -3
View File
@@ -1,6 +1,28 @@
"""@file constants.py
@brief Constants shared by the game modules.
Constants are added here as the game logic (MIL-002) and the game loop
(MIL-003) need them.
"""
## Keys of an account dictionary.
KEY_NAME = "name"
KEY_FOLLOWER_COUNT = "follower_count"
KEY_DESCRIPTION = "description"
KEY_COUNTRY = "country"
## The two choices the player can type, in lower case.
CHOICE_A = "a"
CHOICE_B = "b"
## Score at the start of a game.
START_SCORE = 0
## How an account is described to the player.
ACCOUNT_TEMPLATE = "{name}, a {description}, from {country}"
## Texts shown to the player.
MSG_COMPARE_A = "Compare A: {account}."
MSG_AGAINST_B = "Against B: {account}."
PROMPT_GUESS = "Who has more followers? Type 'A' or 'B': "
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}"
+324
View File
@@ -0,0 +1,324 @@
"""@file game_data.py
@brief The accounts the player compares, as given by the assignment.
The order is kept as in the assignment; the game compares follower counts,
never list positions.
"""
from typing import TypedDict
class Account(TypedDict):
"""@brief One account the player can be asked about.
follower_count is in millions of Instagram followers.
"""
name: str
follower_count: int
description: str
country: str
data: list[Account] = [
{
"name": "Instagram",
"follower_count": 346,
"description": "Social media platform",
"country": "United States",
},
{
"name": "Cristiano Ronaldo",
"follower_count": 215,
"description": "Footballer",
"country": "Portugal",
},
{
"name": "Ariana Grande",
"follower_count": 183,
"description": "Musician and actress",
"country": "United States",
},
{
"name": "Dwayne Johnson",
"follower_count": 181,
"description": "Actor and professional wrestler",
"country": "United States",
},
{
"name": "Selena Gomez",
"follower_count": 174,
"description": "Musician and actress",
"country": "United States",
},
{
"name": "Kylie Jenner",
"follower_count": 172,
"description": "Reality TV personality and businesswoman and Self-Made Billionaire",
"country": "United States",
},
{
"name": "Kim Kardashian",
"follower_count": 167,
"description": "Reality TV personality and businesswoman",
"country": "United States",
},
{
"name": "Lionel Messi",
"follower_count": 149,
"description": "Footballer",
"country": "Argentina",
},
{
"name": "Beyoncé",
"follower_count": 145,
"description": "Musician",
"country": "United States",
},
{
"name": "Neymar",
"follower_count": 138,
"description": "Footballer",
"country": "Brasil",
},
{
"name": "National Geographic",
"follower_count": 135,
"description": "Magazine",
"country": "United States",
},
{
"name": "Justin Bieber",
"follower_count": 133,
"description": "Musician",
"country": "Canada",
},
{
"name": "Taylor Swift",
"follower_count": 131,
"description": "Musician",
"country": "United States",
},
{
"name": "Kendall Jenner",
"follower_count": 127,
"description": "Reality TV personality and Model",
"country": "United States",
},
{
"name": "Jennifer Lopez",
"follower_count": 119,
"description": "Musician and actress",
"country": "United States",
},
{
"name": "Nicki Minaj",
"follower_count": 113,
"description": "Musician",
"country": "Trinidad and Tobago",
},
{
"name": "Nike",
"follower_count": 109,
"description": "Sportswear multinational",
"country": "United States",
},
{
"name": "Khloé Kardashian",
"follower_count": 108,
"description": "Reality TV personality and businesswoman",
"country": "United States",
},
{
"name": "Miley Cyrus",
"follower_count": 107,
"description": "Musician and actress",
"country": "United States",
},
{
"name": "Katy Perry",
"follower_count": 94,
"description": "Musician",
"country": "United States",
},
{
"name": "Kourtney Kardashian",
"follower_count": 90,
"description": "Reality TV personality",
"country": "United States",
},
{
"name": "Kevin Hart",
"follower_count": 89,
"description": "Comedian and actor",
"country": "United States",
},
{
"name": "Ellen DeGeneres",
"follower_count": 87,
"description": "Comedian",
"country": "United States",
},
{
"name": "Real Madrid CF",
"follower_count": 86,
"description": "Football club",
"country": "Spain",
},
{
"name": "FC Barcelona",
"follower_count": 85,
"description": "Football club",
"country": "Spain",
},
{
"name": "Rihanna",
"follower_count": 81,
"description": "Musician and businesswoman",
"country": "Barbados",
},
{
"name": "Demi Lovato",
"follower_count": 80,
"description": "Musician and actress",
"country": "United States",
},
{
"name": "Victoria's Secret",
"follower_count": 69,
"description": "Lingerie brand",
"country": "United States",
},
{
"name": "Zendaya",
"follower_count": 68,
"description": "Actress and musician",
"country": "United States",
},
{
"name": "Shakira",
"follower_count": 66,
"description": "Musician",
"country": "Colombia",
},
{
"name": "Drake",
"follower_count": 65,
"description": "Musician",
"country": "Canada",
},
{
"name": "Chris Brown",
"follower_count": 64,
"description": "Musician",
"country": "United States",
},
{
"name": "LeBron James",
"follower_count": 63,
"description": "Basketball player",
"country": "United States",
},
{
"name": "Vin Diesel",
"follower_count": 62,
"description": "Actor",
"country": "United States",
},
{
"name": "Cardi B",
"follower_count": 67,
"description": "Musician",
"country": "United States",
},
{
"name": "David Beckham",
"follower_count": 82,
"description": "Footballer",
"country": "United Kingdom",
},
{
"name": "Billie Eilish",
"follower_count": 61,
"description": "Musician",
"country": "United States",
},
{
"name": "Justin Timberlake",
"follower_count": 59,
"description": "Musician and actor",
"country": "United States",
},
{
"name": "UEFA Champions League",
"follower_count": 58,
"description": "Club football competition",
"country": "Europe",
},
{
"name": "NASA",
"follower_count": 56,
"description": "Space agency",
"country": "United States",
},
{
"name": "Emma Watson",
"follower_count": 56,
"description": "Actress",
"country": "United Kingdom",
},
{
"name": "Shawn Mendes",
"follower_count": 57,
"description": "Musician",
"country": "Canada",
},
{
"name": "Virat Kohli",
"follower_count": 55,
"description": "Cricketer",
"country": "India",
},
{
"name": "Gigi Hadid",
"follower_count": 54,
"description": "Model",
"country": "United States",
},
{
"name": "Priyanka Chopra Jonas",
"follower_count": 53,
"description": "Actress and musician",
"country": "India",
},
{
"name": "9GAG",
"follower_count": 52,
"description": "Social media platform",
"country": "China",
},
{
"name": "Ronaldinho",
"follower_count": 51,
"description": "Footballer",
"country": "Brasil",
},
{
"name": "Maluma",
"follower_count": 50,
"description": "Musician",
"country": "Colombia",
},
{
"name": "Camila Cabello",
"follower_count": 49,
"description": "Musician",
"country": "Cuba",
},
{
"name": "NBA",
"follower_count": 47,
"description": "Club Basketball Competition",
"country": "United States",
},
]
+78
View File
@@ -0,0 +1,78 @@
"""@file game_logic.py
@brief Pure functions of the game: drawing accounts, describing them and checking guesses.
"""
import random
from collections.abc import Sequence
from higher_lower.constants import (
ACCOUNT_TEMPLATE,
CHOICE_A,
CHOICE_B,
KEY_DESCRIPTION,
KEY_NAME,
KEY_COUNTRY,
)
from higher_lower.game_data import Account, data
_DEFAULT_RNG = random.Random()
def get_random_account(
accounts: Sequence[Account] = data,
rng: random.Random | None = None,
) -> Account:
"""@brief Return one random account.
@param accounts The accounts to draw from.
@param rng Random generator; pass a seeded one to make the draw repeatable.
@return A random entry of accounts.
"""
return (rng or _DEFAULT_RNG).choice(accounts)
def pick_pair(
accounts: Sequence[Account] = data,
rng: random.Random | None = None,
) -> tuple[Account, Account]:
"""@brief Return two different random accounts.
@param accounts The accounts to draw from; needs at least two entries.
@param rng Random generator; pass a seeded one to make the draw repeatable.
@return The accounts A and B, never the same entry.
@throws ValueError If accounts has fewer than two entries.
"""
first, second = (rng or _DEFAULT_RNG).sample(accounts, 2)
return first, second
def format_data(account: Account) -> str:
"""@brief Describe an account for the player.
@param account The account to describe.
@return Text such as "Nike, a Sportswear multinational, from United States".
"""
return ACCOUNT_TEMPLATE.format(
name=account[KEY_NAME],
description=account[KEY_DESCRIPTION],
country=account[KEY_COUNTRY],
)
def check_answer(guess: str, a_followers: int, b_followers: int) -> bool:
"""@brief Tell whether the guess names the account with more followers.
Equal follower counts make either choice correct.
@param guess "a" or "b", in any case.
@param a_followers Follower count of account A.
@param b_followers Follower count of account B.
@return True if the guess is right.
@throws ValueError If guess is neither "a" nor "b".
"""
choice = guess.lower()
if choice == CHOICE_A:
return a_followers >= b_followers
if choice == CHOICE_B:
return b_followers >= a_followers
raise ValueError(f"guess must be {CHOICE_A!r} or {CHOICE_B!r}, got {guess!r}")
+46
View File
@@ -0,0 +1,46 @@
"""@file test_game_data.py
@brief Tests for the assignment data set and art.
"""
from higher_lower.art import logo, vs
from higher_lower.constants import (
KEY_COUNTRY,
KEY_DESCRIPTION,
KEY_FOLLOWER_COUNT,
KEY_NAME,
)
from higher_lower.game_data import data
EXPECTED_KEYS = {KEY_NAME, KEY_FOLLOWER_COUNT, KEY_DESCRIPTION, KEY_COUNTRY}
def test_data_has_fifty_accounts() -> None:
"""@brief The assignment data set has 50 entries."""
assert len(data) == 50
def test_every_account_has_the_required_keys() -> None:
"""@brief Every entry has exactly the four assignment keys."""
assert all(set(account) == EXPECTED_KEYS for account in data)
def test_follower_counts_are_positive_integers() -> None:
"""@brief Follower counts are positive integers."""
assert all(
isinstance(account[KEY_FOLLOWER_COUNT], int) and account[KEY_FOLLOWER_COUNT] > 0
for account in data
)
def test_account_names_are_unique() -> None:
"""@brief No account appears twice."""
names = [account[KEY_NAME] for account in data]
assert len(names) == len(set(names))
def test_art_is_available() -> None:
"""@brief The logo and the vs art keep their shape, including backslashes."""
assert len(logo.splitlines()) == 10
assert "\\" in logo
assert len(vs.splitlines()) == 6
assert "|___/____(_)" in vs
+89
View File
@@ -0,0 +1,89 @@
"""@file test_game_logic.py
@brief Tests for the pure game functions.
"""
import random
import pytest
from higher_lower.constants import KEY_NAME
from higher_lower.game_data import Account, data
from higher_lower.game_logic import (
check_answer,
format_data,
get_random_account,
pick_pair,
)
def test_get_random_account_returns_an_entry_of_the_data() -> None:
"""@brief The drawn account comes from the data set."""
assert get_random_account(rng=random.Random(1)) in data
def test_get_random_account_is_repeatable_with_a_seed() -> None:
"""@brief The same seed draws the same account."""
assert get_random_account(rng=random.Random(7)) == get_random_account(
rng=random.Random(7)
)
def test_pick_pair_never_returns_the_same_account() -> None:
"""@brief A and B are always different entries."""
rng = random.Random(0)
for _ in range(200):
first, second = pick_pair(rng=rng)
assert first[KEY_NAME] != second[KEY_NAME]
def test_pick_pair_with_two_accounts_returns_both() -> None:
"""@brief With only two accounts, the pair holds both of them."""
two = data[:2]
first, second = pick_pair(two, random.Random(3))
assert {first[KEY_NAME], second[KEY_NAME]} == {a[KEY_NAME] for a in two}
def test_pick_pair_with_one_account_raises() -> None:
"""@brief A pair cannot be drawn from a single account."""
with pytest.raises(ValueError):
pick_pair(data[:1])
def test_format_data_describes_the_account() -> None:
"""@brief The description holds name, description and country."""
account: Account = {
"name": "Nike",
"follower_count": 109,
"description": "Sportswear multinational",
"country": "United States",
}
assert (
format_data(account)
== "Nike, a Sportswear multinational, from United States"
)
@pytest.mark.parametrize(
("guess", "a_followers", "b_followers", "expected"),
[
("a", 200, 100, True),
("a", 100, 200, False),
("b", 100, 200, True),
("b", 200, 100, False),
("A", 200, 100, True),
("B", 100, 200, True),
("a", 100, 100, True),
("b", 100, 100, True),
],
)
def test_check_answer(
guess: str, a_followers: int, b_followers: int, expected: bool
) -> None:
"""@brief The answer is right for the higher account, and for either on a tie."""
assert check_answer(guess, a_followers, b_followers) is expected
def test_check_answer_rejects_an_unknown_choice() -> None:
"""@brief A guess other than a or b is an error."""
with pytest.raises(ValueError):
check_answer("c", 1, 2)