Files
tavolo/server/packages/tavolo-platform
woggioni 2d5c64218f
CI / Build and push docker image (push) Successful in 2m42s
Migrate the backend from pip to uv
Replace requirements.txt with a uv workspace and lockfile, install the five tavolo packages from packages/* in development and non-editable in the image, and build the Docker image with uv. Move dev tools to a PEP 735 dependency group and refresh the locked dependency versions.
2026-10-20 07:25:25 +08:00
..
2026-10-01 15:39:32 +00:00
2026-10-20 07:25:25 +08:00

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 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 and response schemas, 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-scopa and tavolo-briscola.

Development (from server/)

uv run python -m unittest discover -s packages/tavolo-platform/tests
uv run 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.