CI / Build and push docker image (push) Successful in 3m0s
Anonymous users and logged-in non-players can now open /game/<id> and
follow the match: they see the table, the score and each player's
captured cards and scope, but never a hand and never the join code.
Spectator actions are rejected with a forbidden error.
Backend (tavolo-platform / tavolo-scopone):
- /ws/games/{id} accepts anonymous and non-seated connections as
spectators; only unknown games are refused. The state payload carries
spectator: true and withholds join_code unless the viewer is seated.
- GET /api/games/{id} is public and returns the same personalized
(player) or read-only (spectator) view.
- GameEngine.view_for/game_over_view take Optional[str] (None = spectator)
and are documented to hide privileged information for non-seated
viewers.
Frontend (Sycamore):
- Parse the spectator flag and render a spectator table: card backs plus
captured/scope for every seat, no clickable hand, "Spectating" badge.
- A pre-start spectator sees a "game not started yet" waiting room with
the watch link and no join code.
- "Copy watch link" buttons in the waiting room and the game HUD.
- The hand-end summary carries no acknowledge button for spectators and
the game-over overlay hides "My matches".
Tests and docs updated.
tavolo-scopone
Scopone scientifico — the four-player, fixed-partnership Italian card
game — as a tavolo-platform game
implementation.
Contents
state.py—ScoponeState(pure game data: phases, players, hands, table, scores, deadlines),PlayerState,Card,Move, with JSON (de)serialization. No session envelope, no transport, no I/O.engine.py— the pure rules engine: deck, legal captures, plays, auto-play, hand scoring (carte, denara, settebello, primiera, scope, napola), the optional asso pigliatutto capture, hand-end acknowledgements. Deterministic and I/O-free apart from logging, so the whole rule set is unit-testable.errors.py— scopone-specific errors (CardNotInHand); every other failure mode is a sharedtavolo.platform.errorssubclass.plugin.py—ScoponeEngine(GameEngine): the platform-facing adapter. It translates create/join/websocket actions/deadlines into rules-engine calls and back, validates thetarget_score/napola/asso_pigliatuttocreation options, and extracts theMatchResult(teams, winner, per-player scores and the match summary persisted as the match's JSONresult).
Timeouts are constructor arguments (turn_timeout_seconds,
hand_ack_timeout_seconds), wired from the environment by the
application composition root.
Development (from server/)
.venv/bin/python -m unittest discover -s packages/tavolo-scopone/tests
.venv/bin/python -m mypy -p tavolo.scopone
test_engine.py covers the pure rules; test_plugin.py covers the
platform contract (actions, deadlines, serialization, results).