Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | import { useEffect } from 'react' import { type CommentaryContext, getAICommentary } from '../components/AISystem/aiCommentary' import { useComplementRace } from '@/arcade-games/complement-race/Provider' import { useSoundEffects } from './useSoundEffects' export function useAIRacers() { const { state, dispatch } = useComplementRace() const { playSound } = useSoundEffects() useEffect(() => { if (!state.isGameActive) return // Update AI positions every 200ms (line 11690) const aiUpdateInterval = setInterval(() => { const newPositions = state.aiRacers.map((racer) => { // Base speed with random variance (0.6-1.4 range via Math.random() * 0.8 + 0.6) const variance = Math.random() * 0.8 + 0.6 let speed = racer.speed * variance * state.speedMultiplier // Rubber-banding: AI speeds up 2x when >10 units behind player (line 11697-11699) const distanceBehind = state.correctAnswers - racer.position if (distanceBehind > 10) { speed *= 2 } // Update position const newPosition = racer.position + speed return { id: racer.id, position: newPosition, } }) dispatch({ type: 'UPDATE_AI_POSITIONS', positions: newPositions }) // Check for AI win in practice mode (line 14151) if (state.style === 'practice' && state.isGameActive) { const winningAI = state.aiRacers.find((racer, index) => { const updatedPosition = newPositions[index]?.position || racer.position return updatedPosition >= state.raceGoal }) if (winningAI) { // Play game over sound (line 14193) playSound('gameOver') // End the game dispatch({ type: 'END_RACE' }) // Show results after a short delay setTimeout(() => { dispatch({ type: 'SHOW_RESULTS' }) }, 1500) return // Exit early to prevent further updates } } // Check for commentary triggers after position updates state.aiRacers.forEach((racer) => { const updatedPosition = newPositions.find((p) => p.id === racer.id)?.position || racer.position const distanceBehind = state.correctAnswers - updatedPosition const distanceAhead = updatedPosition - state.correctAnswers // Detect passing events const playerJustPassed = racer.previousPosition > state.correctAnswers && updatedPosition < state.correctAnswers const aiJustPassed = racer.previousPosition < state.correctAnswers && updatedPosition > state.correctAnswers // Determine commentary context let context: CommentaryContext | null = null if (playerJustPassed) { context = 'player_passed' } else if (aiJustPassed) { context = 'ai_passed' } else if (distanceBehind > 20) { // Player has lapped the AI (more than 20 units behind) context = 'lapped' } else if (distanceBehind > 10) { // AI is desperate to catch up (rubber-banding active) context = 'desperate_catchup' } else if (distanceAhead > 5) { // AI is significantly ahead context = 'ahead' } else if (distanceBehind > 3) { // AI is behind context = 'behind' } // Trigger commentary if context is valid if (context) { const message = getAICommentary(racer, context, state.correctAnswers, updatedPosition) if (message) { dispatch({ type: 'TRIGGER_AI_COMMENTARY', racerId: racer.id, message, context, }) // Play special turbo sound when AI goes desperate (line 11941) if (context === 'desperate_catchup') { playSound('ai_turbo', 0.12) } } } }) }, 200) return () => clearInterval(aiUpdateInterval) }, [ state.isGameActive, state.aiRacers, state.correctAnswers, state.speedMultiplier, dispatch, // Play game over sound (line 14193) playSound, state.raceGoal, state.style, ]) return { aiRacers: state.aiRacers, } } |