From 1705de5a3f4d7c77216e123ae3d60fada718e108 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 23 Jul 2026 08:40:42 -0400 Subject: [PATCH] Hold the battle result until the replay ends, then show it last. --- internal/game/battle.go | 8 +++++-- internal/game/log.go | 1 + web/src/components/Table.tsx | 45 ++++++++++++++++++++++++++---------- web/src/types.ts | 1 + 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/internal/game/battle.go b/internal/game/battle.go index 59c22f1..7693590 100644 --- a/internal/game/battle.go +++ b/internal/game/battle.go @@ -724,10 +724,14 @@ func (g *Game) resolveBattle() { g.Battle = res g.Phase = PhaseBattle + // Tagged "result" so the client can hold it back until the replay finishes + // (the outcome is known now, but showing it early would spoil the battle). if winner < 0 { - g.logf(-1, "⚔️", "Round %d battle ends in a draw.", g.Round) + g.addLog(LogEntry{Seat: -1, Icon: "⚔️", Kind: "result", + Text: fmt.Sprintf("Round %d battle ends in a draw.", g.Round)}) } else { - g.logf(winner, "⚔️", "%s wins the round %d battle (+%d🏆).", pname(winner), g.Round, res.Trophies) + g.addLog(LogEntry{Seat: winner, Icon: "⚔️", Kind: "result", + Text: fmt.Sprintf("%s wins the round %d battle (+%d🏆).", pname(winner), g.Round, res.Trophies)}) } for _, p := range g.Players { p.Ready = false diff --git a/internal/game/log.go b/internal/game/log.go index 012a9dc..9d40ac8 100644 --- a/internal/game/log.go +++ b/internal/game/log.go @@ -14,6 +14,7 @@ type LogEntry struct { Phase Phase `json:"phase"` Seat int `json:"seat"` // acting seat, or -1 when none Icon string `json:"icon,omitempty"` // leading emoji + Kind string `json:"kind,omitempty"` // e.g. "result" (battle outcome) Text string `json:"text"` // the sentence itself Source string `json:"source,omitempty"` // card id that caused a spawn Spawn string `json:"spawn,omitempty"` // "apple" | "bee" for spawn entries diff --git a/web/src/components/Table.tsx b/web/src/components/Table.tsx index 0988a11..a0b1cc7 100644 --- a/web/src/components/Table.tsx +++ b/web/src/components/Table.tsx @@ -32,13 +32,38 @@ export function Table({ session, onLeave }: { session: Session; onLeave: () => v return { round: battleRound, step: next } }) - const battleLines = useMemo( - () => - view?.phase === 'battle' && view.battle?.events - ? battleLogLines(view.battle.events, step) - : undefined, - [view, step], - ) + // The battle outcome is known before the replay plays out, so we hold its + // "result" log entry back: it's dropped from the persistent list during the + // battle and appended as the final battle line only once the replay reaches + // the end (and reappears in the persistent log in later phases). + const inBattle = view?.phase === 'battle' + const events = view?.battle?.events ?? null + const battleDone = !!(inBattle && events && step >= events.length) + + const entries = useMemo(() => { + const log = view?.log ?? [] + if (!inBattle) return log + return log.filter((e) => !(e.kind === 'result' && e.round === battleRound)) + }, [view, inBattle, battleRound]) + + const battleLines = useMemo(() => { + if (!inBattle || !events) return undefined + const lines = battleLogLines(events, step) + if (battleDone) { + const result = (view?.log ?? []).find( + (e) => e.kind === 'result' && e.round === battleRound, + ) + if (result) { + lines.push({ + key: `result-${result.seq}`, + icon: result.icon, + text: result.text, + seat: result.seat, + }) + } + } + return lines + }, [view, inBattle, events, step, battleDone, battleRound]) if (!view) { return ( @@ -98,11 +123,7 @@ export function Table({ session, onLeave }: { session: Session; onLeave: () => v {view.phase === 'gameover' && } {view.phase !== 'lobby' && ( - + )} diff --git a/web/src/types.ts b/web/src/types.ts index 42617e4..257090b 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -83,6 +83,7 @@ export interface LogEntry { phase: Phase seat: number // acting seat, or -1 icon?: string + kind?: string // e.g. 'result' (battle outcome) text: string source?: string // card id that caused a spawn spawn?: string // 'apple' | 'bee'