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); } /** Parse a strict signed integer string. Reject decimals, scientific notation, and trailing text. */ export function parseStrictSignedInt(input: string): number | null { const s = input.trim(); if (!/^-?\d+$/.test(s)) return null; return parseInt(s, 10); } /** Parse a strict signed decimal string. Reject scientific notation and trailing text. */ export function parseStrictSignedDecimal(input: string): number | null { const s = input.trim(); if (!/^-?\d+(\.\d+)?$/.test(s)) return null; return parseFloat(s); } 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!" }; } // Any non-zero denominator gives the same value for zero, so don't force 0/1 in the UI. if (userNum === 0) { return { correct: true, simplified: true }; } 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!" }; }