CI / Build and push docker image (push) Successful in 1m45s
New tavolo-briscola distribution implementing briscola on the platform: - pure rules (state.py, engine.py): 3-card deals, a face-up trump that is the last card drawn, trump-first trick resolution (A > 3 > K > H > J > 7 > 6 > 5 > 4 > 2), 120 points per deal, 60-60 patta re-deals, a configurable target score (default 61), deal-end acknowledgements and turn auto-play; - BriscolaEngine (briscola, 2 players) and BriscolaQuattroEngine (briscola_a_quattro, 4 players in fixed partnerships) adapters; - registered in tavolo-app, packaged in the Docker image, documented. Platform fixes surfaced by a two-player lobby: - the websocket state and snapshot payloads now carry seats_open (they were only in the REST lobby payload), so lobbies with player counts other than four render correctly; seats_open is clamped at zero; - the web client logs unparseable server messages instead of silently dropping them. Web client: briscola table for both variants (stock, face-up trump, trick, last-trick banner), deal summary modal and game-over overlay. Tests: tavolo-briscola rules/adapter suites, app integration tests (lobby, websocket play, turn timeout, deal-end acks, persistence), a platform seats_open test and frontend model parsing tests.
65 lines
3.2 KiB
Markdown
65 lines
3.2 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) 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.
|