109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
import { gcd } from "./fractions";
|
|
import { simplifyRatio } from "./ratios";
|
|
|
|
/** Parse a strict decimal string (digits and optional single dot). Rejects scientific notation, trailing letters, etc. */
|
|
export function parseStrictDecimal(input: string): number | null {
|
|
const s = input.trim();
|
|
if (!/^\d+(\.\d+)?$/.test(s)) return null;
|
|
return parseFloat(s);
|
|
}
|
|
|
|
/** Parse a strict integer string (digits only, no dot or letters). */
|
|
export function parseStrictInt(input: string): number | null {
|
|
const s = input.trim();
|
|
if (!/^\d+$/.test(s)) return null;
|
|
return parseInt(s, 10);
|
|
}
|
|
|
|
export type AnswerResult =
|
|
| { correct: true; simplified: boolean }
|
|
| { correct: false; message: string };
|
|
|
|
export function checkFractionAnswer(
|
|
userNum: number,
|
|
userDen: number,
|
|
expectedNum: number,
|
|
expectedDen: number,
|
|
requireSimplified: boolean = true,
|
|
): AnswerResult {
|
|
if (userDen === 0) {
|
|
return { correct: false, message: "Denominator cannot be zero" };
|
|
}
|
|
|
|
// Check mathematical equivalence
|
|
const isEquivalent = userNum * expectedDen === expectedNum * userDen;
|
|
|
|
if (!isEquivalent) {
|
|
return { correct: false, message: "That's not quite right. Try again!" };
|
|
}
|
|
|
|
const isSimplified = gcd(Math.abs(userNum), Math.abs(userDen)) === 1;
|
|
|
|
if (requireSimplified && !isSimplified) {
|
|
return { correct: true, simplified: false };
|
|
}
|
|
|
|
return { correct: true, simplified: true };
|
|
}
|
|
|
|
export function checkDecimalAnswer(
|
|
userValue: number,
|
|
expectedValue: number,
|
|
tolerance: number = 0.001,
|
|
): AnswerResult {
|
|
if (Math.abs(userValue - expectedValue) <= tolerance) {
|
|
return { correct: true, simplified: true };
|
|
}
|
|
return { correct: false, message: "That's not quite right. Try again!" };
|
|
}
|
|
|
|
export function checkRatioAnswer(
|
|
userParts: number[],
|
|
expectedParts: number[],
|
|
requireSimplified: boolean = true,
|
|
): AnswerResult {
|
|
if (userParts.length !== expectedParts.length) {
|
|
return { correct: false, message: "Check the number of parts in your ratio" };
|
|
}
|
|
|
|
const userSimplified = simplifyRatio(userParts);
|
|
const expectedSimplified = simplifyRatio(expectedParts);
|
|
|
|
const isEquivalent = userSimplified.every((v, i) => v === expectedSimplified[i]);
|
|
|
|
if (!isEquivalent) {
|
|
return { correct: false, message: "That's not quite right. Try again!" };
|
|
}
|
|
|
|
if (requireSimplified) {
|
|
const isAlreadySimplified = userParts.every((v, i) => v === userSimplified[i]);
|
|
return { correct: true, simplified: isAlreadySimplified };
|
|
}
|
|
|
|
return { correct: true, simplified: true };
|
|
}
|
|
|
|
export function checkIntegerAnswer(
|
|
userValue: number,
|
|
expectedValue: number,
|
|
): AnswerResult {
|
|
if (userValue === expectedValue) {
|
|
return { correct: true, simplified: true };
|
|
}
|
|
return { correct: false, message: "That's not quite right. Try again!" };
|
|
}
|
|
|
|
export function checkOrderingAnswer(
|
|
userOrder: number[],
|
|
expectedOrder: number[],
|
|
): AnswerResult {
|
|
if (userOrder.length !== expectedOrder.length) {
|
|
return { correct: false, message: "Make sure you've ordered all the numbers" };
|
|
}
|
|
const isCorrect = userOrder.every((v, i) => v === expectedOrder[i]);
|
|
if (isCorrect) {
|
|
return { correct: true, simplified: true };
|
|
}
|
|
return { correct: false, message: "Check your ordering. Try again!" };
|
|
}
|