Narrate the battle in the event log, synced to the replay step.

This commit is contained in:
Greyson Parrelli
2026-07-23 08:07:17 -04:00
parent 506c6e5b55
commit 63b64bc1d8
5 changed files with 151 additions and 26 deletions
+36 -3
View File
@@ -1,3 +1,5 @@
import { useMemo, useState } from 'react'
import type { Dispatch, SetStateAction } from 'react'
import { useGame } from '../useGame'
import type { Session } from '../types'
import { Lobby } from './Lobby'
@@ -6,7 +8,7 @@ import { ArrangePhase } from './ArrangePhase'
import { BattlePhase } from './BattlePhase'
import { GameOver } from './GameOver'
import { DebugPanel } from './DebugPanel'
import { EventLog } from './EventLog'
import { EventLog, battleLogLines } from './EventLog'
// Table connects to the game and routes to the right phase screen.
export function Table({ session, onLeave }: { session: Session; onLeave: () => void }) {
@@ -23,6 +25,31 @@ export function Table({ session, onLeave }: { session: Session; onLeave: () => v
const you = view.players[view.youSeat]
const opponents = view.players.filter((p) => p.seat !== view.youSeat)
// Battle replay step lives here (not inside BattlePhase) so the event log,
// a sibling, can render the battle narration up to the same step. Deriving
// the effective step from the current battle round resets it to 0 whenever a
// new battle arrives, without a separate effect.
const battleRound = view.battle?.round ?? -1
const [stepState, setStepState] = useState<{ round: number; step: number }>({
round: -1,
step: 0,
})
const step = stepState.round === battleRound ? stepState.step : 0
const setStep: Dispatch<SetStateAction<number>> = (upd) =>
setStepState((prev) => {
const cur = prev.round === battleRound ? prev.step : 0
const next = typeof upd === 'function' ? (upd as (n: number) => number)(cur) : upd
return { round: battleRound, step: next }
})
const battleLines = useMemo(
() =>
view.phase === 'battle' && view.battle?.events
? battleLogLines(view.battle.events, step)
: undefined,
[view.phase, view.battle, step],
)
return (
<div className="table">
<header className="topbar">
@@ -64,11 +91,17 @@ export function Table({ session, onLeave }: { session: Session; onLeave: () => v
<ShopPhase view={view} you={you} send={send} />
)}
{view.phase === 'arrange' && <ArrangePhase view={view} you={you} send={send} />}
{view.phase === 'battle' && <BattlePhase view={view} send={send} />}
{view.phase === 'battle' && (
<BattlePhase view={view} send={send} step={step} setStep={setStep} />
)}
{view.phase === 'gameover' && <GameOver view={view} onLeave={onLeave} />}
</main>
{view.phase !== 'lobby' && (
<EventLog entries={view.log ?? []} youSeat={view.youSeat} />
<EventLog
entries={view.log ?? []}
youSeat={view.youSeat}
battleLines={battleLines}
/>
)}
</div>