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:
@@ -12,6 +12,7 @@
|
||||
import { LocalGame, type LocalMove, type DropoutTiles, type LocalGameOptions } from './engine';
|
||||
import type { Dawg } from '../dict/dawg';
|
||||
import type { Variant } from '../model';
|
||||
import type { PinLock } from '../pin';
|
||||
|
||||
/** Seat describes one player of a local game (forward-compatible with a 2-4 hotseat). */
|
||||
export interface Seat {
|
||||
@@ -20,6 +21,9 @@ export interface Seat {
|
||||
/** The player's real account id for a human seat, so the client identifies "you" and the correct
|
||||
* turn exactly as in an online game; absent for the robot (a synthetic id is derived). */
|
||||
accountId?: string;
|
||||
/** Offline hotseat only: the seat's optional PIN lock. When set, the seat's rack stays hidden
|
||||
* until the PIN is entered (a social lock for a shared device — see lib/pin). */
|
||||
pin?: PinLock;
|
||||
}
|
||||
|
||||
/** LocalGameRecord is the durable form of one local game — everything needed to reconstruct it. */
|
||||
@@ -36,6 +40,12 @@ export interface LocalGameRecord {
|
||||
/** The dictionary-independent, alphabet-index-space move journal. */
|
||||
journal: LocalMove[];
|
||||
status: 'active' | 'finished';
|
||||
/** Offline pass-and-play (hotseat): 2-4 humans share one device. Absent on vs_ai records (and on
|
||||
* older records predating the feature), which the source reads as false. */
|
||||
hotseat?: boolean;
|
||||
/** Offline hotseat only: the mandatory master (host/referee) PIN lock, needed to skip/resign a
|
||||
* seat, exclude a player or terminate the game (see lib/pin). Absent on vs_ai records. */
|
||||
hostPin?: PinLock;
|
||||
createdAtUnix: number;
|
||||
updatedAtUnix: number;
|
||||
/** Wall-clock ms at which the vs_ai idle hint unlocks (the robot's last move + the window), or 0
|
||||
@@ -49,6 +59,8 @@ export interface LocalGameRecord {
|
||||
export interface RecordMeta {
|
||||
id: string;
|
||||
seats: Seat[];
|
||||
hotseat?: boolean;
|
||||
hostPin?: PinLock;
|
||||
createdAtUnix: number;
|
||||
updatedAtUnix: number;
|
||||
hintUnlockAtMs: number;
|
||||
@@ -71,6 +83,8 @@ export function serializeGame(game: LocalGame, meta: RecordMeta): LocalGameRecor
|
||||
seats: meta.seats,
|
||||
journal: game.history,
|
||||
status: game.isOver ? 'finished' : 'active',
|
||||
hotseat: meta.hotseat,
|
||||
hostPin: meta.hostPin,
|
||||
createdAtUnix: meta.createdAtUnix,
|
||||
updatedAtUnix: meta.updatedAtUnix,
|
||||
hintUnlockAtMs: meta.hintUnlockAtMs,
|
||||
|
||||
Reference in New Issue
Block a user