# tavolo A platform for multiplayer card games. The first game is **scopone scientifico** — the four-player, fixed-partnership Italian card game: - **`server/`** — backend: Python + [kaya](https://github.com/woggioni/kaya) framework, OIDC login, live games in Redis, match statistics in Postgres. A monorepo of three distributions sharing the `tavolo` namespace: the game-independent `tavolo-platform`, the scopone game `tavolo-scopone`, 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**: every player sees how each category was won (carte, denara, settebello, primiera, scope — plus napola and asso pigliatutto when enabled) with 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 (randomizing 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/`) can watch, logged in or not: spectators see the table, the score and every player's captured cards and scope, 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/`): ```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-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 ``` 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.