Victory gets its moment: a gold-ringed proclamation

Winning showed only as a small slip in the rail — anticlimactic, as
playtesting put it. The engine now records HOW the game was won
(winReason: treasures carried home, or last wizard standing) alongside
the winner, the view carries it, and the finish opens a gold-ringed
modal: trophy, the winner's name in tall caps, and the manner of
victory in a handwritten line. "Behold the final board" dismisses it
to the finished table. In hotseat the one screen proclaims to the
whole table at once; in async games each player is greeted by it when
they next open the game.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 11:11:24 -04:00
co-authored by Claude Fable 5
parent 8f7beb5ef3
commit 13aeeea0ef
3 changed files with 49 additions and 0 deletions
+4
View File
@@ -247,6 +247,7 @@ export interface GameState {
stack: CastStack | null;
rng: RngState;
winner: PlayerId | null;
winReason: "treasures" | "lastStanding" | null;
pendingDiscard: PlayerId | null;
/** Monotonic counter for sustained-effect ids. */
nextEffectId: number;
@@ -2999,6 +3000,7 @@ export function createGame(config: GameConfig): { state: GameState; events: Game
stack: null,
rng,
winner: null,
winReason: null,
pendingDiscard: null,
nextEffectId: 1,
};
@@ -4668,6 +4670,7 @@ function checkVictory(state: GameState, events: GameEvent[]): void {
if (stolenAtHome.length >= 2) {
state.phase = "finished";
state.winner = p.id;
state.winReason = "treasures";
events.push({ type: "gameWon", player: p.id, reason: "treasures" });
return;
}
@@ -4677,6 +4680,7 @@ function checkVictory(state: GameState, events: GameEvent[]): void {
if (alive.length === 1) {
state.phase = "finished";
state.winner = alive[0]!.id;
state.winReason = "lastStanding";
events.push({ type: "gameWon", player: alive[0]!.id, reason: "lastStanding" });
}
}