"use client"; import { useState } from "react"; import { Card } from "@/components/ui/card"; import { StepControls } from "./step-controls"; import { MathDisplay } from "@/components/math/math-display"; import { toKatex, simplify, add, subtract, multiply, divide } from "@/lib/math/fractions"; interface FractionVal { num: number; den: number; } interface Expression { fractions: FractionVal[]; operators: string[]; } interface Step { label: string; math: string; highlightIndex: number | null; // which operator is being evaluated } const OP_PRIORITY: Record = { "×": 2, "÷": 2, "+": 1, "−": 1, }; function performOp(a: FractionVal, b: FractionVal, op: string): FractionVal { let result: [number, number]; switch (op) { case "+": result = add(a.num, a.den, b.num, b.den); break; case "−": result = subtract(a.num, a.den, b.num, b.den); break; case "×": result = multiply(a.num, a.den, b.num, b.den); break; case "÷": result = divide(a.num, a.den, b.num, b.den); break; default: result = [0, 1]; } return { num: result[0], den: result[1] }; } function expressionToKatex(fracs: FractionVal[], ops: string[], highlightIdx: number | null): string { const parts: string[] = []; fracs.forEach((f, i) => { parts.push(toKatex(f.num, f.den)); if (i < ops.length) { if (i === highlightIdx) { parts.push(`\\;\\boxed{${ops[i]}}\\;`); } else { parts.push(`\\;${ops[i]}\\;`); } } }); return parts.join(""); } function buildBodmasSteps(expr: Expression): Step[] { const steps: Step[] = []; let fracs = [...expr.fractions]; let ops = [...expr.operators]; // Step 0: Show the expression steps.push({ label: "Start with the expression", math: expressionToKatex(fracs, ops, null), highlightIndex: null, }); while (ops.length > 0) { // Find highest priority operator (leftmost if tie) let maxPri = 0; let opIdx = 0; ops.forEach((op, i) => { if (OP_PRIORITY[op] > maxPri) { maxPri = OP_PRIORITY[op]; opIdx = i; } }); // Highlight the operation to perform steps.push({ label: `BODMAS: do ${ops[opIdx]} first (${OP_PRIORITY[ops[opIdx]] === 2 ? "multiplication/division" : "addition/subtraction"})`, math: expressionToKatex(fracs, ops, opIdx), highlightIndex: opIdx, }); // Perform the operation const result = performOp(fracs[opIdx], fracs[opIdx + 1], ops[opIdx]); const [sn, sd] = simplify(result.num, result.den); const newFracs = [...fracs]; newFracs.splice(opIdx, 2, { num: sn, den: sd }); const newOps = [...ops]; newOps.splice(opIdx, 1); steps.push({ label: `${fracs[opIdx].num}/${fracs[opIdx].den} ${ops[opIdx]} ${fracs[opIdx + 1].num}/${fracs[opIdx + 1].den} = ${sn}/${sd}`, math: expressionToKatex(newFracs, newOps, null), highlightIndex: null, }); fracs = newFracs; ops = newOps; } return steps; } const PRESETS: { label: string; fracs: FractionVal[]; ops: string[] }[] = [ { label: "½ + ¼ × ⅗", fracs: [ { num: 1, den: 2 }, { num: 1, den: 4 }, { num: 3, den: 5 }, ], ops: ["+", "×"], }, { label: "⅔ × ¾ − ½", fracs: [ { num: 2, den: 3 }, { num: 3, den: 4 }, { num: 1, den: 2 }, ], ops: ["×", "−"], }, { label: "⅗ ÷ ¼ + ⅔", fracs: [ { num: 3, den: 5 }, { num: 1, den: 4 }, { num: 2, den: 3 }, ], ops: ["÷", "+"], }, ]; export function BODMASExplorer() { const [fracs, setFracs] = useState([ { num: 1, den: 2 }, { num: 1, den: 4 }, { num: 3, den: 5 }, ]); const [ops, setOps] = useState(["+", "×"]); 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; function updateFrac(idx: number, field: "num" | "den", value: string) { setFracs((prev) => { const next = [...prev]; next[idx] = { ...next[idx], [field]: parseInt(value) || 0 }; return next; }); } function handleGo() { setError(""); if (fracs.some((f) => f.den === 0)) { setError("Denominators cannot be zero."); return; } if (fracs.some((f) => Math.abs(f.num) > 99 || Math.abs(f.den) > 99)) { setError("Keep numbers under 100."); return; } try { const s = buildBodmasSteps({ fractions: fracs, operators: ops }); setSteps(s); setCurrentStep(0); setIsPlaying(false); } catch { setError("Could not compute. Check your inputs."); } } function loadPreset(idx: number) { const p = PRESETS[idx]; setFracs([...p.fracs]); setOps([...p.ops]); reset(); } function stepForward() { if (!steps || currentStep >= steps.length - 1) return; setCurrentStep((s) => s + 1); if (currentStep + 1 >= steps.length - 1) setIsPlaying(false); } function stepBack() { if (currentStep <= 0) return; setCurrentStep((s) => s - 1); } function togglePlay() { setIsPlaying((p) => !p); } function reset() { setSteps(null); setCurrentStep(0); setIsPlaying(false); } const step = steps ? steps[currentStep] : null; const opChoices = ["+", "−", "×", "÷"]; return (
{/* Presets */}
{PRESETS.map((p, i) => ( ))}
{/* Input */}

Enter a BODMAS expression with fractions

{fracs.map((f, i) => (
updateFrac(i, "num", e.target.value)} className="w-12 rounded-lg border-2 border-border bg-surface px-1.5 py-1 text-center text-base font-bold outline-none focus:border-unit-1" aria-label={`Numerator ${i + 1}`} />
updateFrac(i, "den", e.target.value)} className="w-12 rounded-lg border-2 border-border bg-surface px-1.5 py-1 text-center text-base font-bold outline-none focus:border-unit-1" aria-label={`Denominator ${i + 1}`} />
{i < ops.length && ( )}
))}
{error &&

{error}

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

Enter an expression above and click Go

) : ( <>

{step.label}

)}
{/* Controls */} 0} /> {/* Result */} {!done || !steps ? (

Result will appear here when steps are complete

) : ( <>

Final Answer

)}
); }