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 */}
{/* Main Game Container */}
Therapist Note:
);
}
๐ค Robo's R-Word Rescue
Practice your initial "R" sounds!
{/* START SCREEN */}
{gameState === 'start' && (
)}
{/* GAME OVERLAY (Score) */}
{/* PAUSE SCREEN */}
{gameState === 'paused' && (
)}
{/* THERAPY PROMPT OVERLAY */}
{gameState === 'prompt' && currentPrompt && (
Say the word 3 times out loud!
)}
{/* GAME AREA */}
{/* Footer / Instructions */}
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!
Score: {score}
Paused
Great Catch!
speakWord(currentPrompt.word)}>
{currentPrompt.emoji}
R{currentPrompt.word.slice(1)}
"{currentPrompt.sentence}"
{/* Background Grid/Decoration */}
{/* Floor */}
{/* Falling Items */}
{items.map(item => (
{item.wordData.emoji}
))}
{/* Player (Robot) */}
{/* Robot Graphic drawn with CSS/SVG */}
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!".