form 1 topics added

This commit is contained in:
2026-07-05 11:12:45 -04:00
parent a614532810
commit a23fa53772
58 changed files with 7089 additions and 475 deletions

View File

@@ -1,6 +1,6 @@
import type { MathProblem, Difficulty } from "../types";
import { randomInt, randomChoice } from "@/lib/utils";
import * as frac from "@/lib/math/fractions";
import { Fraction, lcm } from "@/lib/math/fractions";
let counter = 0;
function nextId() {
@@ -35,13 +35,13 @@ export function generateFractionAddSubtract(difficulty: Difficulty): MathProblem
}
const prompt = `\\frac{${n1}}{${d1}} ${op} \\frac{${n2}}{${d2}}`;
const commonDen = frac.lcm(d1, d2);
const commonDen = lcm(d1, d2);
const convertedN1 = n1 * (commonDen / d1);
const convertedN2 = n2 * (commonDen / d2);
const rawNum = operation === "add"
? convertedN1 + convertedN2
: convertedN1 - convertedN2;
const [ansNum, ansDen] = frac.simplify(rawNum, commonDen);
const { n: ansNum, d: ansDen } = new Fraction(rawNum, commonDen).simplified();
const steps = d1 === d2
? [
@@ -100,7 +100,7 @@ export function generateFractionMultiply(difficulty: Difficulty): MathProblem {
const n1 = randomInt(1, d1 - 1);
const n2 = randomInt(1, d2 - 1);
const [ansNum, ansDen] = frac.multiply(n1, d1, n2, d2);
const { n: ansNum, d: ansDen } = Fraction.of(n1, d1).times(Fraction.of(n2, d2)).simplified();
return {
id: nextId(),
@@ -126,7 +126,7 @@ export function generateFractionDivide(difficulty: Difficulty): MathProblem {
const n1 = randomInt(1, d1 - 1);
const n2 = randomInt(1, d2 - 1);
const [ansNum, ansDen] = frac.divide(n1, d1, n2, d2);
const { n: ansNum, d: ansDen } = Fraction.of(n1, d1).dividedBy(Fraction.of(n2, d2)).simplified();
return {
id: nextId(),
@@ -168,6 +168,150 @@ export function generateFractionOfQuantity(difficulty: Difficulty): MathProblem
};
}
/** A proper-fraction numerator with a random sign, magnitude 1..den-1. */
function signedProperNum(den: number): number {
const mag = randomInt(1, Math.max(1, den - 1));
return Math.random() < 0.5 ? -mag : mag;
}
/** Two proper fractions where at least one is negative (this is the negative-fractions topic). */
function signedFractionPair(d1: number, d2: number): [Fraction, Fraction] {
let n1 = signedProperNum(d1);
let n2 = signedProperNum(d2);
if (n1 > 0 && n2 > 0) {
if (Math.random() < 0.5) n1 = -n1;
else n2 = -n2;
}
return [new Fraction(n1, d1), new Fraction(n2, d2)];
}
function signedOperand(f: Fraction): string {
return f.n < 0 ? `\\left(${f.toSignedKatex()}\\right)` : f.toSignedKatex();
}
export function generateSignedFractionAddSubtract(difficulty: Difficulty): MathProblem {
const operation = randomChoice(["+", "-"] as const);
const dens = difficulty === 1 ? [2, 3, 4] : difficulty === 2 ? [2, 3, 4, 5, 6] : [3, 4, 5, 6, 8];
const d1 = randomChoice(dens);
const d2 = randomChoice(dens);
const [a, b] = signedFractionPair(d1, d2);
const result = (operation === "+" ? a.plus(b) : a.minus(b)).simplified();
return {
id: nextId(),
prompt: `${a.toSignedKatex()} ${operation} ${signedOperand(b)}`,
answer: { kind: "fraction", numerator: result.n, denominator: result.d },
hints: [
d1 === d2 ? "The denominators already match." : "Find a common denominator first.",
"Combine the numerators using the integer sign rules (subtracting a negative adds).",
"Simplify your answer.",
],
steps: [
{
explanation: d1 === d2 ? `Keep the denominator ${d1}.` : `Use a common denominator: LCM(${d1}, ${d2}) = ${lcm(d1, d2)}.`,
},
{ explanation: `${operation === "+" ? "Add" : "Subtract"} the numerators, watching the signs.` },
{ explanation: "Simplify to lowest terms.", math: result.toSignedKatex() },
],
};
}
export function generateSignedFractionMultiplyDivide(difficulty: Difficulty): MathProblem {
const operation = randomChoice(["\\times", "\\div"] as const);
const dens = difficulty === 1 ? [2, 3, 4] : difficulty === 2 ? [2, 3, 4, 5] : [3, 4, 5, 6, 7];
const d1 = randomChoice(dens);
const d2 = randomChoice(dens);
const [a, b] = signedFractionPair(d1, d2);
const result = (operation === "\\times" ? a.times(b) : a.dividedBy(b)).simplified();
const sameSign = a.n < 0 === b.n < 0;
return {
id: nextId(),
prompt: `${a.toSignedKatex()} ${operation} ${signedOperand(b)}`,
answer: { kind: "fraction", numerator: result.n, denominator: result.d },
hints: [
operation === "\\div" ? "Keep, change, flip: multiply by the reciprocal." : "Multiply numerators and denominators.",
`The signs are ${sameSign ? "the same, so the answer is positive" : "different, so the answer is negative"}.`,
"Simplify your answer.",
],
steps: [
operation === "\\div"
? { explanation: "Multiply by the reciprocal of the second fraction (keep, change, flip)." }
: { explanation: "Multiply the numerators and multiply the denominators." },
{ explanation: `Same signs give positive, different signs give negative — here the answer is ${result.n < 0 ? "negative" : "positive"}.` },
{ explanation: "Simplify to lowest terms.", math: result.toSignedKatex() },
],
};
}
export function generateFractionBodmas(difficulty: Difficulty): MathProblem {
const dens = difficulty === 1 ? [2, 3, 4] : difficulty === 2 ? [2, 3, 4, 5] : [2, 3, 4, 5, 6];
const mk = () => {
const d = randomChoice(dens);
return new Fraction(randomInt(1, d - 1), d);
};
const f1 = mk(), f2 = mk(), f3 = mk();
const multFirst = Math.random() < 0.5;
let prompt: string;
let result: Fraction;
if (multFirst) {
const prod = f2.times(f3);
// avoid a negative answer in this (positive-fractions) unit
const op = f1.toDecimal() >= prod.toDecimal() && Math.random() < 0.5 ? "-" : "+";
result = (op === "+" ? f1.plus(prod) : f1.minus(prod)).simplified();
prompt = `${f1.toKatex()} ${op} ${f2.toKatex()} \\times ${f3.toKatex()}`;
} else {
const prod = f1.times(f2);
const op = prod.toDecimal() >= f3.toDecimal() && Math.random() < 0.5 ? "-" : "+";
result = (op === "+" ? prod.plus(f3) : prod.minus(f3)).simplified();
prompt = `${f1.toKatex()} \\times ${f2.toKatex()} ${op} ${f3.toKatex()}`;
}
return {
id: nextId(),
prompt,
answer: { kind: "fraction", numerator: result.n, denominator: result.d },
hints: [
"BODMAS: do the multiplication before the addition or subtraction.",
"Multiply the two fractions first, then combine with a common denominator.",
],
steps: [
{ explanation: "Multiply first (that's the B-O-D-M before A-S)." },
{ explanation: "Then add or subtract, using a common denominator." },
{ explanation: "Simplify.", math: result.toKatex() },
],
};
}
/** Combined signed-fraction practice covering +, -, x and / (Unit 5 topic). */
export function generateSignedFraction(difficulty: Difficulty): MathProblem {
const operation = randomChoice(["+", "-", "\\times", "\\div"] as const);
const dens = difficulty === 1 ? [2, 3, 4] : difficulty === 2 ? [2, 3, 4, 5] : [3, 4, 5, 6];
const [a, b] = signedFractionPair(randomChoice(dens), randomChoice(dens));
let result: Fraction;
if (operation === "+") result = a.plus(b);
else if (operation === "-") result = a.minus(b);
else if (operation === "\\times") result = a.times(b);
else result = a.dividedBy(b);
result = result.simplified();
return {
id: nextId(),
prompt: `${a.toSignedKatex()} ${operation} ${signedOperand(b)}`,
answer: { kind: "fraction", numerator: result.n, denominator: result.d },
hints: [
"Apply the integer sign rules as you work.",
"Give your answer in its simplest form.",
],
steps: [
{ explanation: "Work through the operation, watching the signs." },
{ explanation: "Simplify.", math: result.toSignedKatex() },
],
};
}
export function generateWholeFromFraction(difficulty: Difficulty): MathProblem {
const denChoices = difficulty === 1 ? [2, 3, 4, 5] : difficulty === 2 ? [3, 5, 6, 8] : [7, 8, 9, 12];
const den = randomChoice(denChoices);