Merge pull request 'MIL-001 Baseline and repository setup' (#25) from mil-001-baseline-setup into main
Run tests / Lint, type check and test (push) Successful in 39s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s

This commit was merged in pull request #25.
This commit is contained in:
2026-10-13 09:46:24 +02:00
20 changed files with 859 additions and 402 deletions
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+35
View File
@@ -0,0 +1,35 @@
# Continuous integration for the U.S. States Game.
# Read by Gitea Actions. Installs the project with its dev extra, then checks
# style (ruff), types (mypy) and behaviour (pytest). Design: docs/project-plan.md.
---
name: Run tests
on:
push:
branches: [main]
pull_request:
jobs:
run-tests:
name: Lint, type check and test
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out the project
uses: actions/checkout@v4.2.2
- name: Set up Python
uses: actions/setup-python@v5.3.0
with:
python-version: "3.13"
- name: Install the project and its dev tools
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
- name: Check style
run: python -m ruff check . && python -m ruff format --check .
- name: Check types
run: python -m mypy
- name: Check workflow files
run: python -m yamllint .gitea .yamllint
- name: Run the tests
run: python -m pytest
+42
View File
@@ -0,0 +1,42 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Virtual environments
.venv/
venv/
env/
ENV/
# Packaging and build
build/
dist/
*.egg-info/
.eggs/
pip-wheel-metadata/
# Test, coverage and tool caches
.pytest_cache/
.coverage
.coverage.*
htmlcov/
.tox/
.nox/
.mypy_cache/
.ruff_cache/
# Generated source documentation (Doxygen)
docs/build/
# Local credentials, never committed
.env
# Game output written to the working directory
states_to_learn.csv
# Editors and OS
.idea/
.vscode/
.DS_Store
Thumbs.db
+8
View File
@@ -0,0 +1,8 @@
# yamllint configuration for the workflow files under .gitea/workflows/.
---
extends: default
rules:
truthy:
allowed-values: ["true", "false", "on"]
line-length:
max: 100
+22
View File
@@ -0,0 +1,22 @@
# Doxygen configuration for the U.S. States Game.
# Build: doxygen Doxyfile
# Output: docs/build/doxygen/index.html
PROJECT_NAME = "U.S. States Game"
PROJECT_BRIEF = "Name the 50 states on a Turtle map"
OUTPUT_DIRECTORY = docs/build
HTML_OUTPUT = doxygen
INPUT = src
FILE_PATTERNS = *.py
RECURSIVE = YES
EXCLUDE_PATTERNS = */__pycache__/* */data/*
OPTIMIZE_OUTPUT_JAVA = YES
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
JAVADOC_AUTOBRIEF = YES
GENERATE_HTML = YES
GENERATE_LATEX = NO
HAVE_DOT = NO
QUIET = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_NO_PARAMDOC = YES
+116 -1
View File
@@ -1,2 +1,117 @@
# 025-us-states-game
# U.S. States Game
A quiz game from Udemy's *100 Days of Code: The Complete Python Pro Bootcamp* (day 25): a Turtle window shows a blank map of the United States, you type state names, and every correct guess appears on the map. When you leave, the states you missed are saved to `states_to_learn.csv`.
> **Status:** under construction. The tooling and layout are in place; the game logic and the Turtle screen follow in the next milestones.
## Requirements
- Python 3.13 or newer, with `tkinter` (Turtle needs it; the python.org installers and the Windows Store build include it)
- `git`
- Optional: [Doxygen](https://www.doxygen.nl/) to build the source documentation
The game has no runtime dependencies. `pytest` is installed only for development.
## Set up
Create a local virtual environment named `.venv` in the project folder, upgrade `pip` and install the project with its test tool.
### Windows powershell
```powershell
git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-us-states-game.git
cd 025-us-states-game
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
If PowerShell refuses to run the activation script, run `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once.
### Linux debian
```bash
sudo apt install python3 python3-venv python3-tk git
git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-us-states-game.git
cd 025-us-states-game
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
### MacOS
```bash
git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-us-states-game.git
cd 025-us-states-game
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
## Run
With the virtual environment active:
```bash
python -m us_states_game
```
Type a state name in the prompt. Type `exit` to quit; the states you did not name are written to `states_to_learn.csv` in the current folder.
## Run the tests
```bash
python -m pytest
```
The tests need no display.
## Continuous integration
`.gitea/workflows/run-tests.yml` runs on every push to `main` and on every pull request: it sets up Python 3.13, installs the project with `python -m pip install -e ".[dev]"`, then runs `ruff`, `mypy`, `yamllint` and `python -m pytest`.
## Build the source documentation
The source uses Doxygen comments. With Doxygen installed:
```bash
doxygen Doxyfile
```
Open `docs/build/doxygen/index.html` in a browser.
## Project layout
| Path | What it is |
| --- | --- |
| `src/us_states_game/` | The game package: constants, game logic and the Turtle screen |
| `src/us_states_game/data/` | `50_states.csv` and `blank_states_img.gif` from the course |
| `tests/` | pytest tests |
| `docs/` | Project documents: business case, plan, milestones, review records |
| `Doxyfile` | Doxygen configuration |
| `pyproject.toml` | Project configuration |
| `.gitea/workflows/run-tests.yml` | Continuous integration |
## License
GNU Affero General Public License v3.0. See [LICENSE](LICENSE).
## Links
- [Repository](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-us-states-game)
- [Issue tracker](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-us-states-game/issues)
- [Wiki](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-us-states-game/wiki)
## Appendix: Project documents
| Document | What it tells you |
| --- | --- |
| [Business Case](docs/business-case.md) | Why the project exists, its scope and success criteria |
| [Stakeholder Analysis](docs/stakeholder-analysis.md) | Who is involved |
| [Project Plan](docs/project-plan.md) | The gateways and their schedule |
| [Milestones](docs/milestones/) | One document per gateway: Go/No-Go criteria and tasks |
| [Artifact registry](docs/artifact-registry.md) | Where each document type lives |
+1 -1
View File
@@ -16,7 +16,7 @@ document of a type. `Primary File` may contain a glob (e.g.
| PP | Project Plan | docs/project-plan.md | 002 |
| MIL | Milestone / Gateway | docs/milestones/*.md | 005 |
| ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0001 |
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 007 |
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 008 |
## Languages
+82
View File
@@ -0,0 +1,82 @@
# SQA Review Record
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-007 |
| CrossReference | [QC-PY-001], [QC-YML-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-13 | Proposed | Claude (AI) for S01 | AI as stand-in for S01 | Initial review record: Go | [22f19c1] |
---
## Artifact Under Review
- Instance reviewed: source and workflow files delivered by MIL-001 (branch mil-001-baseline-setup)
- Checklist used: [QC-PY-001], [QC-YML-001]
- Scope: full review
- Language and domain: n/a (technical type)
- Language reviewer: none
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the author is the same AI)
## Checklist Results
### QC-PY-001
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | Packages, modules, functions, variables, classes and constants follow PEP 8 casing (`snake_case`, `PascalCase`, `UPPER_SNAKE`) (Mandatory) | Pass | Constants are UPPER_SNAKE, functions snake_case; ruff rule set N passes. |
| 2 | Names state purpose in the domain's language; no unexplained abbreviations, no single-letter names outside tiny scopes (Mandatory) | Pass | Names state purpose (STATES_TO_LEARN_FILENAME, NUMBER_OF_STATES). |
| 3 | Code is produced by the project's formatter and passes its linter with no unexplained suppressions (Mandatory) | Pass | `ruff format` leaves files unchanged and `ruff check` passes with no suppressions; configured in pyproject.toml. |
| 4 | Every function and method signature is type-annotated, including `-> None` (Mandatory) | Pass | Both test functions are annotated `-> None`; `mypy --strict` passes. |
| 5 | No bare `except:`, no swallowed exceptions; specific exceptions are raised and the cause is kept (`raise ... from`) (Mandatory) | N-A | No exception handling in this milestone's code. |
| 6 | No mutable default arguments and no shadowed builtins (Mandatory) | Pass | No function arguments with defaults; no builtin shadowed. |
| 7 | Files, locks and connections are managed with context managers (Mandatory) | Pass | Tests read the CSV with `Path.read_text`, which closes the file. |
| 8 | Public modules, classes and functions have docstrings that say what, not how (Optional) | Pass | Module docstrings and `##` Doxygen comments on every constant. |
| 9 | Logging uses `logging`, not `print`; no secrets or personal data in log output (Mandatory) | N-A | No logging or output in this milestone's code. |
| 10 | Classes and operations trace to the Design Class Diagram they implement; deviations are recorded (Mandatory) | N-A | No classes or operations; no Design Class Diagram exists for constants. |
| 11 | Tests exist for new behaviour, are named for the behaviour, and do not depend on order or the network (Mandatory) | Pass | tests/test_constants.py: two tests named for the behaviour; no order or network dependence. |
| 12 | Type checker runs in strict mode without errors; `Any` is justified in a comment (Optional) | Pass | `python -m mypy` strict mode reports no issues. |
| 13 | Dependencies are declared and pinned in the project's dependency file, none unused (Optional) | Pass | No runtime dependencies; dev extra lists pytest, ruff, mypy and yamllint with lower bounds. |
| 14 | Each piece of knowledge (a business rule, constant, format, validation or query) is defined in one place; code is merged only where it expresses the same knowledge, not where it merely looks alike (Mandatory) | Pass | Each path, file name and limit is defined once in constants.py. |
| 15 | Imports point inward: business-rule modules import no framework, database, UI or delivery-mechanism code, and there are no import cycles (Mandatory) | Pass | The package has no imports of UI or framework code. |
| 16 | SOLID holds: a class or module has one reason to change; new behaviour is added by extension, not by editing a type test in several places; subclasses honour the contract of their base; protocols are narrow; high-level code depends on protocols or abstract types and receives its implementations (Mandatory) | Pass | One module with one reason to change; no classes. |
### QC-YML-001
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | The file parses with a safe YAML loader and has no duplicate keys in any mapping (Mandatory) | Pass | yamllint (PyYAML safe loader) parses the workflow and reports no duplicate keys. |
| 2 | Indentation is 2 spaces everywhere, never tabs; lines end with LF; the file ends with one newline and has no trailing spaces (Mandatory) | Pass | 2-space indentation, LF endings enforced by .gitattributes, yamllint default rules pass. |
| 3 | Passes `yamllint` (or the project's YAML linter) with its configuration committed next to the code and no unexplained `disable` comments (Mandatory) | Pass | `yamllint .gitea .yamllint` passes with the committed .yamllint, no disable comments. |
| 4 | A scalar that YAML would coerce to another type is quoted: `yes`, `no`, `on`, `off`, `null`, `~`, numbers with a leading zero, versions such as `1.10`, and values that contain `: ` or ` #` (Mandatory) | Pass | `python-version: "3.13"` is quoted; `on` is allowed by the yamllint truthy rule for workflows. |
| 5 | Only safe, plain types are used: no language-specific tags (`!!python/object`, `!ruby/object`) and no `!include`-style extension unless the consuming tool documents it (Mandatory) | Pass | Plain mappings, sequences and strings only. |
| 6 | No secret is written in the file: no password, token, key or private connection string; a secret is referenced by name and supplied by the consuming tool (Mandatory) | Pass | No secret in the file; the workflow needs none. |
| 7 | Embedded program text (shell, Python, SQL, `jq`, and so on) in a value is at most five lines; a longer one is moved to a file of its own, in its own language, and the YAML calls it (Mandatory) | Pass | Each `run:` value has at most 2 lines. |
| 8 | A block of keys or values repeated in the file or across files is defined once (an anchor, a template, an include or a reusable unit the consumer supports) (Mandatory) | Pass | No repeated block. |
| 9 | Anchors, aliases and merge keys (`&`, `*`, `<<`) are used only where the consuming tool is known to support them (Mandatory) | Pass | No anchors or aliases. |
| 10 | The file validates against the schema of the consuming tool where one exists, and the schema or tool version is stated (Optional) | N-A | No published schema is checked; the workflow is exercised by the host runner. |
| 11 | A header comment states what the file is for, which tool reads it, and where its decision or design is recorded (Mandatory) | Pass | Header comment states purpose, reader and where the design is recorded. |
| 12 | Key names follow the convention of the consuming tool; one case style is used within the file (kebab-case or snake_case, never mixed) (Mandatory) | Pass | kebab-case keys (`runs-on`, `timeout-minutes`) throughout. |
| 13 | Multi-line strings use a block scalar (`\|` or `>`) and state the chomping indicator (`\|-`, `\|+`) where the trailing newline matters (Optional) | Pass | The `run:` block scalars use `|`; trailing newline does not matter. |
| 14 | The file name is lowercase kebab-case with one extension style (`.yml` or `.yaml`) used throughout the repository (Optional) | Pass | `run-tests.yml` is lowercase kebab-case with the .yml extension. |
| 15 | A change is exercised by a check that runs without the real target: a parse and schema test, or a dry run of the consuming tool (Optional) | N-A | The runner is not available locally; yamllint is the local check. |
## Overall Verdict
Go - Mandatory criteria of both checklists pass for the files delivered by MIL-001: constants module, package marker, smoke tests, pyproject and the workflow.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| None | S01 | |
---
[QC-PY-001]: ../../../framework/qc/code/qc-programming-python.md
[QC-YML-001]: ../../../framework/qc/code/qc-yaml.md
[22f19c1]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/025-us-states-game/commit/22f19c188be10b216cc9da6f6f5ff9413aecd285
+40
View File
@@ -0,0 +1,40 @@
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "us-states-game"
version = "0.1.0"
description = "U.S. States Game: name the 50 states on a Turtle map (Udemy 100 Days of Code, day 25)."
readme = "README.md"
requires-python = ">=3.13"
dependencies = []
[project.optional-dependencies]
dev = ["pytest>=8", "ruff>=0.6", "mypy>=1.11", "yamllint>=1.35"]
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
us_states_game = ["data/*.csv", "data/*.gif"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
[tool.ruff]
line-length = 88
src = ["src", "tests"]
[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "B", "D"]
ignore = ["D203", "D213"]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["D"]
[tool.mypy]
strict = true
files = ["src", "tests"]
mypy_path = "src"
+5
View File
@@ -0,0 +1,5 @@
"""The U.S. States Game package: name the 50 states on a Turtle map.
@file __init__.py
@brief Package marker of the game.
"""
+39
View File
@@ -0,0 +1,39 @@
"""Constants of the U.S. States Game: titles, file names and limits.
@file constants.py
@brief Titles, file names and limits used by the game.
Every value the game needs in more than one place lives here, so the
game logic and the Turtle screen never repeat a literal.
"""
from pathlib import Path
## Folder of this package.
PACKAGE_DIR = Path(__file__).resolve().parent
## Folder with the course files the game reads at run time.
DATA_DIR = PACKAGE_DIR / "data"
## CSV file with the columns state, x and y for all 50 states.
STATES_CSV_PATH = DATA_DIR / "50_states.csv"
## GIF of the blank U.S. map, registered as a Turtle shape.
MAP_IMAGE_PATH = DATA_DIR / "blank_states_img.gif"
## File written when the game ends, listing the states the player missed.
STATES_TO_LEARN_FILENAME = "states_to_learn.csv"
## Column names in the states CSV file.
STATE_COLUMN = "state"
X_COLUMN = "x"
Y_COLUMN = "y"
## Title of the game window.
SCREEN_TITLE = "U.S. States Game"
## Number of states the player has to name.
NUMBER_OF_STATES = 50
## Text the player types to leave the game early.
EXIT_COMMAND = "exit"
+51
View File
@@ -0,0 +1,51 @@
state,x,y
Alabama,139,-77
Alaska,-204,-170
Arizona,-203,-40
Arkansas,57,-53
California,-297,13
Colorado,-112,20
Connecticut,297,96
Delaware,275,42
Florida,220,-145
Georgia,182,-75
Hawaii,-317,-143
Idaho,-216,122
Illinois,95,37
Indiana,133,39
Iowa,38,65
Kansas,-17,5
Kentucky,149,1
Louisiana,59,-114
Maine,319,164
Maryland,288,27
Massachusetts,312,112
Michigan,148,101
Minnesota,23,135
Mississippi,94,-78
Missouri,49,6
Montana,-141,150
Nebraska,-61,66
Nevada,-257,56
New Hampshire,302,127
New Jersey,282,65
New Mexico,-128,-43
New York,236,104
North Carolina,239,-22
North Dakota,-44,158
Ohio,176,52
Oklahoma,-8,-41
Oregon,-278,138
Pennsylvania,238,72
Rhode Island,318,94
South Carolina,218,-51
South Dakota,-44,109
Tennessee,131,-34
Texas,-38,-106
Utah,-189,34
Vermont,282,154
Virginia,234,12
Washington,-257,193
West Virginia,200,20
Wisconsin,83,113
Wyoming,-134,90
1 state x y
2 Alabama 139 -77
3 Alaska -204 -170
4 Arizona -203 -40
5 Arkansas 57 -53
6 California -297 13
7 Colorado -112 20
8 Connecticut 297 96
9 Delaware 275 42
10 Florida 220 -145
11 Georgia 182 -75
12 Hawaii -317 -143
13 Idaho -216 122
14 Illinois 95 37
15 Indiana 133 39
16 Iowa 38 65
17 Kansas -17 5
18 Kentucky 149 1
19 Louisiana 59 -114
20 Maine 319 164
21 Maryland 288 27
22 Massachusetts 312 112
23 Michigan 148 101
24 Minnesota 23 135
25 Mississippi 94 -78
26 Missouri 49 6
27 Montana -141 150
28 Nebraska -61 66
29 Nevada -257 56
30 New Hampshire 302 127
31 New Jersey 282 65
32 New Mexico -128 -43
33 New York 236 104
34 North Carolina 239 -22
35 North Dakota -44 158
36 Ohio 176 52
37 Oklahoma -8 -41
38 Oregon -278 138
39 Pennsylvania 238 72
40 Rhode Island 318 94
41 South Carolina 218 -51
42 South Dakota -44 109
43 Tennessee 131 -34
44 Texas -38 -106
45 Utah -189 34
46 Vermont 282 154
47 Virginia 234 12
48 Washington -257 193
49 West Virginia 200 20
50 Wisconsin 83 113
51 Wyoming -134 90
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

+17
View File
@@ -0,0 +1,17 @@
"""Smoke test: the constants point at the assignment files that ship with the game."""
from us_states_game import constants
def test_data_files_exist() -> None:
assert constants.STATES_CSV_PATH.is_file()
assert constants.MAP_IMAGE_PATH.is_file()
def test_states_csv_has_all_states() -> None:
lines = constants.STATES_CSV_PATH.read_text(encoding="utf-8").strip().splitlines()
assert (
lines[0]
== f"{constants.STATE_COLUMN},{constants.X_COLUMN},{constants.Y_COLUMN}"
)
assert len(lines) - 1 == constants.NUMBER_OF_STATES