Fix negation parsing in guessing game
Correctly parse negations in user-provided questions to accurately update the search space.
This commit is contained in:
parent
62c6acc388
commit
591b73c071
1 changed files with 66 additions and 42 deletions
|
|
@ -33,30 +33,42 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
|||
const [questionFeedback, setQuestionFeedback] = useState('');
|
||||
|
||||
// Function to determine the optimal answer to keep search space as large as possible
|
||||
const getOptimalAnswer = (question: string): { understood: boolean, answer: boolean, questionType?: string, value?: number } => {
|
||||
const getOptimalAnswer = (question: string): { understood: boolean, answer: boolean, questionType?: string, value?: number, isNegated?: boolean } => {
|
||||
const q = question.toLowerCase().trim();
|
||||
|
||||
// Remove question marks and normalize
|
||||
const normalizedQ = q.replace(/\?/g, '').trim();
|
||||
|
||||
// Check for negations
|
||||
const isNegated = normalizedQ.includes(' not ') || normalizedQ.includes("n't") || normalizedQ.includes(" isn't") || normalizedQ.includes(" doesn't");
|
||||
|
||||
// Remove negation words for parsing the base question
|
||||
const baseQuestion = normalizedQ
|
||||
.replace(/ not /g, ' ')
|
||||
.replace(/n't/g, '')
|
||||
.replace(/ isn't/g, ' is')
|
||||
.replace(/ doesn't/g, ' does')
|
||||
.replace(/\bis\s+not\b/g, 'is')
|
||||
.trim();
|
||||
|
||||
// Check for various question patterns
|
||||
if (normalizedQ.includes('is') && normalizedQ.includes('number')) {
|
||||
if (baseQuestion.includes('is') && baseQuestion.includes('number')) {
|
||||
// Is the number...
|
||||
if (normalizedQ.includes('even')) {
|
||||
if (baseQuestion.includes('even')) {
|
||||
// Count how many valid numbers are even vs odd
|
||||
const evenCount = Array.from(validNumbers).filter(n => n % 2 === 0).length;
|
||||
const oddCount = validNumbers.size - evenCount;
|
||||
// Choose answer that keeps more numbers
|
||||
const answer = evenCount > oddCount;
|
||||
return { understood: true, answer, questionType: 'even' };
|
||||
return { understood: true, answer, questionType: 'even', isNegated };
|
||||
}
|
||||
if (normalizedQ.includes('odd')) {
|
||||
if (baseQuestion.includes('odd')) {
|
||||
const oddCount = Array.from(validNumbers).filter(n => n % 2 !== 0).length;
|
||||
const evenCount = validNumbers.size - oddCount;
|
||||
const answer = oddCount > evenCount;
|
||||
return { understood: true, answer, questionType: 'odd' };
|
||||
return { understood: true, answer, questionType: 'odd', isNegated };
|
||||
}
|
||||
if (normalizedQ.includes('prime')) {
|
||||
if (baseQuestion.includes('prime')) {
|
||||
const isPrime = (num: number): boolean => {
|
||||
if (num < 2) return false;
|
||||
for (let i = 2; i <= Math.sqrt(num); i++) {
|
||||
|
|
@ -67,97 +79,98 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
|||
const primeCount = Array.from(validNumbers).filter(n => isPrime(n)).length;
|
||||
const nonPrimeCount = validNumbers.size - primeCount;
|
||||
const answer = primeCount > nonPrimeCount;
|
||||
return { understood: true, answer, questionType: 'prime' };
|
||||
return { understood: true, answer, questionType: 'prime', isNegated };
|
||||
}
|
||||
if (normalizedQ.includes('perfect square')) {
|
||||
if (baseQuestion.includes('perfect square')) {
|
||||
const perfectSquareCount = Array.from(validNumbers).filter(n => Math.sqrt(n) % 1 === 0).length;
|
||||
const nonPerfectSquareCount = validNumbers.size - perfectSquareCount;
|
||||
const answer = perfectSquareCount > nonPerfectSquareCount;
|
||||
return { understood: true, answer, questionType: 'perfectSquare' };
|
||||
return { understood: true, answer, questionType: 'perfectSquare', isNegated };
|
||||
}
|
||||
}
|
||||
|
||||
// Check for divisibility questions
|
||||
if (normalizedQ.includes('divisible by') || normalizedQ.includes('multiple of')) {
|
||||
const matches = normalizedQ.match(/(?:divisible by|multiple of)\s+(\d+)/);
|
||||
// Check for divisibility questions (including "multiple of")
|
||||
if (baseQuestion.includes('divisible by') || baseQuestion.includes('multiple of')) {
|
||||
const matches = baseQuestion.match(/(?:divisible by|multiple of)\s+(\d+)/);
|
||||
if (matches) {
|
||||
const divisor = parseInt(matches[1]);
|
||||
const divisibleCount = Array.from(validNumbers).filter(n => n % divisor === 0).length;
|
||||
const nonDivisibleCount = validNumbers.size - divisibleCount;
|
||||
const answer = divisibleCount > nonDivisibleCount;
|
||||
return { understood: true, answer, questionType: 'divisible', value: divisor };
|
||||
return { understood: true, answer, questionType: 'divisible', value: divisor, isNegated };
|
||||
}
|
||||
}
|
||||
|
||||
// Check for range questions
|
||||
if (normalizedQ.includes('greater than') || normalizedQ.includes('bigger than') || normalizedQ.includes('larger than')) {
|
||||
const matches = normalizedQ.match(/(?:greater than|bigger than|larger than)\s+(\d+)/);
|
||||
if (baseQuestion.includes('greater than') || baseQuestion.includes('bigger than') || baseQuestion.includes('larger than')) {
|
||||
const matches = baseQuestion.match(/(?:greater than|bigger than|larger than)\s+(\d+)/);
|
||||
if (matches) {
|
||||
const threshold = parseInt(matches[1]);
|
||||
const greaterCount = Array.from(validNumbers).filter(n => n > threshold).length;
|
||||
const lessEqualCount = validNumbers.size - greaterCount;
|
||||
const answer = greaterCount > lessEqualCount;
|
||||
return { understood: true, answer, questionType: 'greaterThan', value: threshold };
|
||||
return { understood: true, answer, questionType: 'greaterThan', value: threshold, isNegated };
|
||||
}
|
||||
}
|
||||
|
||||
if (normalizedQ.includes('less than') || normalizedQ.includes('smaller than')) {
|
||||
const matches = normalizedQ.match(/(?:less than|smaller than)\s+(\d+)/);
|
||||
if (baseQuestion.includes('less than') || baseQuestion.includes('smaller than')) {
|
||||
const matches = baseQuestion.match(/(?:less than|smaller than)\s+(\d+)/);
|
||||
if (matches) {
|
||||
const threshold = parseInt(matches[1]);
|
||||
const lessCount = Array.from(validNumbers).filter(n => n < threshold).length;
|
||||
const greaterEqualCount = validNumbers.size - lessCount;
|
||||
const answer = lessCount > greaterEqualCount;
|
||||
return { understood: true, answer, questionType: 'lessThan', value: threshold };
|
||||
return { understood: true, answer, questionType: 'lessThan', value: threshold, isNegated };
|
||||
}
|
||||
}
|
||||
|
||||
// Check for digit contains questions
|
||||
if (normalizedQ.includes('contain') && normalizedQ.includes('digit')) {
|
||||
const matches = normalizedQ.match(/contain(?:s)?\s+(?:the\s+)?digit\s+(\d)/);
|
||||
if (baseQuestion.includes('contain') && baseQuestion.includes('digit')) {
|
||||
const matches = baseQuestion.match(/contain(?:s)?\s+(?:the\s+)?digit\s+(\d)/);
|
||||
if (matches) {
|
||||
const digit = matches[1];
|
||||
const containsCount = Array.from(validNumbers).filter(n => n.toString().includes(digit)).length;
|
||||
const doesntContainCount = validNumbers.size - containsCount;
|
||||
const answer = containsCount > doesntContainCount;
|
||||
return { understood: true, answer, questionType: 'containsDigit', value: parseInt(digit) };
|
||||
return { understood: true, answer, questionType: 'containsDigit', value: parseInt(digit), isNegated };
|
||||
}
|
||||
}
|
||||
|
||||
// Check for specific number questions
|
||||
if (normalizedQ.includes('is it') || normalizedQ.includes('is the number')) {
|
||||
const matches = normalizedQ.match(/(?:is it|is the number)\s+(\d+)/);
|
||||
if (baseQuestion.includes('is it') || baseQuestion.includes('is the number')) {
|
||||
const matches = baseQuestion.match(/(?:is it|is the number)\s+(\d+)/);
|
||||
if (matches) {
|
||||
const num = parseInt(matches[1]);
|
||||
// If there's only one number left and they're asking about it, we must answer truthfully
|
||||
if (validNumbers.size === 1) {
|
||||
const answer = validNumbers.has(num);
|
||||
return { understood: true, answer, questionType: 'equals', value: num };
|
||||
return { understood: true, answer, questionType: 'equals', value: num, isNegated };
|
||||
}
|
||||
// Otherwise, say no to keep the game going unless this number isn't even valid
|
||||
const answer = false;
|
||||
return { understood: true, answer, questionType: 'equals', value: num };
|
||||
return { understood: true, answer, questionType: 'equals', value: num, isNegated };
|
||||
}
|
||||
}
|
||||
|
||||
return { understood: false, answer: false };
|
||||
};
|
||||
|
||||
// Function to update valid numbers based on question and answer
|
||||
const updateValidNumbersFromQuestion = (questionType: string, answer: boolean, value?: number) => {
|
||||
// Function to update valid numbers based on question and answer, handling negations
|
||||
const updateValidNumbersFromQuestion = (questionType: string, answer: boolean, value?: number, isNegated?: boolean) => {
|
||||
const newValidNumbers = new Set<number>();
|
||||
|
||||
for (let num = 1; num <= maxRange; num++) {
|
||||
if (!validNumbers.has(num)) continue; // Skip already invalid numbers
|
||||
|
||||
let shouldKeep = false;
|
||||
let baseCondition = false;
|
||||
|
||||
switch (questionType) {
|
||||
case 'even':
|
||||
shouldKeep = answer ? (num % 2 === 0) : (num % 2 !== 0);
|
||||
baseCondition = (num % 2 === 0);
|
||||
break;
|
||||
case 'odd':
|
||||
shouldKeep = answer ? (num % 2 !== 0) : (num % 2 === 0);
|
||||
baseCondition = (num % 2 !== 0);
|
||||
break;
|
||||
case 'prime':
|
||||
const isPrimeNum = (n: number): boolean => {
|
||||
|
|
@ -167,41 +180,52 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
|||
}
|
||||
return true;
|
||||
};
|
||||
shouldKeep = answer ? isPrimeNum(num) : !isPrimeNum(num);
|
||||
baseCondition = isPrimeNum(num);
|
||||
break;
|
||||
case 'perfectSquare':
|
||||
const isPerfectSquareNum = Math.sqrt(num) % 1 === 0;
|
||||
shouldKeep = answer ? isPerfectSquareNum : !isPerfectSquareNum;
|
||||
baseCondition = Math.sqrt(num) % 1 === 0;
|
||||
break;
|
||||
case 'divisible':
|
||||
if (value !== undefined) {
|
||||
shouldKeep = answer ? (num % value === 0) : (num % value !== 0);
|
||||
baseCondition = (num % value === 0);
|
||||
}
|
||||
break;
|
||||
case 'greaterThan':
|
||||
if (value !== undefined) {
|
||||
shouldKeep = answer ? (num > value) : (num <= value);
|
||||
baseCondition = (num > value);
|
||||
}
|
||||
break;
|
||||
case 'lessThan':
|
||||
if (value !== undefined) {
|
||||
shouldKeep = answer ? (num < value) : (num >= value);
|
||||
baseCondition = (num < value);
|
||||
}
|
||||
break;
|
||||
case 'containsDigit':
|
||||
if (value !== undefined) {
|
||||
shouldKeep = answer ? num.toString().includes(value.toString()) : !num.toString().includes(value.toString());
|
||||
baseCondition = num.toString().includes(value.toString());
|
||||
}
|
||||
break;
|
||||
case 'equals':
|
||||
if (value !== undefined) {
|
||||
shouldKeep = answer ? (num === value) : (num !== value);
|
||||
baseCondition = (num === value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
shouldKeep = true; // Keep all numbers if we don't understand the question type
|
||||
}
|
||||
|
||||
if (questionType !== 'equals' && shouldKeep !== true) {
|
||||
// Apply negation logic if present
|
||||
if (isNegated) {
|
||||
// If the question was negated, we need to flip the base condition
|
||||
// e.g., "Is it NOT divisible by 4?" with answer "No" means it IS divisible by 4
|
||||
shouldKeep = answer ? !baseCondition : baseCondition;
|
||||
} else {
|
||||
// Normal case: answer matches the base condition
|
||||
shouldKeep = answer ? baseCondition : !baseCondition;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldKeep) {
|
||||
newValidNumbers.add(num);
|
||||
}
|
||||
|
|
@ -225,7 +249,7 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
|||
setQuestionFeedback(answer);
|
||||
|
||||
// Update valid numbers based on the question and answer
|
||||
updateValidNumbersFromQuestion(result.questionType, result.answer, result.value);
|
||||
updateValidNumbersFromQuestion(result.questionType, result.answer, result.value, result.isNegated);
|
||||
|
||||
} else {
|
||||
setQuestionHistory([...questionHistory, {
|
||||
|
|
@ -281,7 +305,7 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
|||
<label className="font-semibold">Ask a yes/no question:</label>
|
||||
<div className="flex gap-2">
|
||||
<Textarea
|
||||
placeholder="e.g., 'Is the number even?', 'Is it greater than 25?', 'Is it divisible by 3?'"
|
||||
placeholder="e.g., 'Is the number even?', 'Is it greater than 25?', 'Is it NOT divisible by 3?'"
|
||||
value={question}
|
||||
onChange={(e) => setQuestion(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue