import type { GradeResult, ParenthesisSignProblem, UserAnswer, } from "./types"; const MIN_MULTIPLIER = -9; const MAX_MULTIPLIER = -1; const MIN_TERM = 1; const MAX_TERM = 9; const MAX_PROBLEM_COUNT = 9 * 8 * 2; type ProblemSeed = Pick< ParenthesisSignProblem, "multiplier" | "a" | "operator" | "b" >; function randomInt(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1)) + min; } function formatLatexTermPair(first: number, second: number) { const operator = second < 0 ? "-" : "+"; return `${first} ${operator} ${Math.abs(second)}`; } function createProblem({ multiplier, a, operator, b, }: ProblemSeed): ParenthesisSignProblem { const distributedA = multiplier * a; const distributedB = operator === "-" ? multiplier * -b : multiplier * b; const key = `${multiplier}:${a}:${operator}:${b}`; return { id: `parenthesis-sign:${key}`, key, multiplier, a, operator, b, distributedA, distributedB, latexBefore: `${multiplier}\\left(${a} ${operator} ${b}\\right)`, latexAfter: formatLatexTermPair(distributedA, distributedB), }; } function createAllProblemSeeds() { const seeds: ProblemSeed[] = []; for ( let multiplier = MIN_MULTIPLIER; multiplier <= MAX_MULTIPLIER; multiplier += 1 ) { for (let a = MIN_TERM; a <= MAX_TERM; a += 1) { for (let b = MIN_TERM; b <= MAX_TERM; b += 1) { if (a === b) continue; seeds.push({ multiplier, a, operator: "+", b }); seeds.push({ multiplier, a, operator: "-", b }); } } } return seeds; } const ALL_PROBLEM_SEEDS = createAllProblemSeeds(); function shuffle(items: T[]) { const shuffled = [...items]; for (let index = shuffled.length - 1; index > 0; index -= 1) { const targetIndex = randomInt(0, index); const current = shuffled[index]; shuffled[index] = shuffled[targetIndex]; shuffled[targetIndex] = current; } return shuffled; } export function generateProblem(): ParenthesisSignProblem { const a = randomInt(MIN_TERM, MAX_TERM); let b = randomInt(MIN_TERM, MAX_TERM); while (a === b) { b = randomInt(MIN_TERM, MAX_TERM); } return createProblem({ multiplier: randomInt(MIN_MULTIPLIER, MAX_MULTIPLIER), a, operator: Math.random() < 0.5 ? "+" : "-", b, }); } export function generateSession(count = 10): ParenthesisSignProblem[] { if (!Number.isInteger(count) || count < 1) { throw new Error("count must be a positive integer."); } if (count > MAX_PROBLEM_COUNT) { throw new Error(`count cannot exceed ${MAX_PROBLEM_COUNT}.`); } return shuffle(ALL_PROBLEM_SEEDS).slice(0, count).map(createProblem); } export function gradeAnswer( problem: ParenthesisSignProblem, answer: UserAnswer, ): GradeResult { return { correct: answer.a === problem.distributedA && answer.b === problem.distributedB, userAnswer: [answer.a, answer.b], correctAnswer: [problem.distributedA, problem.distributedB], }; }