Commit Graph
11 Commits
Author SHA1 Message Date
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
woggioni 7bb6b74fdc Add briscola: classic two-player and briscola a quattro
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.
2026-10-06 14:08:25 +08:00
woggioni 5be4926885 Bundle kaya-otel in the Docker image
CI / Build and push docker image (push) Successful in 1m57s
The tavolo-app otel extra was never installed: requirements.txt was
locked without it and the Dockerfile installs the local distributions
with --no-deps, so any container with OTEL_ENABLED set died at import
with "kaya-otel is not installed".

Add the extra's full closure (kaya-otel plus its OpenTelemetry/protobuf
dependencies) to the pinned lock and refresh the comments that claimed
the extra had to be added separately.
2026-09-26 15:47:16 +08:00
woggioni b867dd1619 Copy the vendored Bootstrap theme into the web builder stage
CI / Build and push docker image (push) Successful in 1m24s
The Dockerfile copies the frontend inputs explicitly, so the new
theme.css and vendor/ directory were missing: trunk failed to resolve
them and the image build stopped in the web-builder stage. Copy both
into the build context; the runtime image is unchanged.
2026-09-21 23:50:56 +08:00
woggioni 2b738ea2fe Move tavolo-app under packages/ for a uniform monorepo layout
CI / Build and push docker image (push) Successful in 2m29s
Relocate the composition root (app, config, static, logging_config,
aerich_config) from server/src/tavolo/ to
packages/tavolo-app/src/tavolo/, with its own pyproject.toml and README
like the sibling distributions. Module paths and the granian entrypoint
are unchanged.

server/pyproject.toml remains as a tooling shim ([tool.aerich] next to
./migrations, [tool.mypy] for runs from server/); the Dockerfile installs
all three distributions from ./packages. Verified: all suites green,
mypy clean per package, aerich resolves the moved config identically,
wheel builds.
2026-09-21 13:04:09 +08:00
woggioni 4b66407fa4 Split backend into tavolo-platform, tavolo-scopone and tavolo-app packages
Move the game-independent machinery (lobby, live-game store, websocket,
deadline scheduler, match history, leaderboards) into a new
tavolo-platform distribution behind a GameEngine contract, the scopone
scientifico rules plus a platform adapter into tavolo-scopone, and keep
only the composition root in tavolo-app. The three distributions share
the tavolo namespace (PEP 420, kaya-style monorepo).

Match history becomes fully generic: Match carries the engine's result
JSON and MatchPlayer points/details instead of scopone-shaped team
columns (migration 3 backfills existing rows). Lobby creation takes an
opaque per-game options object and websocket actions dispatch to the
session's engine.

Tests: platform suite runs against a DummyEngine toy game, scopone
keeps the rules tests plus new adapter tests, server/tests covers the
wired stack end to end (194 tests, was 143).
2026-09-21 13:04:08 +08:00
woggioni ebeccc937d optimized dockerfile for caching
CI / Build and push docker image (push) Successful in 1m14s
2026-09-18 19:28:33 +08:00
woggioni 876b4abd8b Serve static assets with Granian and add YAML-configurable logging
Granian serves the compiled SPA assets directly in Rust: hashed js/wasm/css
under /static (the release build uses --public-url /static/) and the card
images under /assets, configured with the GRANIAN_STATIC_PATH_ROUTE/MOUNT/
DIR_TO_FILE env vars in the Dockerfile. The Python catch-all now only serves
the SPA shell (index.html) at / and for client-side routes.

Every module logs through getLogger(__name__): lifecycle and business events
at INFO, per-move and store detail at DEBUG. The built-in default writes
DEBUG to the console; LOGGING_CONFIG points at a YAML file in the
logging.config.dictConfig schema to take over the configuration. PyYAML
becomes a direct dependency.
2026-09-17 08:26:45 +08:00
woggioni 6932a3272c Rename the app from scopa to tavolo
CI / Build and push docker image (push) Successful in 3m12s
The platform now hosts multiple card games, with scopone scientifico as
the first one. Rename the brand wherever it is not a game rule:

- move the Python package to server/src/tavolo and update imports
- rename the Postgres database/user, OIDC issuer path, client id and
  Redis key prefixes to tavolo (clean break: existing pgdata volumes and
  live games are not migrated)
- rename the Cargo package to tavolo-web and set the page title to Tavolo
- update docs and the Docker image path to woggioni/tavolo

The scopa game term (clearing the table) in the engine, state and web UI
is intentionally left untouched.
2026-09-16 21:46:06 +08:00
woggioni 8414831849 web: fix OIDC navigation intercepted by the SPA router; tidy table layout
- Login/Logout anchors now carry rel="external": sycamore-router
  intercepts same-origin anchor clicks otherwise, so the OIDC login
  round-trip never left the SPA (found with a real browser test)
- Opponents' hidden hands render as a tidy 5x2 grid of card backs, seats
  have a fixed height and shrink-wrap so the table no longer jumps as
  cards are played
- Dockerfile: cache the Rust target dir so frontend rebuilds are fast
- ignore .playwright-mcp/ tooling output
2026-09-16 13:20:06 +08:00
woggioni 96a95d74b6 Add Sycamore/WASM frontend and restructure into server/ + web/
Repo is now a monorepo:

- server/: the kaya backend, unchanged in behaviour, plus:
  - GET /api/me for SPA session detection
  - last_move recorded on every play and broadcast in the game state, so
    clients can show who played which card the moment they play it
  - legal_moves per hand card for the player on turn (rules stay
    server-side)
  - static catch-all route serving the compiled SPA with index.html
    fallback; Tortoise context now bound only for /api/* requests
  - configurable OIDC post-login/logout redirects for dev against trunk
- web/: Sycamore 0.9 + WASM frontend (trunk): login via the OIDC flow,
  lobby (create match / join by code), live game page over websocket with
  card images (CC0 woodcut napoletane deck), capture picker, move banner,
  game-over overlay, match history and leaderboard pages
- server/Dockerfile gains a rust+trunk stage building web/dist; the single
  app image serves the SPA; compose builds from the repo root with
  overridable ports/OIDC env

Verified end-to-end against the compose stack: four OIDC logins, game
creation, three joins by code, websocket play with broadcasts to all
players and out-of-turn rejection. 51 backend tests, mypy and cargo tests
all green.
2026-09-16 13:20:05 +08:00