Files
tavolo/server/packages/tavolo-platform/README.md
T
woggioni 1ab748a34f
CI / Build and push docker image (push) Successful in 1m19s
Paginate the leaderboard from materialized player ratings
Store each player's leaderboard totals (wins, accumulated points and the
latest display name) on player_rating alongside the Elo rating, updated
in the same transaction as the match, so the board is a single indexed
table query instead of aggregating every participation on each request.

GET /api/leaderboard is now page-number paginated (page/limit) and
returns the page, page count and total, plus the caller's own rank and
page when authenticated; focus=me returns the caller's page directly.
The migration adds the columns, backfills them from the match history
(inserting rows for players from pre-Elo matches at the initial rating)
and replaces the index. backfill_elo rebuilds the aggregates too.

The web page opens on the page containing the signed-in player, marks
their row and adds a numbered footer pager.
2026-10-09 16:42:47 +08:00

69 lines
3.4 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 and leaderboard aggregates per
`(user_sub, game_type)`).
- `stats.py` — `save_match_result` (engine `MatchResult` → Postgres,
transactionally, with Elo and the leaderboard aggregates) and
`apply_match_stats`.
- `elo.py` — chess-style Elo math generalized to two-team matches.
- `backfill_elo.py` — rebuild every rating and aggregate 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 and
page-number 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.