Files
tavolo/server/packages/tavolo-platform/README.md
T
woggioni 68200bbe62 Add scopa: classic two-player and scopa a quattro
New tavolo-scopa package with a pure rules engine and the platform
adapters for two-player scopa and four-player fixed-partnership scopa a
quattro: three cards each and four on the table, capture by rank or sum,
sweep for scope and score carte/denara/settebello/primiera.

The optional asso pigliatutto creation option turns either variant into
scopa d'assi (an ace sweeps the whole table and scores no scopa), reusing
scopone's rule semantics.

Wire both engines into the app and the Docker install, add the scopa
table view to the SPA (duo and four-seat layouts, deck count,
captured/scope stats, hand-summary and final screens, player names in the
two-player summary), and cover the rules, the platform contract, the
REST/WebSocket flows, match persistence and Elo.
2026-10-09 14:45:20 +08:00

66 lines
3.3 KiB
Markdown

# tavolo-platform
The game-independent half of tavolo: lobby, live play, match history and
leaderboards. Everything here works for any game that implements the
[`GameEngine`](src/tavolo/platform/engine.py) contract; the package
itself ships no game.
## Contents
- `engine.py` — the platform↔game contract: `GameEngine` (ABC),
`GameSession` (the platform-owned envelope with an opaque `state` blob),
`Seat`, `Deadline` (kind + due time + revalidation token) and the
`MatchResult`/`PlayerResult` outcome types.
- `registry.py` — `GameRegistry`: game id → engine lookup, single source
of truth for which games exist.
- `mixin.py` — `Platform` (registry + store + scheduler + OIDC, the
collaborators every endpoint needs) and `PlatformMixin`, the kaya mixin
that registers all routes and the websocket endpoint on an app.
- `routes/` — `health` (`GET /api/health`), `me` (`GET /api/me`), `games`
(lobby: `GET /api/game-types`, `POST /api/games`, `POST /api/games/join`,
`GET /api/games/{id}`) and `stats` (`GET /api/me/matches`,
`GET /api/leaderboard`, `GET /api/me/ratings`).
- `ws.py` — the live-play websocket (`/ws/games/{id}`): connection
lifecycle, the message envelope and the publish/subscribe fan-out.
Game-specific actions are dispatched to the session's engine.
- `store.py` — live-session persistence: `RedisGameStore` (production)
and `InMemoryGameStore` (tests/dev) over a JSON envelope plus the
engine's opaque state blob, with per-game locks, change signals and the
shared deadline queue.
- `deadlines.py` — `DeadlineScheduler`: enqueue the deadline an engine
declares after each mutation; a per-loop consumer fires due entries
through `engine.fire_deadline` under the per-game lock. Engines
revalidate the token, so stale or duplicate deliveries are harmless.
- `models.py` — `Match` (`game_type`, timestamps, game-specific JSON
`result`), `MatchPlayer` (seat, team, won, points, Elo delta, JSON
`details`) and `PlayerRating` (Elo per `(user_sub, game_type)`).
- `stats.py` — `save_match_result` (engine `MatchResult` → Postgres,
transactionally, with Elo) and `apply_elo`.
- `elo.py` — chess-style Elo math generalized to two-team matches.
- `backfill_elo.py` — rebuild every rating from the match history:
`python -m tavolo.platform.backfill_elo --database-url postgres://…`.
- `auth.py`, `http.py`, `pagination.py`, `openapi.py`,
`tortoise_mixin.py` — OIDC helpers, JSON helpers, keyset pagination,
shared OpenAPI fragments, the TortoiseORM lifecycle mixin.
## Adding a game
Implement `GameEngine` (see `engine.py` for the full contract), register
it in a `GameRegistry`, and mount `PlatformMixin(Platform(...))` on a
`KayaApp` — see the composition root in `tavolo.app`. The canonical
examples are [`tavolo-scopone`](../tavolo-scopone/README.md),
[`tavolo-scopa`](../tavolo-scopa/README.md) and
[`tavolo-briscola`](../tavolo-briscola/README.md).
## Development (from `server/`)
```sh
.venv/bin/python -m unittest discover -s packages/tavolo-platform/tests
.venv/bin/python -m mypy -p tavolo.platform
```
Tests run fully in-process against a `DummyEngine` (a two-player toy game
in `tests/helpers.py`): in-memory sqlite, in-memory stores, a patched
OIDC user and `httpx` / `httpx-ws` ASGI transports. No test in this
package may import a real game.