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.
101 lines
5.1 KiB
Markdown
101 lines
5.1 KiB
Markdown
# tavolo-briscola
|
||
|
||
Briscola — the Italian trick-taking game — as a
|
||
[`tavolo-platform`](../tavolo-platform/README.md) game implementation.
|
||
Three variants share one rules core:
|
||
|
||
- `briscola` — the classic two-player game;
|
||
- `briscola_a_quattro` — four-player fixed-partnership briscola;
|
||
- `briscola_chiamata` — five-player briscola with a calling auction and a
|
||
secret partner.
|
||
|
||
## Contents
|
||
|
||
- `state.py` — `BriscolaState` (pure game data: phases, players, hands,
|
||
stock, face-up briscola, tricks, scores, deadlines), `PlayerState`,
|
||
`Card`, `TrickCard`, `Trick`, with JSON (de)serialization. No session
|
||
envelope, no transport, no I/O.
|
||
- `engine.py` — the pure rules engine for two- and four-player briscola:
|
||
deck, deals, trick resolution (trump first, then the led suit; strength
|
||
ace > three > king > horse > jack > 7 > 6 > 5 > 4 > 2), the draw order
|
||
(trick winner first, the face-up briscola last), card points (ace 11,
|
||
three 10, king 4, horse 3, jack 2), patta re-deals and the
|
||
target-score match end, deal-end acknowledgements and auto-play.
|
||
Deterministic and I/O-free apart from logging, so the whole rule set is
|
||
unit-testable.
|
||
- `chiamata.py` — the pure rules engine for five-player briscola
|
||
chiamata: `ChiamataState`/`ChiamataPlayer`/`Auction` (JSON
|
||
round-trip), the 8-card deal with no stock, the descending calling
|
||
auction with point raises, the winner's call (including calling one's
|
||
own card, which means playing alone), the called-card reveal, single-
|
||
deal scoring with the 60–60 patta and the auto-actions for timeouts.
|
||
- `errors.py` — briscola-specific errors (`CardNotInHand`); every other
|
||
failure mode is a shared `tavolo.platform.errors` subclass.
|
||
- `plugin.py` — `BriscolaEngine`, `BriscolaQuattroEngine` and
|
||
`BriscolaChiamataEngine` (`GameEngine`): the platform-facing adapters.
|
||
The first two validate the `target_score` creation option (61–120,
|
||
default 61) and extract the `MatchResult` (teams, winner, per-player
|
||
scores and the match summary persisted as the match's JSON `result`).
|
||
Briscola chiamata takes no creation options.
|
||
|
||
Timeouts are constructor arguments (`turn_timeout_seconds`,
|
||
`hand_ack_timeout_seconds`), wired from the environment by the
|
||
application composition root. Briscola chiamata uses the turn timeout
|
||
for its auction turns, the called-card choice and the plays: an absent
|
||
bidder passes, the last player standing with no bid is made to bid the
|
||
ace, and an absent winner's call names a random suit of the won rank. It
|
||
never enters the between-deals acknowledgement phase (a single deal ends
|
||
the match), so the hand-ack timeout is accepted but unused.
|
||
|
||
## Rules
|
||
|
||
### Briscola (two-player and four-player)
|
||
|
||
- Three cards per player; the next card is turned face up: its suit is
|
||
the trump (briscola) for the deal and it is the last card drawn.
|
||
- The winner of a trick is the highest trump, or the highest card of the
|
||
led suit when no trump was played. The winner takes the trick and leads
|
||
the next one.
|
||
- After each trick everyone draws back up to three cards, starting with
|
||
the trick winner.
|
||
- Card points total 120 per deal; a 60–60 deal is a *patta*, its points
|
||
are discarded and the deal is replayed. The first team to the target
|
||
score wins; a tie at or above the target is broken by another deal.
|
||
- Between deals of an unfinished match every player acknowledges the
|
||
summary (or the hand-end timeout deals on), exactly like scopone.
|
||
|
||
### Briscola chiamata (five-player)
|
||
|
||
- The whole 40-card deck is dealt out, eight cards each: there is no
|
||
stock and no face-up card.
|
||
- Starting with the player after the dealer, each player either passes
|
||
(leaving the auction for good) or bids a **lower** rank than the
|
||
standing bid on the ladder *A > 3 > K > H > J > 7 > 6 > 5 > 4 > 2*;
|
||
the first bidder may bid any rank. Once the standing bid is the two,
|
||
the auction continues on points instead (62, 64, …): the calling side
|
||
then has to reach the raised target. The last player standing must
|
||
bid, so a deal always gets a caller.
|
||
- The auction winner names any card of the winning rank. If the winner
|
||
holds it, they **play 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 for the deal. The partner's
|
||
identity is hidden until the called card is played.
|
||
- Play follows briscola: the player after the dealer leads, the highest
|
||
trump (or highest led-suit card) takes the trick and leads the next
|
||
one; eight tricks exhaust every hand.
|
||
- The calling side (caller plus partner, or the lone caller) must reach
|
||
the declared target — 61 unless the auction raised it. 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 two sides map onto the platform's
|
||
two-team result (Elo rating, match history).
|
||
|
||
## Development (from `server/`)
|
||
|
||
```sh
|
||
uv run python -m unittest discover -s packages/tavolo-briscola/tests
|
||
uv run mypy -p tavolo.briscola
|
||
```
|
||
|
||
`test_engine.py`/`test_plugin.py` cover the two classic variants;
|
||
`test_chiamata.py`/`test_chiamata_plugin.py` cover briscola chiamata.
|