105 lines
3.6 KiB
TypeScript
105 lines
3.6 KiB
TypeScript
import type { MathProblem, Difficulty } from "../types";
|
||
import { randomInt } from "@/lib/utils";
|
||
|
||
let counter = 0;
|
||
function nextId() {
|
||
return `ns-${++counter}-${Date.now()}`;
|
||
}
|
||
|
||
export function generateBinaryToDenary(difficulty: Difficulty): MathProblem {
|
||
const bits = difficulty === 1 ? randomInt(3, 4) : difficulty === 2 ? randomInt(5, 6) : randomInt(7, 8);
|
||
// ensure a leading 1 so the length is meaningful
|
||
let binary = "1";
|
||
for (let i = 1; i < bits; i++) binary += Math.random() < 0.5 ? "0" : "1";
|
||
|
||
const len = binary.length;
|
||
let value = 0;
|
||
const parts: string[] = [];
|
||
for (let i = 0; i < len; i++) {
|
||
const exp = len - 1 - i;
|
||
const digit = parseInt(binary[i], 10);
|
||
const place = Math.pow(2, exp);
|
||
if (digit === 1) parts.push(`${place}`);
|
||
value += digit * place;
|
||
}
|
||
|
||
return {
|
||
id: nextId(),
|
||
prompt: `${binary}_{2} = ?_{10}`,
|
||
answer: { kind: "integer", value },
|
||
hints: [
|
||
"Each column is a power of 2, doubling from the right: 1, 2, 4, 8, 16 …",
|
||
"Add up the place values wherever there is a 1.",
|
||
`${parts.join(" + ")} = ${value}`,
|
||
],
|
||
steps: [
|
||
{ explanation: "Label each column with its power of 2, starting at 1 on the right." },
|
||
{ explanation: `Add the place values under each 1: ${parts.join(" + ")}` },
|
||
{ explanation: `${binary} in binary = ${value} in denary.` },
|
||
],
|
||
};
|
||
}
|
||
|
||
export function generateQuaternaryToDenary(difficulty: Difficulty): MathProblem {
|
||
const digits = difficulty === 1 ? randomInt(2, 3) : difficulty === 2 ? randomInt(3, 4) : randomInt(4, 5);
|
||
let quaternary = `${randomInt(1, 3)}`; // leading non-zero digit
|
||
for (let i = 1; i < digits; i++) quaternary += `${randomInt(0, 3)}`;
|
||
|
||
const len = quaternary.length;
|
||
let value = 0;
|
||
const parts: string[] = [];
|
||
for (let i = 0; i < len; i++) {
|
||
const exp = len - 1 - i;
|
||
const digit = parseInt(quaternary[i], 10);
|
||
const place = Math.pow(4, exp);
|
||
if (digit !== 0) parts.push(`${digit}×${place}`);
|
||
value += digit * place;
|
||
}
|
||
|
||
return {
|
||
id: nextId(),
|
||
prompt: `${quaternary}_{4} = ?_{10}`,
|
||
answer: { kind: "integer", value },
|
||
hints: [
|
||
"Each column is a power of 4, from the right: 1, 4, 16, 64 …",
|
||
"Multiply each digit by its place value and add them up.",
|
||
`${parts.join(" + ")} = ${value}`,
|
||
],
|
||
steps: [
|
||
{ explanation: "Label each column with its power of 4, starting at 1 on the right." },
|
||
{ explanation: `Add the products: ${parts.join(" + ")}` },
|
||
{ explanation: `${quaternary} in base 4 = ${value} in denary.` },
|
||
],
|
||
};
|
||
}
|
||
|
||
export function generateDigitValue(difficulty: Difficulty): MathProblem {
|
||
const digits = difficulty === 1 ? randomInt(3, 4) : difficulty === 2 ? 5 : 6;
|
||
const min = Math.pow(10, digits - 1);
|
||
const max = Math.pow(10, digits) - 1;
|
||
const n = randomInt(min, max);
|
||
const str = `${n}`;
|
||
|
||
// pick a non-zero digit position
|
||
const nonZeroPositions = str.split("").map((d, i) => ({ d, i })).filter((x) => x.d !== "0");
|
||
const pick = nonZeroPositions[randomInt(0, nonZeroPositions.length - 1)];
|
||
const exp = str.length - 1 - pick.i;
|
||
const place = Math.pow(10, exp);
|
||
const value = parseInt(pick.d, 10) * place;
|
||
|
||
return {
|
||
id: nextId(),
|
||
prompt: `\\text{In } ${n} \\text{, what is the value of the digit } ${pick.d}\\text{?}`,
|
||
answer: { kind: "integer", value },
|
||
hints: [
|
||
"Find which column the digit sits in.",
|
||
`That column is worth 10^${exp} = ${place}.`,
|
||
`${pick.d} × ${place} = ${value}`,
|
||
],
|
||
steps: [
|
||
{ explanation: `The digit ${pick.d} is in the 10^${exp} (${place}) column.` },
|
||
{ explanation: `Its value is ${pick.d} × ${place} = ${value}.` },
|
||
],
|
||
};
|
||
}
|