import type { DistributedVariableTerm, VariableParenthesisProblem, VariableParenthesisTerm, VariableTermChoice, VariableUserAnswer, VariableGradeResult, } from "./variable-types"; const MIN_MULTIPLIER = -9; const MAX_MULTIPLIER = -1; const MIN_COEFFICIENT = 1; const MAX_COEFFICIENT = 9; const MAX_PROBLEM_COUNT = 9 * 9 * 9 * 2 * 2; interface VariableProblemSeed { multiplier: number; operator: "+" | "-"; firstTerm: VariableParenthesisTerm; secondTerm: VariableParenthesisTerm; } function randomInt(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1)) + min; } 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; } function createConstantTerm(coefficient: number): VariableParenthesisTerm { return { kind: "constant", coefficient, }; } function createVariableTerm(coefficient: number): VariableParenthesisTerm { return { kind: "variable", coefficient, variable: "x", }; } function termKey(term: VariableParenthesisTerm) { return `${term.kind}:${term.coefficient}`; } export function formatVariableTermLatex( term: Pick, ) { if (term.kind === "constant") { return `${term.coefficient}`; } if (term.coefficient === 1) { return "x"; } if (term.coefficient === -1) { return "-x"; } return `${term.coefficient}x`; } function formatParenthesisTermLatex(term: VariableParenthesisTerm) { return formatVariableTermLatex(term); } export function formatVariableExpressionLatex( terms: [DistributedVariableTerm, DistributedVariableTerm], ) { const [firstTerm, secondTerm] = terms; const operator = secondTerm.coefficient < 0 ? "-" : "+"; const positiveSecondTerm = { ...secondTerm, coefficient: Math.abs(secondTerm.coefficient), }; return `${firstTerm.latex} ${operator} ${formatVariableTermLatex( positiveSecondTerm, )}`; } function distributeTerm( multiplier: number, term: VariableParenthesisTerm, sign: 1 | -1, ): DistributedVariableTerm { const coefficient = multiplier * sign * term.coefficient; return { kind: term.kind, coefficient, variable: term.variable, latex: formatVariableTermLatex({ ...term, coefficient }), }; } function createChoice( key: string, termIndex: 0 | 1, term: DistributedVariableTerm, coefficient: number, ): VariableTermChoice { const signKey = coefficient < 0 ? "negative" : "positive"; return { id: `${key}:term-${termIndex}:${signKey}`, coefficient, variable: term.variable, latex: formatVariableTermLatex({ ...term, coefficient }), }; } function createChoices( key: string, termIndex: 0 | 1, term: DistributedVariableTerm, ) { const absoluteCoefficient = Math.abs(term.coefficient); const choices: VariableTermChoice[] = [ createChoice(key, termIndex, term, -absoluteCoefficient), createChoice(key, termIndex, term, absoluteCoefficient), ]; return shuffle(choices); } function createProblem(seed: VariableProblemSeed): VariableParenthesisProblem { const { multiplier, operator, firstTerm, secondTerm } = seed; const key = [ multiplier, termKey(firstTerm), operator, termKey(secondTerm), ].join(":"); const correctTerms: [DistributedVariableTerm, DistributedVariableTerm] = [ distributeTerm(multiplier, firstTerm, 1), distributeTerm(multiplier, secondTerm, operator === "-" ? -1 : 1), ]; const choices: [VariableTermChoice[], VariableTermChoice[]] = [ createChoices(key, 0, correctTerms[0]), createChoices(key, 1, correctTerms[1]), ]; return { id: `variable-parenthesis:${key}`, key, multiplier, operator, firstTerm, secondTerm, correctTerms, choices, latexBefore: `${multiplier}\\left(${formatParenthesisTermLatex( firstTerm, )} ${operator} ${formatParenthesisTermLatex(secondTerm)}\\right)`, latexAfter: formatVariableExpressionLatex(correctTerms), }; } function createAllProblemSeeds() { const seeds: VariableProblemSeed[] = []; for ( let multiplier = MIN_MULTIPLIER; multiplier <= MAX_MULTIPLIER; multiplier += 1 ) { for ( let constant = MIN_COEFFICIENT; constant <= MAX_COEFFICIENT; constant += 1 ) { for ( let variableCoefficient = MIN_COEFFICIENT; variableCoefficient <= MAX_COEFFICIENT; variableCoefficient += 1 ) { for (const operator of ["+", "-"] as const) { seeds.push({ multiplier, operator, firstTerm: createVariableTerm(variableCoefficient), secondTerm: createConstantTerm(constant), }); seeds.push({ multiplier, operator, firstTerm: createConstantTerm(constant), secondTerm: createVariableTerm(variableCoefficient), }); } } } } return seeds; } const ALL_PROBLEM_SEEDS = createAllProblemSeeds(); export function generateVariableParenthesisProblem(): VariableParenthesisProblem { const constant = randomInt(MIN_COEFFICIENT, MAX_COEFFICIENT); const variableCoefficient = randomInt(MIN_COEFFICIENT, MAX_COEFFICIENT); const variableFirst = Math.random() < 0.5; const operator = Math.random() < 0.5 ? "+" : "-"; return createProblem({ multiplier: randomInt(MIN_MULTIPLIER, MAX_MULTIPLIER), operator, firstTerm: variableFirst ? createVariableTerm(variableCoefficient) : createConstantTerm(constant), secondTerm: variableFirst ? createConstantTerm(constant) : createVariableTerm(variableCoefficient), }); } export function generateVariableParenthesisSession( count = 10, ): VariableParenthesisProblem[] { 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); } function findSelectedChoice( problem: VariableParenthesisProblem, termIndex: 0 | 1, choiceId: string | null, ): DistributedVariableTerm | null { if (!choiceId) return null; const choice = problem.choices[termIndex].find((item) => item.id === choiceId); if (!choice) return null; return { kind: problem.correctTerms[termIndex].kind, coefficient: choice.coefficient, variable: choice.variable, latex: choice.latex, }; } export function gradeVariableParenthesisAnswer( problem: VariableParenthesisProblem, answer: VariableUserAnswer, ): VariableGradeResult { const userAnswer: [ DistributedVariableTerm | null, DistributedVariableTerm | null, ] = [ findSelectedChoice(problem, 0, answer.selectedChoiceIds[0]), findSelectedChoice(problem, 1, answer.selectedChoiceIds[1]), ]; const correct = userAnswer[0]?.coefficient === problem.correctTerms[0].coefficient && userAnswer[1]?.coefficient === problem.correctTerms[1].coefficient; return { correct, userAnswer, correctAnswer: problem.correctTerms, }; }