// game-counting-stars.jsx // Counting Stars — Evelyn's first game. Count the cuties, tap the number. // Exposes window.CountingStars. Wrapped in an IIFE so its hook bindings don't // collide with app.jsx's globals (Babel scripts share global scope). // // Props: // level number — her current "counting" skill level (scales difficulty) // onExit() — go back home // onComplete(result) — { skillId:"counting", xp, correct, total }; the app // then plays the XP / level-up / chest celebration. (function () { const { useState, useRef } = React; const CUTIES = ["⭐", "🌸", "🦋", "🐰", "🍓", "🐣", "🌈", "🧁"]; const QUESTIONS_PER_ROUND = 5; const XP_PER_CORRECT = 10; const PERFECT_BONUS = 20; const randInt = (lo, hi) => lo + Math.floor(Math.random() * (hi - lo + 1)); const pick = (arr) => arr[Math.floor(Math.random() * arr.length)]; const shuffle = (arr) => { const a = arr.slice(); for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; }; // Build one question. Difficulty grows gently with level. function makeQuestion(level) { const max = Math.min(5 + (level - 1) * 2, 12); const count = randInt(1, max); const cutie = pick(CUTIES); const choices = new Set([count]); let guard = 0; while (choices.size < 3 && guard++ < 50) { const d = count + randInt(-3, 3); if (d >= 1 && d <= Math.max(max, count + 3)) choices.add(d); } // Fill if we somehow couldn't get 3 distinct. let n = 1; while (choices.size < 3) choices.add(count + n++); // Stable little rotations so the scattered cuties look playful but don't // jump around on re-render. const rotations = Array.from({ length: count }, () => randInt(-18, 18)); return { count, cutie, choices: shuffle([...choices]), rotations }; } function makeRound(level) { return Array.from({ length: QUESTIONS_PER_ROUND }, () => makeQuestion(level)); } function CountingStars({ level = 1, onExit, onComplete }) { const [round, setRound] = useState(() => makeRound(level)); const [idx, setIdx] = useState(0); const [phase, setPhase] = useState("play"); // "play" | "end" const [picked, setPicked] = useState(null); // last picked value const [wrong, setWrong] = useState(null); // value shown as wrong (shake) const earnedRef = useRef(0); // questions correct on first try const firstTryRef = useRef(true); // first try on the current question? const lockRef = useRef(false); // ignore taps during transition const q = round[idx]; function nextQuestion() { setPicked(null); setWrong(null); firstTryRef.current = true; lockRef.current = false; if (idx + 1 >= round.length) { finishRound(); } else { setIdx(idx + 1); } } function finishRound() { const earned = earnedRef.current; const perfect = earned === QUESTIONS_PER_ROUND; const xp = earned * XP_PER_CORRECT + (perfect ? PERFECT_BONUS : 0); setPhase("end"); if (onComplete) { onComplete({ skillId: "counting", xp, correct: earned, total: QUESTIONS_PER_ROUND, perfect }); } } function choose(value) { if (lockRef.current) return; if (value === q.count) { if (firstTryRef.current) earnedRef.current += 1; setPicked(value); setWrong(null); lockRef.current = true; setTimeout(nextQuestion, 850); } else { // Gentle: let her try again. Just mark the wrong tap and lose first-try. firstTryRef.current = false; setWrong(value); setTimeout(() => setWrong(null), 500); } } function playAgain() { earnedRef.current = 0; firstTryRef.current = true; lockRef.current = false; setRound(makeRound(level)); setIdx(0); setPicked(null); setWrong(null); setPhase("play"); } // ----- End screen ------------------------------------------------------ if (phase === "end") { const earned = earnedRef.current; const perfect = earned === QUESTIONS_PER_ROUND; return (
{perfect ? "🏆" : "🎉"}

{perfect ? "Perfect!" : "Great job!"}

You counted {earned} of {QUESTIONS_PER_ROUND} right!

); } // ----- Playing --------------------------------------------------------- return (
{round.map((_, i) => ( ))}
{idx + 1}/{QUESTIONS_PER_ROUND}

How many do you see?

{Array.from({ length: q.count }).map((_, i) => ( {q.cutie} ))}
{picked != null &&
🎉 {q.count}!
}
{q.choices.map((c) => ( ))}
); } window.CountingStars = CountingStars; })();