woggioni 7bb6b74fdc
CI / Build and push docker image (push) Successful in 1m45s
Add briscola: classic two-player and briscola a quattro
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
2026-09-18 19:18:04 +08:00

tavolo

A platform for multiplayer card games. Two games are included: scopone scientifico — the four-player, fixed-partnership Italian card game — and briscola, in its classic two-player and four-player partnership (briscola a quattro) variants:

  • server/ — backend: Python + kaya framework, OIDC login, live games in Redis, match statistics in Postgres. A monorepo of four distributions sharing the tavolo namespace: the game-independent tavolo-platform, the tavolo-scopone and tavolo-briscola games, and the tavolo-app composition root wiring them together. See server/README.md.
  • web/ — frontend: Rust + Sycamore compiled to WebAssembly, built with Trunk and styled with Bootstrap 5.3 (MIT, vendored under web/vendor/ so builds work offline) plus the custom felt theme in web/theme.css. Card images are the CC0 woodcut napoletane deck traced from a 1902 Naples print (SONDLecT/woodcut-napoletane).

Run the whole stack

docker compose up --build

Postgres, Redis, a mock OIDC provider (test users alice, bob, carol, dave), the database migrator and the app all come up together. The app — frontend and API — listens on http://127.0.0.1:8080.

Because both the browser and the app talk to the OIDC issuer at http://mockoauth:8180/tavolo, add a host entry once:

echo "127.0.0.1 mockoauth" | sudo tee -a /etc/hosts

Between hands

When a hand ends but the match is not decided, the game pauses on a scoring summary screen: scopone players see how each category was won (carte, denara, settebello, primiera, scope — plus napola and asso pigliatutto when enabled), briscola players see the deal's points and any patta; both see the running totals and must click "Understood" before the next hand is dealt. If someone is away the next hand is dealt automatically after HAND_ACK_TIMEOUT_SECONDS (default 30s). The match-ending hand is explained on the final screen.

On your turn

Every turn shows a countdown (TURN_TIMEOUT_SECONDS, default 30s). If a player does not move — disconnected or fallen asleep — the server plays a random legal card for them (choosing among the legal captures when the rules require a capture), so one absent player cannot stall the table.

Watching

Anyone who has the game URL (/game/<id>) can watch, logged in or not: spectators see the table, the score and every player's captured cards (and scope for scopone), but never a hand and never the join code. The "Copy watch link" button in the waiting room and in the game HUD copies the URL to share. A spectator who opens the URL before the match starts sees a "game not started yet" waiting room.

Development

Backend (from server/):

cd server
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/pip install -e ./packages/tavolo-platform -e ./packages/tavolo-scopone -e ./packages/tavolo-briscola -e './packages/tavolo-app[dev]'
.venv/bin/python -m unittest discover -s tests -t .
.venv/bin/python -m unittest discover -s packages/tavolo-platform/tests
.venv/bin/python -m unittest discover -s packages/tavolo-scopone/tests
.venv/bin/python -m unittest discover -s packages/tavolo-briscola/tests

Frontend (from web/), with the backend running on :8080:

cd web
trunk serve          # SPA on http://localhost:8000, /api /auth /ws proxied

Trunk proxies /api, /auth and /ws to 127.0.0.1:8080 (see web/Trunk.toml). For the login redirect to land back on the dev server, run the backend with:

OIDC_0_PROVIDER=default \
OIDC_0_ISSUER=http://localhost:8180/tavolo \
OIDC_0_CLIENT_ID=tavolo \
OIDC_0_REDIRECT_URI=http://localhost:8080/auth/default/callback \
OIDC_0_POST_LOGIN_REDIRECT=http://localhost:8000/ \
OIDC_0_POST_LOGOUT_REDIRECT=http://localhost:8000/ \
  .venv/bin/granian --host 127.0.0.1 --port 8080 tavolo.app:app

Card images are committed under web/assets/cards/; web/fetch-cards.sh re-downloads them if needed.

S
Description
Scopone scientifico multiplayer backend built on the kaya framework
Readme
6 MiB
Languages
Python 73.5%
Rust 23.5%
CSS 1.9%
Dockerfile 0.6%
JavaScript 0.2%
Other 0.3%