All files / web/src/app/arcade/complement-race/hooks useAIRacers.ts

0% Statements 0/126
0% Branches 0/1
0% Functions 0/1
0% Lines 0/126

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