import React, { useState, useEffect, useRef, useCallback } from 'react'; import { Play, RotateCcw, Volume2, Mic, Star, Info, Pause } from 'lucide-react'; // --- Game Constants & Data --- const GAME_WIDTH = 600; const GAME_HEIGHT = 400; const PLAYER_SIZE = 60; const ITEM_SIZE = 40; const SPAWN_RATE = 1500; // ms between spawns const FALL_SPEED = 2.5; // Target words: Initial /r/ const TARGET_WORDS = [ { word: "Rabbit", emoji: "๐Ÿ‡", sentence: "The rabbit runs fast." }, { word: "Radio", emoji: "๐Ÿ“ป", sentence: "Turn on the radio." }, { word: "Rain", emoji: "๐ŸŒง๏ธ", sentence: "The rain is wet." }, { word: "Rat", emoji: "๐Ÿ€", sentence: "I saw a little rat." }, { word: "Red", emoji: "๐ŸŸฅ", sentence: "The apple is red." }, { word: "Ring", emoji: "๐Ÿ’", sentence: "She has a gold ring." }, { word: "Road", emoji: "๐Ÿ›ฃ๏ธ", sentence: "Drive down the road." }, { word: "Robot", emoji: "๐Ÿค–", sentence: "The robot can dance." }, { word: "Rocket", emoji: "๐Ÿš€", sentence: "The rocket flies high." }, { word: "Rose", emoji: "๐ŸŒน", sentence: "Smell the red rose." }, { word: "Rug", emoji: "๐Ÿงถ", sentence: "Sit on the soft rug." }, { word: "Run", emoji: "๐Ÿƒ", sentence: "Ready, set, run!" }, ]; // --- Types --- interface FallingItem { id: number; x: number; y: number; wordData: typeof TARGET_WORDS[0]; } // --- Main Component --- export default function RobosRWordRescue() { // Game State const [gameState, setGameState] = useState<'start' | 'playing' | 'paused' | 'prompt' | 'gameover'>('start'); const [score, setScore] = useState(0); const [highScore, setHighScore] = useState(0); const [playerX, setPlayerX] = useState(GAME_WIDTH / 2); const [items, setItems] = useState([]); const [currentPrompt, setCurrentPrompt] = useState(null); // Refs for loop management const requestRef = useRef(); const lastSpawnTime = useRef(0); const gameAreaRef = useRef(null); // --- Audio Logic (Browser Speech Synthesis) --- const speakWord = (text: string) => { if ('speechSynthesis' in window) { const utterance = new SpeechSynthesisUtterance(text); utterance.rate = 0.9; // Slightly slower for therapy window.speechSynthesis.speak(utterance); } }; // --- Game Loop Logic --- const spawnItem = useCallback(() => { const randomWord = TARGET_WORDS[Math.floor(Math.random() * TARGET_WORDS.length)]; const newItem: FallingItem = { id: Date.now(), x: Math.random() * (GAME_WIDTH - ITEM_SIZE), y: -ITEM_SIZE, wordData: randomWord, }; setItems(prev => [...prev, newItem]); }, []); const updateGame = useCallback((time: number) => { if (gameState !== 'playing') return; // Spawning if (time - lastSpawnTime.current > SPAWN_RATE) { spawnItem(); lastSpawnTime.current = time; } setItems(prevItems => { const newItems: FallingItem[] = []; let itemCaught = false; let caughtWordData = null; prevItems.forEach(item => { const newY = item.y + FALL_SPEED; // Collision Detection const isColliding = newY + ITEM_SIZE > GAME_HEIGHT - PLAYER_SIZE && // Bottom check newY < GAME_HEIGHT && // Top check (inside player height) item.x < playerX + PLAYER_SIZE && // Right bound item.x + ITEM_SIZE > playerX; // Left bound if (isColliding) { itemCaught = true; caughtWordData = item.wordData; // Don't add to newItems (remove it) } else if (newY < GAME_HEIGHT) { // Keep item if it hasn't fallen off screen newItems.push({ ...item, y: newY }); } }); if (itemCaught && caughtWordData) { // Trigger Prompt Mode setCurrentPrompt(caughtWordData); setGameState('prompt'); speakWord(caughtWordData.word); } return newItems; }); requestRef.current = requestAnimationFrame((t) => updateGame(t)); }, [gameState, playerX, spawnItem]); // Start/Stop Loop useEffect(() => { if (gameState === 'playing') { requestRef.current = requestAnimationFrame((t) => { lastSpawnTime.current = t; updateGame(t); }); } return () => cancelAnimationFrame(requestRef.current!); }, [gameState, updateGame]); // Input Handling (Mouse/Touch) const handleMove = (e: React.MouseEvent | React.TouchEvent) => { if (gameState !== 'playing' || !gameAreaRef.current) return; const rect = gameAreaRef.current.getBoundingClientRect(); let clientX; if ('touches' in e) { clientX = e.touches[0].clientX; } else { clientX = (e as React.MouseEvent).clientX; } const relativeX = clientX - rect.left; // Clamp player within bounds const clampedX = Math.max(0, Math.min(GAME_WIDTH - PLAYER_SIZE, relativeX - PLAYER_SIZE / 2)); setPlayerX(clampedX); }; // --- Actions --- const startGame = () => { setScore(0); setItems([]); setGameState('playing'); setPlayerX(GAME_WIDTH / 2 - PLAYER_SIZE / 2); }; const resumeGame = () => { setScore(s => s + 1); setCurrentPrompt(null); setGameState('playing'); }; const quitGame = () => { setGameState('start'); setScore(0); }; return (
{/* Header */}

๐Ÿค– Robo's R-Word Rescue

Practice your initial "R" sounds!

{/* Main Game Container */}
{/* START SCREEN */} {gameState === 'start' && (

How to Play

  • 1 Move the Robot left and right to catch the falling items.
  • 2 When you catch one, practice saying the R-word out loud!
)} {/* GAME OVERLAY (Score) */}
Score: {score}
{/* PAUSE SCREEN */} {gameState === 'paused' && (

Paused

)} {/* THERAPY PROMPT OVERLAY */} {gameState === 'prompt' && currentPrompt && (
Great Catch!
speakWord(currentPrompt.word)}> {currentPrompt.emoji}

R{currentPrompt.word.slice(1)}

"{currentPrompt.sentence}"

Say the word 3 times out loud!
)} {/* GAME AREA */}
{/* Background Grid/Decoration */}
{/* Floor */}
{/* Falling Items */} {items.map(item => (
{item.wordData.emoji}
))} {/* Player (Robot) */}
{/* Robot Graphic drawn with CSS/SVG */} {/* Antenna */} {/* Head */} {/* Eyes */} {/* Mouth */} {/* Body (partial) */}
{/* Footer / Instructions */}
Therapist Note:

This game utilizes auditory bombardment (hearing the word upon catch) and production practice (pausing to say the word). Encourage the user to articulate the 'R' clearly before clicking "I Said It!".

); }