// Per-player projection of GameState: everything public, plus YOUR hand. // The server sends this after every state change; clients never see the // deck order or other players' hands. import { type AssembledBoard } from "./board"; import { type CardInstance } from "./cards"; import { boardView, type CastStack, type GameState, type PlayerId, type SquareContent, type SustainedEffect, type TreasureState, type TurnState, } from "./game"; export interface PlayerPublicView { id: PlayerId; position: { x: number; y: number }; home: { x: number; y: number }; life: number; alive: boolean; handCount: number; carriedTreasureId: string | null; lostTurns: number; extraTurns: number; displayed: CardInstance[]; } export interface GameView { you: PlayerId; phase: GameState["phase"]; winner: PlayerId | null; turn: TurnState; activePlayerId: PlayerId; /** Board with dynamic wall changes already merged in. */ board: AssembledBoard; players: PlayerPublicView[]; yourHand: CardInstance[]; treasures: TreasureState[]; deckCount: number; discardCount: number; /** Cards on the stack are face-up: the whole exchange is public. */ stack: CastStack | null; pendingDiscard: PlayerId | null; /** Duration spells in play (public knowledge). */ sustained: SustainedEffect[]; squareContents: Record; groundObjects: Record; doorStates: Record; openDoorEdges: string[]; } export function viewFor(state: GameState, playerId: PlayerId): GameView { const you = state.players.find((p) => p.id === playerId); return { you: playerId, phase: state.phase, winner: state.winner, turn: state.turn, activePlayerId: state.players[state.turn.activeIndex]!.id, board: boardView(state), players: state.players.map((p) => ({ id: p.id, position: p.position, home: p.home, life: p.life, alive: p.alive, handCount: p.hand.length, carriedTreasureId: p.carriedTreasureId, lostTurns: p.lostTurns, extraTurns: p.extraTurns, displayed: p.hand.filter((c) => p.displayed.includes(c.instanceId)), })), yourHand: you ? [...you.hand] : [], treasures: state.treasures.map((t) => ({ ...t })), deckCount: state.deck.length, discardCount: state.discard.length, stack: state.stack, pendingDiscard: state.pendingDiscard, sustained: state.sustained.map((s) => ({ ...s })), squareContents: { ...state.squareContents }, groundObjects: Object.fromEntries( Object.entries(state.groundObjects).map(([k, v]) => [k, [...v]]), ), doorStates: { ...state.doorStates }, openDoorEdges: [...state.openDoorEdges], }; }