const MathEngine = { _rand(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min }, _pick(arr) { return arr[Math.floor(Math.random() * arr.length)] }, generateProblem(topic, difficulty) { const level = Math.min(3, Math.max(1, difficulty || 1)); const ops = ['addition', 'subtraction', 'multiplication', 'division', 'word-problems']; const t = topic === 'mixed' ? this._pick(ops) : topic; switch (t) { case 'addition': return this._genAddition(level); case 'subtraction': return this._genSubtraction(level); case 'multiplication': return this._genMultiplication(level); case 'division': return this._genDivision(level); case 'word-problems': return this._genWordProblem(level); default: return this._genAddition(level); } }, generateQuizQuestions(topic, difficulty, count) { const questions = []; for (let i = 0; i < count; i++) { questions.push(this.generateProblem(topic, difficulty)); } return questions; }, _genAddition(level) { let a, b; if (level === 1) { a = this._rand(10, 99); b = this._rand(10, 99); } else if (level === 2) { a = this._rand(100, 999); b = this._rand(100, 999); } else { a = this._rand(1000, 9999); b = this._rand(1000, 9999); } return { type: 'addition', display: `${a} + ${b}`, answer: a + b, steps: this._additionSteps(a, b) }; }, _additionSteps(a, b) { const result = a + b; const s = []; s.push('Line up the numbers by place value:'); s.push(`${a} + ${b}`); const sa = String(a), sb = String(b); const maxLen = Math.max(sa.length, sb.length); const pa = sa.padStart(maxLen, '0'); const pb = sb.padStart(maxLen, '0'); const places = ['ones', 'tens', 'hundreds', 'thousands', 'ten-thousands']; let carry = 0; for (let i = maxLen - 1; i >= 0; i--) { const idx = maxLen - 1 - i; const da = parseInt(pa[i]); const db = parseInt(pb[i]); const sum = da + db + carry; const digit = sum % 10; const newCarry = Math.floor(sum / 10); const pname = places[idx] || 'next'; if (carry > 0) { s.push(`${pname}: ${da} + ${db} + carried ${carry} = ${sum}. Write ${digit}.`); } else { s.push(`${pname}: ${da} + ${db} = ${sum}. Write ${digit}.`); } carry = newCarry; } if (carry > 0) s.push(`Write the carried ${carry} at the front.`); s.push(`Answer: ${result}`); return s; }, _genSubtraction(level) { let a, b; if (level === 1) { a = this._rand(10, 99); b = this._rand(10, a); } else if (level === 2) { a = this._rand(100, 999); b = this._rand(100, a); } else { a = this._rand(1000, 9999); b = this._rand(1000, a); } return { type: 'subtraction', display: `${a} - ${b}`, answer: a - b, steps: this._subtractionSteps(a, b) }; }, _subtractionSteps(a, b) { const result = a - b; const s = []; s.push('Line up the numbers by place value:'); s.push(`${a} - ${b}`); const sa = String(a), sb = String(b); const maxLen = Math.max(sa.length, sb.length); const pa = sa.padStart(maxLen, '0'); const pb = sb.padStart(maxLen, '0'); const places = ['ones', 'tens', 'hundreds', 'thousands', 'ten-thousands']; let digits = pa.split('').map(Number); for (let i = maxLen - 1; i >= 0; i--) { const idx = maxLen - 1 - i; let da = digits[i]; const db = parseInt(pb[i]); const pname = places[idx] || 'next'; if (da < db) { digits[i - 1]--; digits[i] += 10; da = digits[i]; const borrowFrom = places[idx + 1] || 'next'; s.push(`Borrow 1 from ${borrowFrom}: ${da - 10} becomes ${da}.`); } s.push(`${pname}: ${da} - ${db} = ${da - db}. Write ${da - db}.`); } s.push(`Answer: ${result}`); return s; }, _genMultiplication(level) { let a, b; if (level === 1) { a = this._rand(2, 9); b = this._rand(10, 99); } else if (level === 2) { if (Math.random() < 0.5) { a = this._rand(2, 9); b = this._rand(100, 999); } else { a = this._rand(11, 99); b = this._rand(11, 99); } } else { a = this._rand(11, 99); b = this._rand(100, 999); } return { type: 'multiplication', display: `${a} × ${b}`, answer: a * b, steps: this._multiplicationSteps(a, b) }; }, _multiplicationSteps(a, b) { const result = a * b; const s = []; s.push(`Multiply ${a} by ${b}:`); const sb = String(b); if (sb.length === 1) { let carry = 0; const sa = String(a); for (let i = sa.length - 1; i >= 0; i--) { const digit = parseInt(sa[i]); const product = digit * b + carry; const write = product % 10; carry = Math.floor(product / 10); s.push(`${digit} × ${b} + carry = ${product}. Write ${write}.`); } if (carry > 0) s.push(`Write the carry ${carry}.`); } else { const bStr = String(b); let partials = []; for (let i = bStr.length - 1; i >= 0; i--) { const d = parseInt(bStr[i]); const p = a * d * Math.pow(10, bStr.length - 1 - i); partials.push(p); s.push(`Multiply ${a} by ${d}${d !== 0 ? ' in the ' + (bStr.length - 1 - i === 0 ? 'ones' : bStr.length - 1 - i === 1 ? 'tens' : 'hundreds') + ' place' : ''}: ${a} × ${d} = ${a * d}${d !== 0 ? ' (add ' + (bStr.length - 1 - i === 0 ? '0' : bStr.length - 1 - i === 1 ? '1 zero' : '2 zeros') + ')' : ''}.`); } const sum = partials.reduce((x, y) => x + y, 0); s.push(`Add the partial products: ${partials.filter(p => p !== 0).join(' + ')} = ${sum}.`); } s.push(`Answer: ${result}`); return s; }, _genDivision(level) { let divisor, dividend; if (level === 1) { divisor = this._rand(2, 9); const quotient = this._rand(10, 99); dividend = divisor * quotient; } else if (level === 2) { divisor = this._rand(2, 9); const quotient = this._rand(10, 99); dividend = divisor * quotient + this._rand(0, divisor - 1); } else { divisor = this._rand(2, 12); const quotient = this._rand(100, 999); dividend = divisor * quotient + this._rand(0, divisor - 1); } const quotient = Math.floor(dividend / divisor); const remainder = dividend % divisor; return { type: 'division', display: `${dividend} ÷ ${divisor}`, answer: quotient, answerDisplay: remainder > 0 ? `${quotient} R${remainder}` : String(quotient), steps: this._divisionSteps(dividend, divisor) }; }, _divisionSteps(dividend, divisor) { const s = []; const quotient = Math.floor(dividend / divisor); const remainder = dividend % divisor; s.push(`Divide ${dividend} by ${divisor}:`); s.push(`${divisor} goes into ${dividend}...`); s.push(`${divisor} × ${quotient} = ${divisor * quotient}`); if (remainder > 0) { s.push(`${dividend} - ${divisor * quotient} = ${remainder} (remainder)`); s.push(`Answer: ${quotient} R${remainder}`); } else { s.push(`${dividend} - ${divisor * quotient} = 0 (no remainder)`); s.push(`Answer: ${quotient}`); } return s; }, _genWordProblem(level) { const templates = [ { fn: () => { const a = this._rand(10, 99); const b = this._rand(1, a); const names = ['Sarah', 'Tom', 'Emma', 'Jack', 'Lily', 'Sam']; const items = ['stickers', 'marbles', 'crayons', 'books', 'coins', 'pencils']; const n = this._pick(names); const f = this._pick(names.filter(x => x !== n)); const i = this._pick(items); return { display: `${n} has ${a} ${i}. ${f} gives ${n} ${b} more ${i}. How many ${i} does ${n} have now?`, answer: a + b, op: 'addition' }; }}, { fn: () => { const a = this._rand(20, 100); const b = this._rand(1, a - 1); const items = ['cookies', 'apples', 'toys', 'balloons', 'candies', 'pennies']; const n = this._pick(['Mia', 'Lucas', 'Ava', 'Noah', 'Sophia', 'Ethan']); const i = this._pick(items); return { display: `${n} had ${a} ${i}. ${n} gave away ${b} ${i}. How many ${i} does ${n} have left?`, answer: a - b, op: 'subtraction' }; }}, { fn: () => { const a = this._rand(2, 12); const b = this._rand(2, 12); const items1 = this._pick(['bags', 'boxes', 'packs', 'baskets', 'trays']); const items2 = this._pick(['apples', 'cookies', 'eggs', 'marbles', 'crayons', 'pennies']); return { display: `There are ${a} ${items1}. Each ${items1[0]}${items1[0] === 'boxes' ? '' : ''} has ${b} ${items2}. How many ${items2} are there total?`, answer: a * b, op: 'multiplication' }; }}, { fn: () => { const b = this._rand(2, 9); const q = this._rand(10, 20); const a = b * q; const items = this._pick(['cookies', 'candies', 'books', 'toys', 'pencils', 'stickers']); const n = this._pick(['Aiden', 'Olivia', 'James', 'Isabella', 'Benjamin', 'Charlotte']); return { display: `${n} has ${a} ${items}. ${n} shares them equally among ${b} friends. How many ${items} does each friend get?`, answer: q, op: 'division' }; }}, { fn: () => { const b = this._rand(2, 12); const q = this._rand(5, 15); const a = b * q; const items = this._pick(['seats', 'chairs', 'rows', 'crates', 'shelves', 'bags']); return { display: `There are ${a} ${items}. They are arranged into ${b} equal groups. How many ${items} are in each group?`, answer: q, op: 'division' }; }}, { fn: () => { const a = this._rand(100, 999); const b = this._rand(100, 999); const items = this._pick(['tickets sold', 'miles driven', 'pages read', 'points scored', 'dollars raised']); return { display: `School A ${this._pick(['raised', 'sold', 'collected', 'earned'])} ${a} ${items}. School B ${this._pick(['raised', 'sold', 'collected', 'earned'])} ${b} ${items}. How many ${items} in total?`, answer: a + b, op: 'addition' }; }}, { fn: () => { const b = this._rand(2, 9); const q = this._rand(10, 99); const a = b * q; const items = this._pick(['pencils', 'rulers', 'notebooks', 'erasers', 'markers']); return { display: `${a} ${items} are packed into boxes. Each box holds ${b} ${items}. How many boxes are needed?`, answer: q, op: 'division' }; }}, { fn: () => { const names = ['Zoe', 'Max', 'Chloe', 'Liam', 'Grace', 'Owen']; const items = this._pick(['bracelets', 'drawings', 'cards', 'origami', 'paintings', 'poems']); const n = this._pick(names); const a = this._rand(10, 99); const b = this._rand(10, 99); return { display: `${n} made ${a} ${items} on Monday and ${b} ${items} on Tuesday. How many more ${items} did ${n} make on Monday than Tuesday?`, answer: Math.abs(a - b), op: 'subtraction' }; }}, ]; const t = this._pick(templates)(); return { type: 'word-problem', display: t.display, answer: t.answer, steps: [t.display, `The answer is ${t.answer}. (${t.op})`] }; } };