Refine guessing game logic
Update the guessing game to handle user-provided questions and optimize computer-generated questions. The game will now grey out numbers divisible by 10 based on user input. Additionally, the computer will prioritize questions that maximize the remaining search space, ensuring a longer guessing period for the user. The "too high/too low" feedback is removed, relying solely on user questions for deduction.
This commit is contained in:
parent
b0595d1cc7
commit
62c6acc388
2 changed files with 77 additions and 27 deletions
|
|
@ -475,7 +475,6 @@ const GuessingGame: React.FC<GuessingGameProps> = ({ showSocialShare = true }) =
|
||||||
{gameState === 'playing' && gameMode === 'user-guesses' && userGuessingMode === 'questions' && (
|
{gameState === 'playing' && gameMode === 'user-guesses' && userGuessingMode === 'questions' && (
|
||||||
<QuestionGuessingMode
|
<QuestionGuessingMode
|
||||||
maxRange={maxRange}
|
maxRange={maxRange}
|
||||||
secretNumber={secretNumber!}
|
|
||||||
guesses={guesses}
|
guesses={guesses}
|
||||||
feedback={feedback}
|
feedback={feedback}
|
||||||
validNumbers={validNumbers}
|
validNumbers={validNumbers}
|
||||||
|
|
@ -483,6 +482,7 @@ const GuessingGame: React.FC<GuessingGameProps> = ({ showSocialShare = true }) =
|
||||||
setUserGuess={setUserGuess}
|
setUserGuess={setUserGuess}
|
||||||
onGuess={handleGuess}
|
onGuess={handleGuess}
|
||||||
onUpdateValidNumbers={setValidNumbers}
|
onUpdateValidNumbers={setValidNumbers}
|
||||||
|
onWin={() => setGameState('won')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import { HelpCircle, CheckCircle, XCircle } from 'lucide-react';
|
||||||
|
|
||||||
interface QuestionGuessingModeProps {
|
interface QuestionGuessingModeProps {
|
||||||
maxRange: number;
|
maxRange: number;
|
||||||
secretNumber: number;
|
|
||||||
guesses: number[];
|
guesses: number[];
|
||||||
feedback: string;
|
feedback: string;
|
||||||
validNumbers: Set<number>;
|
validNumbers: Set<number>;
|
||||||
|
|
@ -15,25 +14,26 @@ interface QuestionGuessingModeProps {
|
||||||
setUserGuess: (value: string) => void;
|
setUserGuess: (value: string) => void;
|
||||||
onGuess: (guessValue?: number) => void;
|
onGuess: (guessValue?: number) => void;
|
||||||
onUpdateValidNumbers: (newValidNumbers: Set<number>) => void;
|
onUpdateValidNumbers: (newValidNumbers: Set<number>) => void;
|
||||||
|
onWin: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
maxRange,
|
maxRange,
|
||||||
secretNumber,
|
|
||||||
guesses,
|
guesses,
|
||||||
feedback,
|
feedback,
|
||||||
validNumbers,
|
validNumbers,
|
||||||
userGuess,
|
userGuess,
|
||||||
setUserGuess,
|
setUserGuess,
|
||||||
onGuess,
|
onGuess,
|
||||||
onUpdateValidNumbers
|
onUpdateValidNumbers,
|
||||||
|
onWin
|
||||||
}) => {
|
}) => {
|
||||||
const [question, setQuestion] = useState('');
|
const [question, setQuestion] = useState('');
|
||||||
const [questionHistory, setQuestionHistory] = useState<{question: string, answer: string, understood: boolean}[]>([]);
|
const [questionHistory, setQuestionHistory] = useState<{question: string, answer: string, understood: boolean}[]>([]);
|
||||||
const [questionFeedback, setQuestionFeedback] = useState('');
|
const [questionFeedback, setQuestionFeedback] = useState('');
|
||||||
|
|
||||||
// Function to interpret and answer yes/no questions without revealing the secret number
|
// Function to determine the optimal answer to keep search space as large as possible
|
||||||
const interpretQuestion = (question: string, secretNumber: number): { understood: boolean, answer: boolean, questionType?: string, value?: number } => {
|
const getOptimalAnswer = (question: string): { understood: boolean, answer: boolean, questionType?: string, value?: number } => {
|
||||||
const q = question.toLowerCase().trim();
|
const q = question.toLowerCase().trim();
|
||||||
|
|
||||||
// Remove question marks and normalize
|
// Remove question marks and normalize
|
||||||
|
|
@ -43,10 +43,18 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
if (normalizedQ.includes('is') && normalizedQ.includes('number')) {
|
if (normalizedQ.includes('is') && normalizedQ.includes('number')) {
|
||||||
// Is the number...
|
// Is the number...
|
||||||
if (normalizedQ.includes('even')) {
|
if (normalizedQ.includes('even')) {
|
||||||
return { understood: true, answer: secretNumber % 2 === 0, questionType: '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' };
|
||||||
}
|
}
|
||||||
if (normalizedQ.includes('odd')) {
|
if (normalizedQ.includes('odd')) {
|
||||||
return { understood: true, answer: secretNumber % 2 !== 0, questionType: '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' };
|
||||||
}
|
}
|
||||||
if (normalizedQ.includes('prime')) {
|
if (normalizedQ.includes('prime')) {
|
||||||
const isPrime = (num: number): boolean => {
|
const isPrime = (num: number): boolean => {
|
||||||
|
|
@ -56,12 +64,16 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
const prime = isPrime(secretNumber);
|
const primeCount = Array.from(validNumbers).filter(n => isPrime(n)).length;
|
||||||
return { understood: true, answer: prime, questionType: 'prime' };
|
const nonPrimeCount = validNumbers.size - primeCount;
|
||||||
|
const answer = primeCount > nonPrimeCount;
|
||||||
|
return { understood: true, answer, questionType: 'prime' };
|
||||||
}
|
}
|
||||||
if (normalizedQ.includes('perfect square')) {
|
if (normalizedQ.includes('perfect square')) {
|
||||||
const isPerfectSquare = Math.sqrt(secretNumber) % 1 === 0;
|
const perfectSquareCount = Array.from(validNumbers).filter(n => Math.sqrt(n) % 1 === 0).length;
|
||||||
return { understood: true, answer: isPerfectSquare, questionType: 'perfectSquare' };
|
const nonPerfectSquareCount = validNumbers.size - perfectSquareCount;
|
||||||
|
const answer = perfectSquareCount > nonPerfectSquareCount;
|
||||||
|
return { understood: true, answer, questionType: 'perfectSquare' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,8 +82,10 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
const matches = normalizedQ.match(/(?:divisible by|multiple of)\s+(\d+)/);
|
const matches = normalizedQ.match(/(?:divisible by|multiple of)\s+(\d+)/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
const divisor = parseInt(matches[1]);
|
const divisor = parseInt(matches[1]);
|
||||||
const isDivisible = secretNumber % divisor === 0;
|
const divisibleCount = Array.from(validNumbers).filter(n => n % divisor === 0).length;
|
||||||
return { understood: true, answer: isDivisible, questionType: 'divisible', value: divisor };
|
const nonDivisibleCount = validNumbers.size - divisibleCount;
|
||||||
|
const answer = divisibleCount > nonDivisibleCount;
|
||||||
|
return { understood: true, answer, questionType: 'divisible', value: divisor };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,8 +94,10 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
const matches = normalizedQ.match(/(?:greater than|bigger than|larger than)\s+(\d+)/);
|
const matches = normalizedQ.match(/(?:greater than|bigger than|larger than)\s+(\d+)/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
const threshold = parseInt(matches[1]);
|
const threshold = parseInt(matches[1]);
|
||||||
const isGreater = secretNumber > threshold;
|
const greaterCount = Array.from(validNumbers).filter(n => n > threshold).length;
|
||||||
return { understood: true, answer: isGreater, questionType: 'greaterThan', value: threshold };
|
const lessEqualCount = validNumbers.size - greaterCount;
|
||||||
|
const answer = greaterCount > lessEqualCount;
|
||||||
|
return { understood: true, answer, questionType: 'greaterThan', value: threshold };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,8 +105,10 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
const matches = normalizedQ.match(/(?:less than|smaller than)\s+(\d+)/);
|
const matches = normalizedQ.match(/(?:less than|smaller than)\s+(\d+)/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
const threshold = parseInt(matches[1]);
|
const threshold = parseInt(matches[1]);
|
||||||
const isLess = secretNumber < threshold;
|
const lessCount = Array.from(validNumbers).filter(n => n < threshold).length;
|
||||||
return { understood: true, answer: isLess, questionType: 'lessThan', value: threshold };
|
const greaterEqualCount = validNumbers.size - lessCount;
|
||||||
|
const answer = lessCount > greaterEqualCount;
|
||||||
|
return { understood: true, answer, questionType: 'lessThan', value: threshold };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,8 +117,10 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
const matches = normalizedQ.match(/contain(?:s)?\s+(?:the\s+)?digit\s+(\d)/);
|
const matches = normalizedQ.match(/contain(?:s)?\s+(?:the\s+)?digit\s+(\d)/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
const digit = matches[1];
|
const digit = matches[1];
|
||||||
const contains = secretNumber.toString().includes(digit);
|
const containsCount = Array.from(validNumbers).filter(n => n.toString().includes(digit)).length;
|
||||||
return { understood: true, answer: contains, questionType: 'containsDigit', value: parseInt(digit) };
|
const doesntContainCount = validNumbers.size - containsCount;
|
||||||
|
const answer = containsCount > doesntContainCount;
|
||||||
|
return { understood: true, answer, questionType: 'containsDigit', value: parseInt(digit) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,8 +129,14 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
const matches = normalizedQ.match(/(?:is it|is the number)\s+(\d+)/);
|
const matches = normalizedQ.match(/(?:is it|is the number)\s+(\d+)/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
const num = parseInt(matches[1]);
|
const num = parseInt(matches[1]);
|
||||||
const isEqual = secretNumber === num;
|
// If there's only one number left and they're asking about it, we must answer truthfully
|
||||||
return { understood: true, answer: isEqual, questionType: 'equals', value: num };
|
if (validNumbers.size === 1) {
|
||||||
|
const answer = validNumbers.has(num);
|
||||||
|
return { understood: true, answer, questionType: 'equals', value: num };
|
||||||
|
}
|
||||||
|
// 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 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,7 +213,7 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
const handleQuestionSubmit = () => {
|
const handleQuestionSubmit = () => {
|
||||||
if (!question.trim()) return;
|
if (!question.trim()) return;
|
||||||
|
|
||||||
const result = interpretQuestion(question, secretNumber);
|
const result = getOptimalAnswer(question);
|
||||||
|
|
||||||
if (result.understood && result.questionType) {
|
if (result.understood && result.questionType) {
|
||||||
const answer = result.answer ? 'Yes' : 'No';
|
const answer = result.answer ? 'Yes' : 'No';
|
||||||
|
|
@ -213,6 +239,30 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
setQuestion('');
|
setQuestion('');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle final number guess - just check if it's the only remaining valid number
|
||||||
|
const handleFinalGuess = (guessValue?: number) => {
|
||||||
|
const guess = guessValue || parseInt(userGuess);
|
||||||
|
if (isNaN(guess) || guess < 1 || guess > maxRange) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validNumbers.size === 1 && validNumbers.has(guess)) {
|
||||||
|
onWin();
|
||||||
|
} else if (validNumbers.has(guess)) {
|
||||||
|
// Remove this number from valid numbers since it's not the answer
|
||||||
|
const newValidNumbers = new Set(validNumbers);
|
||||||
|
newValidNumbers.delete(guess);
|
||||||
|
onUpdateValidNumbers(newValidNumbers);
|
||||||
|
|
||||||
|
// Check if we're down to one number
|
||||||
|
if (newValidNumbers.size === 1) {
|
||||||
|
onWin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserGuess('');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="text-center space-y-2">
|
<div className="text-center space-y-2">
|
||||||
|
|
@ -285,7 +335,7 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
|
|
||||||
{/* Number Grid Display */}
|
{/* Number Grid Display */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h4 className="font-semibold text-center">Click a number to make your final guess:</h4>
|
<h4 className="font-semibold text-center">Click a number to make your final guess ({validNumbers.size} possibilities remaining):</h4>
|
||||||
<div className="grid grid-cols-8 sm:grid-cols-10 md:grid-cols-12 lg:grid-cols-16 gap-1 max-h-64 overflow-y-auto p-2 border rounded-lg">
|
<div className="grid grid-cols-8 sm:grid-cols-10 md:grid-cols-12 lg:grid-cols-16 gap-1 max-h-64 overflow-y-auto p-2 border rounded-lg">
|
||||||
{Array.from({ length: maxRange }, (_, i) => i + 1).map(num => {
|
{Array.from({ length: maxRange }, (_, i) => i + 1).map(num => {
|
||||||
const isValid = validNumbers.has(num);
|
const isValid = validNumbers.has(num);
|
||||||
|
|
@ -302,7 +352,7 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
${isGuessed ? 'bg-accent text-accent-foreground' : ''}
|
${isGuessed ? 'bg-accent text-accent-foreground' : ''}
|
||||||
${isValid && !isGuessed ? 'hover:bg-accent/10' : ''}
|
${isValid && !isGuessed ? 'hover:bg-accent/10' : ''}
|
||||||
`}
|
`}
|
||||||
onClick={() => isValid && !isGuessed && onGuess(num)}
|
onClick={() => isValid && !isGuessed && handleFinalGuess(num)}
|
||||||
disabled={!isValid || isGuessed}
|
disabled={!isValid || isGuessed}
|
||||||
>
|
>
|
||||||
{num}
|
{num}
|
||||||
|
|
@ -318,11 +368,11 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
placeholder="Or type your final guess"
|
placeholder="Or type your final guess"
|
||||||
value={userGuess}
|
value={userGuess}
|
||||||
onChange={(e) => setUserGuess(e.target.value)}
|
onChange={(e) => setUserGuess(e.target.value)}
|
||||||
onKeyDown={(e) => e.key === 'Enter' && onGuess()}
|
onKeyDown={(e) => e.key === 'Enter' && handleFinalGuess()}
|
||||||
min={1}
|
min={1}
|
||||||
max={maxRange}
|
max={maxRange}
|
||||||
/>
|
/>
|
||||||
<Button onClick={() => onGuess()} disabled={!userGuess}>
|
<Button onClick={() => handleFinalGuess()} disabled={!userGuess}>
|
||||||
Guess
|
Guess
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue