From 5cb5329f4e0bf1c63cd242be13e0bc663d1c4299 Mon Sep 17 00:00:00 2001 From: kdletters Date: Mon, 11 May 2026 18:32:02 +0800 Subject: [PATCH] feat: add bark battle debug feedback --- src/games/bark-battle/ui/BarkBattleHud.css | 53 +++++++++++++++ src/games/bark-battle/ui/BarkBattleHud.tsx | 10 ++- .../bark-battle/ui/BarkBattleRuntimeShell.tsx | 66 ++++++++++++++++--- .../__tests__/BarkBattleRuntimeShell.test.tsx | 5 ++ 4 files changed, 124 insertions(+), 10 deletions(-) diff --git a/src/games/bark-battle/ui/BarkBattleHud.css b/src/games/bark-battle/ui/BarkBattleHud.css index c59ee3e8..b4763410 100644 --- a/src/games/bark-battle/ui/BarkBattleHud.css +++ b/src/games/bark-battle/ui/BarkBattleHud.css @@ -46,9 +46,11 @@ } .bark-battle-dog { + position: relative; display: grid; place-items: center; gap: 8px; + animation: barkBattleDogPulse 420ms ease-out; } .bark-battle-dog__body { @@ -61,11 +63,29 @@ } .bark-battle-dog__label, +.bark-battle-dog__burst, .bark-battle-vs { font-weight: 900; text-shadow: 0 2px 10px rgba(0, 0, 0, 0.35); } +.bark-battle-dog__burst { + position: absolute; + top: -18px; + border-radius: 999px; + padding: 5px 10px; + color: #1f1147; + background: #facc15; + box-shadow: 0 0 22px rgba(250, 204, 21, 0.72); + animation: barkBattleBurst 640ms ease-out both; +} + +.bark-battle-dog--opponent .bark-battle-dog__burst { + color: #fff7ed; + background: #7c3aed; + box-shadow: 0 0 22px rgba(124, 58, 237, 0.72); +} + .bark-battle-vs { border-radius: 999px; padding: 10px 18px; @@ -176,6 +196,27 @@ margin-top: 10px; } +.bark-battle-debug-metrics, +.bark-battle-debug-events { + margin: 10px 0 0; + padding: 10px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.1); + font-size: 12px; +} + +.bark-battle-debug-metrics { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 6px; +} + +.bark-battle-debug-events { + display: grid; + gap: 4px; + padding-left: 28px; +} + .bark-battle-debug-panel__controls button { flex: 1; border: 0; @@ -186,6 +227,18 @@ font-weight: 800; } +@keyframes barkBattleDogPulse { + from { transform: scale(1); } + 45% { transform: scale(1.08); } + to { transform: scale(1); } +} + +@keyframes barkBattleBurst { + from { transform: translateY(18px) scale(0.72); opacity: 0; } + 35% { opacity: 1; } + to { transform: translateY(-38px) scale(1.16); opacity: 0; } +} + @keyframes barkBattleParticlePop { from { transform: translateY(28px) scale(0.7); opacity: 0; } 42% { opacity: 1; } diff --git a/src/games/bark-battle/ui/BarkBattleHud.tsx b/src/games/bark-battle/ui/BarkBattleHud.tsx index 8391da87..72210648 100644 --- a/src/games/bark-battle/ui/BarkBattleHud.tsx +++ b/src/games/bark-battle/ui/BarkBattleHud.tsx @@ -4,6 +4,8 @@ import type { BarkBattleSnapshot } from '../domain/BarkBattleTypes'; type BarkBattleHudProps = { snapshot: BarkBattleSnapshot; + playerPulseKey?: number; + opponentPulseKey?: number; onStartMicrophone?: () => void; onMockBark?: () => void; onMockQuiet?: () => void; @@ -24,6 +26,8 @@ const failureText = { export function BarkBattleHud({ snapshot, + playerPulseKey = 0, + opponentPulseKey = 0, onStartMicrophone, onMockBark, onMockQuiet, @@ -61,12 +65,14 @@ export function BarkBattleHud({ ) : (
-
+
+ 🐕 你 · {snapshot.player.barkCount}
VS
-
+
+ 🐶 对手 · {snapshot.opponent.barkCount}
diff --git a/src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx b/src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx index 2ef9d227..add39648 100644 --- a/src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx +++ b/src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { type BarkBattleConfig, @@ -12,6 +12,11 @@ type BarkBattleRuntimeShellProps = { title?: string; }; +type DebugEvent = { + id: number; + text: string; +}; + const DEBUG_CONFIG_FIELDS: Array<{ key: keyof Pick< BarkBattleConfig, @@ -46,12 +51,39 @@ export function BarkBattleRuntimeShell({ title = '汪汪声浪大作战' }: Bark const controller = controllerRef.current; const [snapshot, setSnapshot] = useState(() => controller.getSnapshot()); const [particleText, setParticleText] = useState(''); + const [playerPulseKey, setPlayerPulseKey] = useState(0); + const [opponentPulseKey, setOpponentPulseKey] = useState(0); + const [debugEvents, setDebugEvents] = useState([]); const heldRef = useRef(false); + const lastPlayerBarkCountRef = useRef(0); + const lastOpponentPowerRef = useRef(0); + const debugEventIdRef = useRef(0); + + const appendDebugEvent = useCallback((text: string) => { + debugEventIdRef.current += 1; + const event = { id: debugEventIdRef.current, text }; + setDebugEvents((current) => [event, ...current].slice(0, 5)); + }, []); + + const syncSnapshot = useCallback(() => { + const nextSnapshot = controller.getSnapshot(); + if (nextSnapshot.player.barkCount > lastPlayerBarkCountRef.current) { + setPlayerPulseKey((current) => current + 1); + appendDebugEvent(`玩家叫声触发 #${nextSnapshot.player.barkCount} · 能量 ${Math.round(nextSnapshot.energy)}`); + } + if (nextSnapshot.phase === 'playing' && Math.abs(nextSnapshot.opponent.power - lastOpponentPowerRef.current) >= 0.08) { + setOpponentPulseKey((current) => current + 1); + appendDebugEvent(`对手反击强度 ${(nextSnapshot.opponent.power * 100).toFixed(0)}%`); + } + lastPlayerBarkCountRef.current = nextSnapshot.player.barkCount; + lastOpponentPowerRef.current = nextSnapshot.opponent.power; + setSnapshot(nextSnapshot); + }, [appendDebugEvent, controller]); useEffect(() => { controller.updateConfig(config); - setSnapshot(controller.getSnapshot()); - }, [config, controller]); + syncSnapshot(); + }, [config, controller, syncSnapshot]); useEffect(() => { const timer = window.setInterval(() => { @@ -61,31 +93,38 @@ export function BarkBattleRuntimeShell({ title = '汪汪声浪大作战' }: Bark } else { controller.submitMockSample(0.12); } - setSnapshot(controller.getSnapshot()); + syncSnapshot(); }, 100); return () => window.clearInterval(timer); - }, [controller]); + }, [controller, syncSnapshot]); const restart = () => { heldRef.current = false; controller.restart(); setParticleText(''); - setSnapshot(controller.getSnapshot()); + setDebugEvents([]); + lastPlayerBarkCountRef.current = 0; + lastOpponentPowerRef.current = 0; + syncSnapshot(); }; const startMock = () => { controller.startWithMockInput(); - setSnapshot(controller.getSnapshot()); + appendDebugEvent('开始 mock 对局'); + syncSnapshot(); }; const finishNow = () => { heldRef.current = false; controller.finishNow(); - setSnapshot(controller.getSnapshot()); + appendDebugEvent('人工结束对局'); + syncSnapshot(); }; const bark = () => { heldRef.current = true; + setPlayerPulseKey((current) => current + 1); + appendDebugEvent('按下模拟叫声按钮'); setParticleText('汪!'); window.setTimeout(() => setParticleText(''), 680); }; @@ -94,6 +133,8 @@ export function BarkBattleRuntimeShell({ title = '汪汪声浪大作战' }: Bark
{ @@ -111,6 +152,15 @@ export function BarkBattleRuntimeShell({ title = '汪汪声浪大作战' }: Bark
+
+ 玩家触发:{snapshot.player.barkCount} + 玩家强度:{(snapshot.player.power * 100).toFixed(0)}% + 对手强度:{(snapshot.opponent.power * 100).toFixed(0)}% + 能量:{Math.round(snapshot.energy)} +
+
    + {debugEvents.length ? debugEvents.map((event) =>
  1. {event.text}
  2. ) :
  3. 等待输入触发
  4. } +
{DEBUG_CONFIG_FIELDS.map((field) => (