feat: integrate sound effects into game flow (countdown, answers, performance)

Wire up sound effects to game events:

Countdown & Start:
- Play countdown beep (0.4 volume) for 3-2-1
- Play race_start fanfare (0.6 volume) on GO!

Answer Feedback (matching original logic from web_generator.py):
- Streak sound: every 5th correct answer when streak > 0
- Whoosh sound: responses under 800ms (very fast!)
- Combo sound: responses under 1200ms when streak >= 3
- Correct sound: regular correct answers
- Incorrect sound: wrong answers

Sound triggering follows exact original logic (lines 11530-11542, 11589):
- Check streak % 5 first
- Then check response time < 800ms
- Then check response time < 1200ms AND streak >= 3
- Default to regular correct sound

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-09-30 14:10:47 -05:00
parent 90ba86640c
commit 8c3a855239
2 changed files with 29 additions and 4 deletions

View File

@@ -3,9 +3,11 @@
import { useEffect, useState } from 'react'
import { useComplementRace } from '../context/ComplementRaceContext'
import { useGameLoop } from '../hooks/useGameLoop'
import { useSoundEffects } from '../hooks/useSoundEffects'
export function GameCountdown() {
const { dispatch } = useComplementRace()
const { playSound } = useSoundEffects()
const [count, setCount] = useState(3)
const [showGo, setShowGo] = useState(false)
@@ -13,12 +15,14 @@ export function GameCountdown() {
const countdownInterval = setInterval(() => {
setCount(prevCount => {
if (prevCount > 1) {
// TODO: Play countdown sound
// Play countdown beep (volume 0.4)
playSound('countdown', 0.4)
return prevCount - 1
} else if (prevCount === 1) {
// Show GO!
setShowGo(true)
// TODO: Play start sound
// Play race start fanfare (volume 0.6)
playSound('race_start', 0.6)
return 0
}
return prevCount
@@ -26,7 +30,7 @@ export function GameCountdown() {
}, 1000)
return () => clearInterval(countdownInterval)
}, [])
}, [playSound])
useEffect(() => {
if (showGo) {

View File

@@ -5,6 +5,7 @@ import { useComplementRace } from '../context/ComplementRaceContext'
import { useAIRacers } from '../hooks/useAIRacers'
import { useAdaptiveDifficulty } from '../hooks/useAdaptiveDifficulty'
import { useSteamJourney } from '../hooks/useSteamJourney'
import { useSoundEffects } from '../hooks/useSoundEffects'
import { LinearTrack } from './RaceTrack/LinearTrack'
import { CircularTrack } from './RaceTrack/CircularTrack'
import { SteamTrainJourney } from './RaceTrack/SteamTrainJourney'
@@ -16,6 +17,7 @@ export function GameDisplay() {
useAIRacers() // Activate AI racer updates (not used in sprint mode)
const { trackPerformance, getAdaptiveFeedbackMessage } = useAdaptiveDifficulty()
const { boostMomentum } = useSteamJourney()
const { playSound } = useSoundEffects()
// Show adaptive feedback with auto-hide
useEffect(() => {
@@ -66,6 +68,22 @@ export function GameDisplay() {
dispatch({ type: 'SUBMIT_ANSWER', answer })
trackPerformance(true, responseTime)
// Play appropriate sound based on performance (from web_generator.py lines 11530-11542)
const newStreak = state.streak + 1
if (newStreak > 0 && newStreak % 5 === 0) {
// Epic streak sound for every 5th correct answer
playSound('streak')
} else if (responseTime < 800) {
// Whoosh sound for very fast responses (under 800ms)
playSound('whoosh')
} else if (responseTime < 1200 && state.streak >= 3) {
// Combo sound for rapid answers while on a streak
playSound('combo')
} else {
// Regular correct sound
playSound('correct')
}
// Boost momentum for sprint mode
if (state.style === 'sprint') {
boostMomentum()
@@ -82,6 +100,9 @@ export function GameDisplay() {
// Incorrect answer
trackPerformance(false, responseTime)
// Play incorrect sound (from web_generator.py line 11589)
playSound('incorrect')
// Show adaptive feedback
const feedback = getAdaptiveFeedbackMessage(pairKey, false, responseTime)
if (feedback) {
@@ -99,7 +120,7 @@ export function GameDisplay() {
window.addEventListener('keydown', handleKeyPress)
return () => window.removeEventListener('keydown', handleKeyPress)
}, [state.currentInput, state.currentQuestion, state.questionStartTime, state.style, dispatch, trackPerformance, getAdaptiveFeedbackMessage, boostMomentum])
}, [state.currentInput, state.currentQuestion, state.questionStartTime, state.style, state.streak, dispatch, trackPerformance, getAdaptiveFeedbackMessage, boostMomentum, playSound])
// Handle route celebration continue
const handleContinueToNextRoute = () => {