Files
morsequest/client/src/hooks/useMorseAudio.ts
T
kitadmin 10afc2bbb0 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>
2026-04-30 02:19:58 +00:00

39 lines
1.2 KiB
TypeScript

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 }
}