feat: game practice loop with audio, scoring, and level-up modal (Task 11)
- useMorseAudio: wraps MorseAudioEngine, exposes currentSignal for FlashArea - useProgress: fetches /api/progress + /api/mnemonics, recordAnswer with local stats - FlashArea: gold flash on signal, height varies dot/dash - ScoreBar: level name, score, streak display - PracticeCard: play button, answer input, correct/incorrect feedback with mnemonic - GamePage: full practice loop, level-up modal at streak%10, anti-repeat history - Fix: incorrect attempt recorded only once per challenge (no stat inflation on retries) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { useRef, useState, useCallback } from 'react'
|
||||
import { MorseAudioEngine } from '../lib/audio'
|
||||
import { DEFAULT_WPM } from '../lib/morse'
|
||||
|
||||
type SignalType = 'dot' | 'dash' | null
|
||||
|
||||
export function useMorseAudio() {
|
||||
const engineRef = useRef<MorseAudioEngine | null>(null)
|
||||
const [isPlaying, setIsPlaying] = useState(false)
|
||||
const [currentSignal, setCurrentSignal] = useState<SignalType>(null)
|
||||
|
||||
function getEngine() {
|
||||
if (!engineRef.current) {
|
||||
const engine = new MorseAudioEngine()
|
||||
engine.on('signal-start', ({ type }: { type: 'dot' | 'dash' }) => setCurrentSignal(type))
|
||||
engine.on('signal-end', () => setCurrentSignal(null))
|
||||
engine.on('play-end', () => { setIsPlaying(false); setCurrentSignal(null) })
|
||||
engineRef.current = engine
|
||||
}
|
||||
return engineRef.current
|
||||
}
|
||||
|
||||
const play = useCallback((text: string) => {
|
||||
const engine = getEngine()
|
||||
engine.stop()
|
||||
setIsPlaying(true)
|
||||
setCurrentSignal(null)
|
||||
engine.play(text, DEFAULT_WPM)
|
||||
}, [])
|
||||
|
||||
const stop = useCallback(() => {
|
||||
engineRef.current?.stop()
|
||||
setIsPlaying(false)
|
||||
setCurrentSignal(null)
|
||||
}, [])
|
||||
|
||||
return { play, stop, isPlaying, currentSignal }
|
||||
}
|
||||
Reference in New Issue
Block a user