diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 085b93a..e2ac887 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ uv run python examples/demo.py # run the demo scenario uv run uvicorn mundane.api.app:app --reload # serve the API; OpenAPI docs at /schema ``` -The README has the endpoint table and `curl` examples for exercising the API. +The [API docs](docs/api.rst) have the endpoint table and `curl` examples for exercising the API. ## Development workflow diff --git a/README.md b/README.md index c9ea9de..9de6cc8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# mundane +# Mundane, backened + +*Part of https://github.com/letsbuilda/mundane* No dragons. No spells. Just Tuesday. @@ -7,110 +9,3 @@ Two households face off. The engine is a **referee**: a whole game is a fold ove current state and then transitions it. Illegal moves are rejected (the state is left untouched), not crashed on. The HTTP API is a thin shell that translates requests into engine actions; all the rules live in the engine. - -The rules and card catalog live in the [`mundane`](https://github.com/letsbuilda/mundane) meta/spec -repo: - -- The rules, in human-readable form: - [`game-docs/SPEC.md`](https://github.com/letsbuilda/mundane/blob/main/game-docs/SPEC.md) -- The card catalog: - [`game-docs/CARDS.md`](https://github.com/letsbuilda/mundane/blob/main/game-docs/CARDS.md) - -## Layout - -``` -src/mundane/ - engine/ # the game, with no HTTP knowledge - state.py # Card, CardType, Player, StackItem, GameState - actions.py # PlayCard, CastInstant, PassPriority, IllegalAction - rules.py # apply_action + helpers — the one door - cards.py # CARD_LIBRARY (id -> Card) + effect functions - serialize.py # state/action -> JSON-ready data - game.py # Game: state + action log + submit() / export() - api/ - app.py # Litestar app, in-memory store, exception handler - schemas.py # action JSON (tagged union) -> action dataclasses -examples/ - demo.py # runnable scenario (python examples/demo.py) -``` - -## Requirements - -- Python **3.14+** and [`uv`](https://docs.astral.sh/uv/). -- Litestar 3 is still in development; it is tracked from `main` and pinned to a commit SHA in - `uv.lock`, so builds are reproducible. - -## Setup - -```bash -uv sync --group tests --group dev # create the venv and install everything -``` - -## Run the demo - -Watch Steve's house party get shut down by Alex's noise complaint: - -```bash -uv run python examples/demo.py -``` - -## Run the API - -```bash -uv run uvicorn mundane.api.app:app --reload -``` - -Interactive OpenAPI docs are served at `http://localhost:8000/schema`. - -### Endpoints - -| Method | Path | Purpose | -|--------|----------------------------|----------------------------------------| -| POST | `/games` | create a game | -| GET | `/games/{id}` | read current state | -| POST | `/games/{id}/actions` | submit a move (422 if illegal) | -| GET | `/games/{id}/export` | download the game log + final state | - -### Exercise it - -```bash -# create a game and capture its id -GID=$(curl -s -X POST localhost:8000/games | python -c 'import sys,json; print(json.load(sys.stdin)["game_id"])') - -# read the current state -curl -s localhost:8000/games/$GID - -# submit a move (the tagged-union body carries a "type" discriminator) -curl -s -X POST localhost:8000/games/$GID/actions \ - -H 'content-type: application/json' \ - -d '{"type": "play_card", "player": 0, "hand_index": 0}' - -# an illegal move is rejected with 422; the stored game is unchanged -curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8000/games/$GID/actions \ - -H 'content-type: application/json' \ - -d '{"type": "cast_instant", "player": 1, "hand_index": 9}' - -# download the game log (saves to mundane-game-$GID.json) -curl -s -OJ localhost:8000/games/$GID/export -``` - -The action body is a tagged union — every action carries a `type`: - -| `type` | fields | -|-----------------|-------------------------------------------------| -| `play_card` | `player`, `hand_index` | -| `cast_instant` | `player`, `hand_index`, optional `target_id` | -| `pass_priority` | `player` | - -### A note on the store - -The game store is an **in-memory dict**, so games are **volatile** — they are lost when the process -restarts. It lives behind a small `GameStore` interface (`create` / `get` / `save`), so swapping it -for Redis or SQLite later is a localised change. Persistence beyond memory is out of scope for the MVP. - -## Develop - -```bash -uv run pytest # tests -uv run nox -s lints # ruff format + ruff check + mypy + ty -``` diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..700e6a7 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,70 @@ +API +=== + +Serve the API locally: + +.. code-block:: bash + + uv run uvicorn mundane.api.app:app --reload + +Interactive OpenAPI docs are served at ``http://localhost:8000/schema``. + +Endpoints +--------- + +.. list-table:: + :header-rows: 1 + + * - Method + - Path + - Purpose + * - POST + - ``/games`` + - create a game + * - GET + - ``/games/{id}`` + - read current state + * - POST + - ``/games/{id}/actions`` + - submit a move (422 if illegal) + * - GET + - ``/games/{id}/export`` + - download the game log + final state + +Exercise it +----------- + +.. code-block:: bash + + # create a game and capture its id + GID=$(curl -s -X POST localhost:8000/games | python -c 'import sys,json; print(json.load(sys.stdin)["game_id"])') + + # read the current state + curl -s localhost:8000/games/$GID + + # submit a move (the tagged-union body carries a "type" discriminator) + curl -s -X POST localhost:8000/games/$GID/actions \ + -H 'content-type: application/json' \ + -d '{"type": "play_card", "player": 0, "hand_index": 0}' + + # an illegal move is rejected with 422; the stored game is unchanged + curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8000/games/$GID/actions \ + -H 'content-type: application/json' \ + -d '{"type": "cast_instant", "player": 1, "hand_index": 9}' + + # download the game log (saves to mundane-game-$GID.json) + curl -s -OJ localhost:8000/games/$GID/export + +The action body is a tagged union — every action carries a ``type``: + +.. list-table:: + :header-rows: 1 + + * - ``type`` + - fields + * - ``play_card`` + - ``player``, ``hand_index`` + * - ``cast_instant`` + - ``player``, ``hand_index``, optional ``target_id`` + * - ``pass_priority`` + - ``player`` diff --git a/docs/index.rst b/docs/index.rst index a262372..4f2f616 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -68,6 +68,7 @@ The full module reference is generated from the source. .. toctree:: :maxdepth: 1 + api autoapi/index .. toctree::