Fix negation parsing in guessing game

Correctly parse negations in user-provided questions to accurately update the search space.
This commit is contained in:
gpt-engineer-app[bot] 2025-08-10 14:15:19 +00:00
parent 62c6acc388
commit 591b73c071

View file

@ -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) => {
@ -380,4 +404,4 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
);
};
export default QuestionGuessingMode;
export default QuestionGuessingMode;