"use client"; import { useState, useCallback } from "react"; import { Card } from "@/components/ui/card"; import { ClientOnly } from "@/components/ui/client-only"; import { StepControls } from "./step-controls"; import { MathDisplay } from "@/components/math/math-display"; type Mode = "commutative" | "associative" | "distributive"; type Op = "+" | "-" | "\\times" | "\\div"; interface Step { label: string; math: string; match?: boolean; // colour the result green (holds) / red (fails) } const OP_META: Record number; commutative: boolean; associative: boolean; name: string }> = { "+": { apply: (a, b) => a + b, commutative: true, associative: true, name: "Addition" }, "-": { apply: (a, b) => a - b, commutative: false, associative: false, name: "Subtraction" }, "\\times": { apply: (a, b) => a * b, commutative: true, associative: true, name: "Multiplication" }, "\\div": { apply: (a, b) => a / b, commutative: false, associative: false, name: "Division" }, }; function fmt(x: number): string { if (Number.isInteger(x)) return `${x}`; return `${parseFloat(x.toFixed(3))}`; } function buildCommutativeSteps(a: number, b: number, op: Op): Step[] { const meta = OP_META[op]; const r1 = meta.apply(a, b); const r2 = meta.apply(b, a); const same = r1 === r2; return [ { label: "Commutative law: does the ORDER of the numbers change the answer?", math: `${a} ${op} ${b} \\quad \\text{vs} \\quad ${b} ${op} ${a}`, }, { label: `Work out the first order.`, math: `${a} ${op} ${b} = ${fmt(r1)}` }, { label: `Now swap the order.`, math: `${b} ${op} ${a} = ${fmt(r2)}` }, meta.commutative ? { label: `Same answer! ${meta.name} IS commutative — order does not matter.`, math: `a ${op} b = b ${op} a`, match: true, } : { label: same ? `They match here only because the numbers are special. In general, ${meta.name.toLowerCase()} is NOT commutative.` : `Different answers! ${meta.name} is NOT commutative — order matters.`, math: `a ${op} b \\neq b ${op} a`, match: false, }, ]; } function buildAssociativeSteps(a: number, b: number, c: number, op: Op): Step[] { const meta = OP_META[op]; const left = meta.apply(meta.apply(a, b), c); const right = meta.apply(a, meta.apply(b, c)); return [ { label: "Associative law: does where we put the BRACKETS change the answer?", math: `(${a} ${op} ${b}) ${op} ${c} \\quad \\text{vs} \\quad ${a} ${op} (${b} ${op} ${c})`, }, { label: "Do the brackets on the LEFT first.", math: `(${a} ${op} ${b}) ${op} ${c} = ${fmt(meta.apply(a, b))} ${op} ${c} = ${fmt(left)}` }, { label: "Now do the brackets on the RIGHT first.", math: `${a} ${op} (${b} ${op} ${c}) = ${a} ${op} ${fmt(meta.apply(b, c))} = ${fmt(right)}` }, meta.associative ? { label: `Same answer! ${meta.name} IS associative — the grouping does not matter.`, math: `(a ${op} b) ${op} c = a ${op} (b ${op} c)`, match: true } : { label: `Different answers! ${meta.name} is NOT associative — the grouping matters.`, math: `(a ${op} b) ${op} c \\neq a ${op} (b ${op} c)`, match: false }, ]; } function buildDistributiveSteps(a: number, b: number, c: number): Step[] { const inside = b + c; const answer = a * inside; return [ { label: "Distributive law: a number outside a bracket multiplies EVERYTHING inside.", math: `${a} \\times (${b} + ${c})` }, { label: `Multiply the ${a} by each number inside the bracket separately.`, math: `${a} \\times ${b} \\; + \\; ${a} \\times ${c}`, }, { label: "Working the direct way: add inside the bracket first, then multiply.", math: `${a} \\times (${b} + ${c}) = ${a} \\times ${inside} = ${answer}` }, { label: "Working the expanded way gives the same answer.", math: `${a * b} + ${a * c} = ${answer}`, match: true }, ]; } // ── Quick practice ─────────────────────────────────────────────────────────── interface PracticeQ { prompt: string; answer: number; } function randInt(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1)) + min; } function makeQuestion(): PracticeQ { const roll = Math.floor(Math.random() * 4); if (roll === 0) { const a = randInt(2, 9), b = randInt(1, 9), c = randInt(1, 9); return { prompt: `Fill the blank: ${a} × (${b} + ${c}) = ${a} × ${b} + ${a} × ▢`, answer: c }; } if (roll === 1) { const a = randInt(3, 12), b = randInt(1, a - 1); return { prompt: `Subtraction is not commutative. What is ${a} − ${b}?`, answer: a - b }; } if (roll === 2) { const a = randInt(2, 8), b = randInt(1, 6), c = randInt(1, 6); return { prompt: `Work out ${a} × (${b} + ${c})`, answer: a * (b + c) }; } const a = randInt(2, 9), b = randInt(2, 9); return { prompt: `Multiplication is commutative, so ${a} × ${b} equals ${b} × ▢. What is ▢?`, answer: a }; } function QuickPractice() { const [q, setQ] = useState(() => makeQuestion()); const [val, setVal] = useState(""); const [feedback, setFeedback] = useState<"correct" | "incorrect" | null>(null); const [score, setScore] = useState({ correct: 0, total: 0 }); function check() { const parsed = parseInt(val, 10); if (isNaN(parsed)) return; const correct = parsed === q.answer; setFeedback(correct ? "correct" : "incorrect"); setScore((s) => ({ correct: s.correct + (correct ? 1 : 0), total: s.total + 1 })); } function next() { setQ(makeQuestion()); setVal(""); setFeedback(null); } return (

Quick Practice

{score.correct}/{score.total}

{q.prompt}

{ setVal(e.target.value); setFeedback(null); }} onKeyDown={(e) => { if (e.key === "Enter") { if (feedback !== null) next(); else check(); } }} className={`w-24 rounded-lg border-2 px-3 py-2 text-center text-xl font-bold outline-none transition-colors ${ feedback === "correct" ? "border-correct bg-correct-light" : feedback === "incorrect" ? "border-incorrect bg-incorrect-light" : "border-border focus:border-unit-8" }`} placeholder="?" aria-label="Your answer" /> {feedback === null ? ( ) : ( )}
{feedback === "correct" &&

Correct!

} {feedback === "incorrect" && (

Not quite — the answer is {q.answer}.

)}
); } // ── Main explorer ──────────────────────────────────────────────────────────── export function NumberLawsExplorer() { const [mode, setMode] = useState("commutative"); const [op, setOp] = useState("+"); const [a, setA] = useState("8"); const [b, setB] = useState("3"); const [c, setC] = useState("2"); const [error, setError] = useState(""); const [steps, setSteps] = useState(null); const [currentStep, setCurrentStep] = useState(0); const [isPlaying, setIsPlaying] = useState(false); const done = steps ? currentStep >= steps.length - 1 : false; const reset = useCallback(() => { setSteps(null); setCurrentStep(0); setIsPlaying(false); }, []); function switchMode(m: Mode) { setMode(m); setError(""); // distributive is fixed to + inside; keep whatever op for the others reset(); } const opChoices: Op[] = mode === "associative" ? ["+", "\\times", "-"] : ["+", "-", "\\times", "\\div"]; function handleGo() { setError(""); const na = parseInt(a, 10); const nb = parseInt(b, 10); const nc = parseInt(c, 10); if (isNaN(na) || isNaN(nb) || (mode !== "commutative" && isNaN(nc))) { return setError("Enter whole numbers in every box."); } const vals = mode === "commutative" ? [na, nb] : [na, nb, nc]; if (vals.some((v) => Math.abs(v) > 50)) return setError("Keep the numbers between -50 and 50."); if ((op === "\\div") && (nb === 0 || (mode === "associative" && nc === 0))) { return setError("Cannot divide by zero — change the numbers."); } if (mode === "commutative") setSteps(buildCommutativeSteps(na, nb, op)); else if (mode === "associative") setSteps(buildAssociativeSteps(na, nb, nc, op)); else setSteps(buildDistributiveSteps(na, nb, nc)); setCurrentStep(0); setIsPlaying(false); } const stepForward = useCallback(() => { if (!steps || currentStep >= steps.length - 1) return; setCurrentStep((s) => s + 1); if (currentStep + 1 >= steps.length - 1) setIsPlaying(false); }, [steps, currentStep]); const stepBack = useCallback(() => { if (currentStep <= 0) return; setCurrentStep((s) => s - 1); }, [currentStep]); const togglePlay = useCallback(() => setIsPlaying((p) => !p), []); const step = steps ? steps[currentStep] : null; const showThree = mode !== "commutative"; const showOp = mode !== "distributive"; const opLabel: Record = { "+": "+", "-": "−", "\\times": "×", "\\div": "÷" }; return (
{/* Mode toggle */}
{(["commutative", "associative", "distributive"] as Mode[]).map((m) => ( ))}
{/* Operation tabs */} {showOp && (
{opChoices.map((o) => ( ))}
)} {/* Input card */}

{mode === "distributive" ? "Enter a, b and c for a × (b + c)" : showThree ? "Enter three numbers" : "Enter two numbers"}

setA(e.target.value)} className="w-20 rounded-lg border-2 border-border bg-surface px-3 py-2 text-center text-lg font-bold outline-none focus:border-unit-8" aria-label="a" /> setB(e.target.value)} className="w-20 rounded-lg border-2 border-border bg-surface px-3 py-2 text-center text-lg font-bold outline-none focus:border-unit-8" aria-label="b" /> {showThree && ( setC(e.target.value)} className="w-20 rounded-lg border-2 border-border bg-surface px-3 py-2 text-center text-lg font-bold outline-none focus:border-unit-8" aria-label="c" /> )}
{error &&

{error}

}
{/* Display card */} {!step ? (

Enter numbers above and click Go

) : ( <>

{step.label}

)}
{/* Controls */} 0} /> {/* Quick practice */} }>
); }