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.
112 lines
4.7 KiB
Markdown
112 lines
4.7 KiB
Markdown
# tavolo
|
||
|
||
A platform for multiplayer card games. Three games are included:
|
||
**scopone scientifico** — the four-player, fixed-partnership Italian card
|
||
game — **briscola**, in its classic two-player and four-player
|
||
partnership (*briscola a quattro*) variants, and **briscola chiamata** —
|
||
the five-player variant with a calling auction and a secret partner:
|
||
|
||
- **`server/`** — backend: Python + [kaya](https://github.com/woggioni/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](server/README.md).
|
||
- **`web/`** — frontend: Rust + [Sycamore](https://sycamore.dev) compiled to
|
||
WebAssembly, built with [Trunk](https://trunkrs.dev) and styled with
|
||
[Bootstrap](https://getbootstrap.com) 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](https://github.com/SONDLecT/woodcut-napoletane)).
|
||
|
||
## Run the whole stack
|
||
|
||
```sh
|
||
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:
|
||
|
||
```sh
|
||
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. Briscola chiamata
|
||
plays a single deal per match, so it goes straight from the last trick to
|
||
the final scoring screen; a 60–60 *patta* is replayed immediately.
|
||
|
||
## 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.
|
||
Briscola chiamata arms the same countdown for its auction bids and for
|
||
the auction winner's called-card choice (auto-bid or auto-pass, then a
|
||
random suit of the won rank).
|
||
|
||
## 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. In
|
||
briscola chiamata they also see no role labels and no side totals until
|
||
the called card is played, so the secret partner stays secret. 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/`):
|
||
|
||
```sh
|
||
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:
|
||
|
||
```sh
|
||
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:
|
||
|
||
```sh
|
||
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.
|