All files / web/src/arcade-games/type-racer-jr/hooks useTypingTTS.ts

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

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                                                                                                                                                                                               
'use client'

import { useCallback, useRef, useState } from 'react'
import { useTTS } from '@/hooks/useTTS'

/**
 * Smart TTS for the typing game.
 *
 * - `announceWord` says the word aloud, then (for the first few words) spells
 *   the letters. Chained via await so they don't talk over each other.
 * - Encouragement messages suppress after repeated use.
 * - A global mute toggle silences everything.
 */
export function useTypingTTS() {
  const speak = useTTS({
    tone: 'Gently encouraging a young child learning to type.',
    say: { en: 'Type each letter!' },
  })

  const [muted, setMuted] = useState(false)

  const instructionCount = useRef(0)
  const suppressedRef = useRef(false)
  const MAX_INSTRUCTIONS = 6

  /**
   * Announce a new word: say it aloud, then spell the letters (first few only).
   * Chained sequentially so they don't overlap.
   */
  const announceWord = useCallback(
    async (word: string) => {
      if (muted) return

      // Always say the word
      await speak({ say: { en: word } })

      // Spell the letters for the first few words (subject to suppression)
      if (!suppressedRef.current && instructionCount.current < MAX_INSTRUCTIONS) {
        instructionCount.current++
        const spelled = word.toUpperCase().split('').join('. ')
        await speak({ say: { en: `${spelled}.` } })
      }
    },
    [speak, muted]
  )

  const speakGameStart = useCallback(async () => {
    if (muted) return
    await speak({ say: { en: 'Type each letter!' } })
  }, [speak, muted])

  const speakWordComplete = useCallback(async () => {
    if (muted) return
    if (suppressedRef.current) return
    if (instructionCount.current >= MAX_INSTRUCTIONS) {
      suppressedRef.current = true
      return
    }
    instructionCount.current++
    await speak({ say: { en: 'Great job!' } })
  }, [speak, muted])

  const speakDifficultyAdvance = useCallback(async () => {
    if (muted) return
    // Reset suppression so the child hears encouragement again
    instructionCount.current = Math.min(instructionCount.current, MAX_INSTRUCTIONS - 1)
    suppressedRef.current = false
    await speak({ say: { en: 'Wow, bigger words!' } })
  }, [speak, muted])

  const speakTimerWarning = useCallback(async () => {
    if (muted) return
    await speak({ say: { en: 'Ten seconds left!' } })
  }, [speak, muted])

  const toggleMute = useCallback(() => {
    setMuted((prev) => !prev)
  }, [])

  const reset = useCallback(() => {
    instructionCount.current = 0
    suppressedRef.current = false
  }, [])

  return {
    announceWord,
    speakGameStart,
    speakWordComplete,
    speakDifficultyAdvance,
    speakTimerWarning,
    toggleMute,
    isMuted: muted,
    reset,
  }
}