// Memorama — match operation card with result card const Memorama = ({ profile, onFinish, onQuit }) => { const t = window.MP_I18N.t; const diff = window.MP_DATA.difficultyForGrade(profile.grade); const PAIRS = parseInt(profile.grade, 10) <= 2 ? 6 : 8; const cols = PAIRS === 6 ? 3 : 4; const buildDeck = React.useCallback(() => { const used = new Set(); const cards = []; while (cards.length < PAIRS) { const p = window.MP_DATA.genProblem({ tables: diff.tables, max: diff.max }); const key = `${Math.min(p.a,p.b)}x${Math.max(p.a,p.b)}`; if (used.has(key)) continue; used.add(key); const id = cards.length; cards.push({ pair: id, kind: 'op', label: `${p.a} × ${p.b}`, value: p.answer }); cards.push({ pair: id, kind: 'res', label: `${p.answer}`, value: p.answer }); } return cards.map((c, i) => ({ ...c, id: i })).sort(() => Math.random() - 0.5); }, []); const [deck, setDeck] = React.useState(buildDeck); const [flipped, setFlipped] = React.useState([]); const [matched, setMatched] = React.useState([]); const [moves, setMoves] = React.useState(0); const [errors, setErrors] = React.useState(0); const [time, setTime] = React.useState(0); const [done, setDone] = React.useState(false); React.useEffect(() => { if (done) return; const id = setInterval(() => setTime(t => t + 1), 1000); return () => clearInterval(id); }, [done]); React.useEffect(() => { if (matched.length === deck.length && deck.length > 0) { setDone(true); const score = Math.max(20, 200 - errors * 10 - Math.floor(time / 2)); onFinish({ mode: 'memorama', score, correct: PAIRS, total: PAIRS + errors, perfect: errors === 0 }); } }, [matched]); const click = (i) => { if (matched.includes(i) || flipped.includes(i) || flipped.length >= 2 || done) return; window.MP_SOUND.flip(); const newFl = [...flipped, i]; setFlipped(newFl); if (newFl.length === 2) { setMoves(m => m + 1); const [a, b] = newFl; if (deck[a].pair === deck[b].pair && a !== b) { setTimeout(() => { window.MP_SOUND.correct(); setMatched(m => [...m, a, b]); setFlipped([]); }, 500); } else { setErrors(e => e + 1); setTimeout(() => { window.MP_SOUND.wrong(); setFlipped([]); }, 900); } } }; return (
{t('time')}
{time}s
Errores
{errors}
{t('you_got')}
{matched.length / 2}/{PAIRS}
{deck.map((c, i) => { const isFlipped = flipped.includes(i) || matched.includes(i); const isMatched = matched.includes(i); return (
click(i)}>
{c.label}
); })}
); }; window.Memorama = Memorama;