After each hand of an unfinished match the game now pauses in a new
hand_end phase instead of dealing immediately:
- engine: hand_points gains an 'award' map (which team won each category),
_end_hand stops at hand_end with a deadline, new acknowledge_hand deals
the next hand once all four players have acked; plays are rejected while
the summary is up
- state: acked seats, hand_end_deadline and hand_ack_timeout are persisted
and exposed in the personalized view (also on the finished state, so the
final hand is explained before the result)
- ws: new {"action": "ack"}; a per-hand timer force-deals the next hand
after HAND_ACK_TIMEOUT_SECONDS (new env var, default 30s) so an away
player cannot stall the match
- web: modal explaining each category in plain language with icons (card
images for denara/settebello/primiera), team-coloured rows, running
totals with progress bars, an 'Understood — next hand' button that turns
into 'Waiting for …' plus an auto-continue countdown; the final screen
shows the last hand's breakdown too
Verified in the browser against the compose stack: hand played to
completion, summary rendered (including a carte tie), ack from all four
players dealt the next hand live, and the auto-continue path fired when
nobody acked. 60 backend tests + mypy + cargo tests green.
72 lines
2.3 KiB
Markdown
72 lines
2.3 KiB
Markdown
# scopa
|
|
|
|
Multiplayer **scopone scientifico** — the four-player, fixed-partnership
|
|
Italian card game — as a web application:
|
|
|
|
- **`server/`** — backend: Python + [kaya](https://github.com/woggioni/kaya)
|
|
framework, OIDC login, live games in Redis, match statistics in Postgres.
|
|
See [server/README.md](server/README.md).
|
|
- **`web/`** — frontend: Rust + [Sycamore](https://sycamore.dev) compiled to
|
|
WebAssembly, built with [Trunk](https://trunkrs.dev). 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/scopa`, 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) 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.
|
|
|
|
## Development
|
|
|
|
Backend (from `server/`):
|
|
|
|
```sh
|
|
cd server
|
|
python3 -m venv .venv
|
|
.venv/bin/pip install -r requirements.txt
|
|
.venv/bin/pip install -e '.[dev]'
|
|
.venv/bin/python -m unittest discover -s tests -t .
|
|
.venv/bin/mypy src
|
|
```
|
|
|
|
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_POST_LOGIN_REDIRECT=http://localhost:8000/ \
|
|
OIDC_POST_LOGOUT_REDIRECT=http://localhost:8000/ \
|
|
.venv/bin/granian --host 127.0.0.1 --port 8080 scopa.app:app
|
|
```
|
|
|
|
Card images are committed under `web/assets/cards/`; `web/fetch-cards.sh`
|
|
re-downloads them if needed.
|