// What happened while you were away, as a few facts before the reel: // read from the missed steps as the server redacted them for you, so it // tells you only what you would have seen at the table. import type { GameEvent, GameView } from "@wizwar/engine"; import { cardName } from "./reference"; /** One step of a replayed stretch: who acted, what happened, the table * after, and any talk that landed alongside. */ export interface ReplayStep { seq: number; actor: string; events: GameEvent[]; view: GameView; chat?: { player: string; text: string }[]; } export interface MissedFact { text: string; /** The step of the reel this fact belongs to. */ step: number; } function spell(id: string | null): string { return id ? cardName(id) : "a punch"; } function nearHome(view: GameView, you: string, cell: { x: number; y: number }): boolean { const me = view.players.find((p) => p.id === you); if (!me) return false; return Math.abs(me.home.x - cell.x) + Math.abs(me.home.y - cell.y) <= 2; } /** The facts of the missed steps that touch you, in order, capped so the * slip stays a slip. Each fact points at its step for the reel. */ export function summarizeMissed(steps: readonly ReplayStep[], you: string, now: GameView | null): MissedFact[] { const facts: MissedFact[] = []; let lifeLost = 0; let lifeLostStep = -1; let talk = 0; steps.forEach((s, i) => { talk += s.chat?.length ?? 0; for (const e of s.events) { switch (e.type) { case "treasurePickedUp": if (e.owner === you && e.player !== you) facts.push({ text: `${e.player} took your treasure.`, step: i }); else if (e.player !== you && e.owner !== e.player) facts.push({ text: `${e.player} picked up ${e.owner}'s treasure.`, step: i }); break; case "treasureDropped": { const owner = s.view.treasures.find((t) => t.id === e.treasureId)?.owner; if (e.onHomeOf != null && owner && e.onHomeOf !== owner) { facts.push({ text: owner === you ? `Your treasure was carried home by ${e.player}.` : `${e.player} carried ${owner}'s treasure home.`, step: i }); } break; } case "damaged": if (e.player === you && e.amount > 0) { lifeLost += e.amount; if (lifeLostStep < 0) lifeLostStep = i; } break; case "spellCast": if (e.target === you) facts.push({ text: `${e.caster} cast ${spell(e.cardId)} at you.`, step: i }); break; case "punched": if (e.target === you) facts.push({ text: `${e.attacker} punched you.`, step: i }); break; case "wallDestroyed": if (nearHome(s.view, you, e.edge.cell)) facts.push({ text: "A wall near your home was destroyed.", step: i }); break; case "playerEliminated": facts.push({ text: e.player === you ? "You were eliminated." : `${e.player} is out of the game.`, step: i }); break; case "gameWon": facts.push({ text: e.player === you ? "You won." : `${e.player} won the game.`, step: i }); break; case "tableTalk": talk++; break; default: break; } } }); // Life lost reads second: the first fact names who came for you. if (lifeLost > 0) facts.splice(Math.min(facts.length, 1), 0, { text: `You lost ${lifeLost} life.`, step: lifeLostStep }); if (facts.length === 0) { // Nothing touched you: say what each wizard was up to, all the same. const doings = new Map(); steps.forEach((s, i) => { if (s.actor === you) return; const d = doings.get(s.actor) ?? { moves: 0, casts: 0, first: i }; for (const e of s.events) { if (e.type === "moved" && e.player === s.actor) d.moves++; if (e.type === "spellCast" && e.caster === s.actor) d.casts++; } doings.set(s.actor, d); }); for (const [who, d] of doings) { const bits = [d.moves > 0 ? `walked ${d.moves} square${d.moves === 1 ? "" : "s"}` : "", d.casts > 0 ? `cast ${d.casts} spell${d.casts === 1 ? "" : "s"}` : ""].filter(Boolean); facts.push({ text: `${who} ${bits.length ? bits.join(" and ") : "passed the time"}; nothing touched you.`, step: d.first }); } } if (talk > 0) facts.push({ text: `${talk} line${talk === 1 ? "" : "s"} of table talk.`, step: -1 }); const out = facts.slice(0, 6); if (facts.length > 6) out.push({ text: `…and ${facts.length - 6} more.`, step: -1 }); if (now) { const me = now.players.find((p) => p.id === you); if (now.phase === "playing" && me?.alive) { const mine = now.activePlayerId === you || now.stack?.waitingOn === you || now.pendingDiscard === you; out.push({ text: mine ? "It's your turn." : `It's ${now.activePlayerId}'s turn.`, step: -1 }); } } return out; }