feat(game): official first-move tile draw + admin step-by-step replay
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 17s
CI / ui (pull_request) Has been skipped
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 17s
CI / ui (pull_request) Has been skipped
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s
Decide who moves first by the official rule: each seated player draws one tile, the one closest to "A" leads (a blank beats every letter), ties re-drawing until a single leader remains. Each draw uses honest per-draw crypto/rand entropy (not the deterministic bag seed), so the recorded draw — not a seed — is the only account of the outcome. The leader takes seat 0, so the engine and journal replay are unchanged. The draw is recorded with the game (game_setup_draws, migration 00013) for future tournaments, designed as a discrete "player N draws a tile" step. Friend/AI games draw at creation. Auto-match draws when the game opens, against a synthetic uuid.Nil opponent whose draw rows (NULL account_id) are back-filled to the real opponent on join — so the opener's seat is fixed up front and the existing open-game pre-move is preserved with no reseating. Admin /_gm/games/:id gains the recorded draw list and a simple step-by-step board replay (game.ReplayTimeline + a vanilla-JS stepper): a board with A-O/1-15 headers and highlighted premium squares, placed letters with their tile value as a subscript, rack panels around the board (seat 0 top, 1 bottom, 2 left, 3 right) with the current player highlighted, and a per-move log with the tiles drawn and the bag remainder. Docs: ARCHITECTURE §6/§9, FUNCTIONAL (+_ru), PRERELEASE (FM row), design spec.
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
-- +goose Up
|
||||
-- The first-move draw (docs/ARCHITECTURE.md §6): before a game starts, each seated
|
||||
-- player draws one tile from the bag and the tile closest to "A" decides who moves
|
||||
-- first (a blank supersedes all letters); players tied for the best tile re-draw
|
||||
-- until a single leader remains. Each draw uses fresh entropy (not the game's
|
||||
-- deterministic bag seed), so this record — not a seed — is the only account of how
|
||||
-- the order was chosen. It is kept for tournaments, where the draw becomes a manual
|
||||
-- per-tile call. The record is dictionary-independent: the decoded letter, the blank
|
||||
-- flag and the numeric draw rank describe each draw without any alphabet table. The
|
||||
-- winner is reflected as seat 0 in game_players, so no order column is duplicated
|
||||
-- here. In auto-match the opponent is unknown at draw time (the draw runs against a
|
||||
-- synthetic placeholder when the game opens), so their draw rows carry a NULL account_id,
|
||||
-- back-filled when a real opponent joins. Hidden from players for now; surfaced only in the
|
||||
-- admin console.
|
||||
SET search_path = backend, pg_catalog;
|
||||
|
||||
CREATE TABLE game_setup_draws (
|
||||
game_id uuid NOT NULL REFERENCES games (game_id) ON DELETE CASCADE,
|
||||
round smallint NOT NULL,
|
||||
pick_no smallint NOT NULL,
|
||||
account_id uuid REFERENCES accounts (account_id) ON DELETE CASCADE,
|
||||
letter text NOT NULL,
|
||||
is_blank boolean NOT NULL DEFAULT false,
|
||||
draw_rank smallint NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (game_id, round, pick_no)
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
SET search_path = backend, pg_catalog;
|
||||
DROP TABLE game_setup_draws;
|
||||
Reference in New Issue
Block a user