Spaces:
Running
Running
Download math-engine.js from matt-ritchey/math4: direct link, hf CLI and curl.
- Browser
- Download file 11.1 kB
-
https://huggingface.co/spaces/matt-ritchey/math4/resolve/main/math-engine.js
- Command line
-
hf download hf://spaces/matt-ritchey/math4/math-engine.js
-
curl -L -o math-engine.js https://huggingface.co/spaces/matt-ritchey/math4/resolve/main/math-engine.js
11.1 kB
| 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})`] | |
| }; | |
| } | |
| }; | |