feat(offline): hotseat record schema + source (locks, host actions)
Extend the local game to offline pass-and-play (hotseat): - serialize.ts: Seat.pin, record hotseat + hostPin (all optional; old vs_ai records read hotseat as false). - source.ts: create() takes hotseat/hostPin + per-seat pins; stateView reveals the seat-to-move's rack and withholds it (locked) when that seat is PIN-locked, until unlockSeat; ephemeral per-turn unlock re-locks on advance; new unlockSeat / verifyHostPin / hostAction (skip/resign/terminate); gameView sets vsAi=!hotseat + the hotseat flag. - model.ts: GameView.hotseat, StateView.locked (offline-only, optional). Tests: source.hotseat (lock/unlock/re-lock, host skip/resign/terminate, master-PIN gate, natural-end persistence) + serialize hotseat shape.
This commit is contained in:
@@ -47,6 +47,10 @@ export interface GameView {
|
||||
lastActivityUnix: number;
|
||||
/** true = an honest-AI game: the opponent is shown as 🤖 and chat/nudge/add-friend are disabled. */
|
||||
vsAi: boolean;
|
||||
/** true = a local offline pass-and-play (hotseat) game: 2-4 humans share one device. Set only by
|
||||
* the local source; the online gateway never sets it. Drives the per-seat PIN-lock UI and the host
|
||||
* menu (which replaces the hint control). A hotseat game is never vsAi. */
|
||||
hotseat?: boolean;
|
||||
/** Per-viewer flag: the requesting player has at least one unread chat entry (message or
|
||||
* nudge) in this game. Set on the authoritative REST views (lobby list, game state,
|
||||
* move result); the live-event GameView leaves it false (events bump unread instead). */
|
||||
@@ -87,6 +91,9 @@ export interface StateView {
|
||||
* non-vs_ai game). The client anchors a MONOTONIC countdown (performance.now()) to it on receipt,
|
||||
* so a client clock change cannot skew it; a relaunch re-fetches a fresh value. See lib/hints. */
|
||||
hintUnlockLeftSeconds?: number;
|
||||
/** Offline hotseat only: the seat to move is PIN-locked, so its rack is withheld (empty) until the
|
||||
* seat's PIN is entered (see LocalSource.unlockSeat). Absent/false everywhere else. */
|
||||
locked?: boolean;
|
||||
}
|
||||
|
||||
export interface MoveResult {
|
||||
|
||||
Reference in New Issue
Block a user