Add briscola chiamata
CI / Build and push docker image (push) Successful in 5m35s

Five-player briscola with a descending calling auction and a secret
partner. Starting with the player after the dealer, each player bids a
lower rank (A > 3 > K > H > J > 7 > 6 > 5 > 4 > 2) or passes; once the
standing bid is the two the auction continues on points (62, 64, ...).
The last player standing must bid, and the winner names any card of the
won rank: its holder is the secret partner, or the caller plays alone
when it is one of their own cards, and its suit becomes the trump. The
calling side must reach 61 (or the raised target); a 60-60 deal with the
plain target is a patta and is replayed. A match is a single deal, so
the caller and partner (or the lone caller) against the other players
map onto the platform's two-team result, Elo and match history.

The rules live in tavolo.briscola.chiamata with the
BriscolaChiamataEngine adapter registered alongside the other briscola
variants; the partner's role and the side totals stay hidden until the
called card is played. The web client gets a five-seat table, the
auction panel with legal bids and point raises, the call picker, the
reveal bands and a chiamata-aware final summary.
This commit is contained in:
2026-10-08 21:50:56 +08:00
parent 6427afd12b
commit 353f012906
21 changed files with 4036 additions and 73 deletions
+56 -7
View File
@@ -16,9 +16,10 @@ and
scientifico, the four-player, fixed-partnership variant of the classic
Italian card game: pure rules plus the platform adapter.
- **`tavolo-briscola`** (`packages/tavolo-briscola/`) — briscola, the
Italian trick-taking game, in its classic two-player (`briscola`) and
four-player partnership (`briscola_a_quattro`) variants: pure rules
plus the platform adapter.
Italian trick-taking game, in its classic two-player (`briscola`),
four-player partnership (`briscola_a_quattro`) and five-player
called-partner (`briscola_chiamata`) variants: pure rules plus the
platform adapter.
- **`tavolo-app`** (`packages/tavolo-app/`) — the
composition root wiring the platform to the games, with the
environment-driven settings and the SPA shell.
@@ -236,7 +237,9 @@ aggregate with `max`, not `sum`.
as reported by the engine (`result` JSON — for scopone: both teams'
final scores, winner, target score, hands played and the per-hand
audit; for briscola: the final scores, winner, target score, deals
played and the per-deal audit).
played and the per-deal audit; for briscola chiamata: the calling side's
points against the defenders', the declared target, the called card and
whether the caller played alone).
- `match_player` — one row per participant: the OIDC `sub`, display name,
seat, game-defined team label (nullable), whether they won, the points
they scored, the Elo change the match produced (`elo_delta`) and
@@ -275,7 +278,7 @@ All endpoints except `/api/health`, `/api/docs`, `/api/openapi.json`,
| Method | Path | Description |
|---|---|---|
| `GET` | `/api/game-types` | The games the platform can host (for the creation dropdown), each with its `options_schema` |
| `POST` | `/api/games` | Create a lobby game. Optional body `{"game_type": "scopone_scientifico", "options": {"target_score": 11, "napola": true, "asso_pigliatutto": false}}`; briscola games use `{"game_type": "briscola"}` or `{"game_type": "briscola_a_quattro"}` with an optional `{"target_score": 61}`. Returns the lobby (`{id, join_code, players, seats_open, ...}`) |
| `POST` | `/api/games` | Create a lobby game. Optional body `{"game_type": "scopone_scientifico", "options": {"target_score": 11, "napola": true, "asso_pigliatutto": false}}`; briscola games use `{"game_type": "briscola"}` or `{"game_type": "briscola_a_quattro"}` with an optional `{"target_score": 61}`, and briscola chiamata uses `{"game_type": "briscola_chiamata"}` with no options. Returns the lobby (`{id, join_code, players, seats_open, ...}`) |
| `POST` | `/api/games/join` | Join with `{"code": "ABC123"}`. The last player triggers the start and gets the game view back |
| `GET` | `/api/games/preview?code=ABC123` | Pre-join summary of the lobby behind a code: game name/description, seated players, open seats and the engine-echoed options. `404` when the code is unknown |
| `GET` | `/api/games/{id}` | Game snapshot. Seated players get the personalized view (own hand plus the join code); anyone else gets the read-only spectator view: table, scores, captured counts and scope, no hands and no join code |
@@ -341,6 +344,24 @@ For briscola (`briscola`, `briscola_a_quattro`) the game actions are
every player has acknowledged, or automatically after
`HAND_ACK_TIMEOUT_SECONDS`.
For briscola chiamata the game actions are the calling auction plus play:
```json
{"action": "bid", "rank": 3}
{"action": "bid", "points": 64}
{"action": "pass"}
{"action": "call", "card": "03D"}
{"action": "play", "card": "04C"}
```
- `bid` with a `rank` calls a lower rank than the standing bid
(`A > 3 > K > H > J > 7 > 6 > 5 > 4 > 2`); with `points` it raises the
target once the standing bid is the two.
- `pass` leaves the auction; the last player standing must bid.
- `call` names any card of the won rank; naming one of your own cards
means playing alone.
- `play` follows briscola: any card in hand is legal.
After every accepted move the new state is broadcast to every connected
client, spectators included.
@@ -353,7 +374,10 @@ is required), so a disconnected or idle player cannot stall the match. The
timeout is `TURN_TIMEOUT_SECONDS` (default 30); the auto-played move is
broadcast like any other. Deadlines fire from the shared `tavolo:deadlines`
queue (see above), not from timers tied to client connections, so the
match keeps progressing even with every player disconnected.
match keeps progressing even with every player disconnected. Briscola
chiamata uses the same timeout for its auction bids (auto-bid or
auto-pass) and for the called-card choice (a random suit of the won
rank).
### Hand-end summary
@@ -416,6 +440,30 @@ player must dismiss. A play attempted in this phase is rejected with an
four players in two fixed partnerships (seats 0/2 vs 1/3, partners
opposite).
### Briscola chiamata
- 40-card Italian deck, eight cards each for five players: the whole deck
is dealt, so there is no stock and no face-up card.
- The calling auction starts with the player after the dealer: each
player either passes (leaving the auction) or bids a lower rank than
the standing bid (*A > 3 > K > H > J > 7 > 6 > 5 > 4 > 2*); once the
standing bid is the two, the auction continues on points (62, 64, …).
The last player standing must bid, so every deal gets a caller.
- The winner names any card of the won rank. If the winner holds it, the
caller plays alone against the other four; otherwise its holder is the
secret partner and the two form the calling side. The called card's
suit is the trump; the partner's identity stays hidden until the
called card is played.
- Play and trick resolution follow briscola; eight tricks exhaust every
hand. The calling side must reach the declared target (61 unless
raised); with the plain target a 60–60 deal is a *patta* and is
replayed with the next dealer. A match is a single deal, so the caller
and partner (or the lone caller) against the other players map onto
the platform's two-team result, Elo and history.
- Auction bids, the called-card choice and the plays all have their own
`TURN_TIMEOUT_SECONDS` deadlines (an absent player is auto-bid,
auto-called or auto-played for).
## Development
```sh
@@ -493,8 +541,9 @@ packages/tavolo-briscola/ # briscola game (own pyproject + tests)
src/tavolo/briscola/
├── state.py # BriscolaState / PlayerState / Card / Trick, JSON (de)serialization
├── engine.py # pure briscola rules (2- and 4-player)
├── chiamata.py # pure briscola chiamata rules (5-player, auction, secret partner)
├── errors.py # briscola-specific rule violations
└── plugin.py # BriscolaEngine + BriscolaQuattroEngine adapters
└── plugin.py # BriscolaEngine + BriscolaQuattroEngine + BriscolaChiamataEngine
packages/tavolo-app/ # deployable app: composition root (own pyproject)
src/tavolo/
├── app.py # session/OIDC/Tortoise/OpenAPI/Platform/DeadlineScheduler mixins