Add Guessing Game

Implement a guessing game with user-selectable range via slider. Includes two modes: user guesses computer's number, and computer guesses user's number using random, linear, or binary search strategies.
This commit is contained in:
gpt-engineer-app[bot] 2025-08-10 13:38:45 +00:00
parent aaa3b7f06f
commit a981f2ec87
4 changed files with 559 additions and 0 deletions

View file

@ -36,6 +36,7 @@ import PlateSwapPuzzlePage from "./pages/PlateSwapPuzzlePage";
import ChessboardRepaintPuzzlePage from "./pages/ChessboardRepaintPuzzlePage";
import ZeckendorfGamePage from "./pages/ZeckendorfGamePage";
import ZeckendorfSearchTrickPage from "./pages/ZeckendorfSearchTrickPage";
import GuessingGamePage from "./pages/GuessingGamePage";
import Foundations from "./pages/theme-pages/discrete-math/Foundations";
import Proofs from "./pages/theme-pages/discrete-math/Proofs";
import Counting from "./pages/theme-pages/discrete-math/Counting";
@ -106,6 +107,7 @@ const App = () => (
<Route path="/puzzles/n-queens" element={<NQueensPuzzlePage />} />
<Route path="/discrete-math/foundations/zeckendorf" element={<ZeckendorfGamePage />} />
<Route path="/discrete-math/foundations/zeckendorf-search" element={<ZeckendorfSearchTrickPage />} />
<Route path="/guessing-game" element={<GuessingGamePage />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} />

View file

@ -9,6 +9,7 @@ import NorthcottsGame from '@/components/NorthcottsGame';
import NimGame from '@/components/NimGame';
import AssistedNimGame from '@/components/AssistedNimGame';
import GoldCoinGame from '@/components/GoldCoinGame';
import GuessingGame from '@/components/GuessingGame';
interface Game {
id: string;
@ -19,6 +20,13 @@ interface Game {
}
const games: Game[] = [
{
id: 'guessing-game',
title: 'Number Guessing Game',
description: 'Experience different search algorithms through interactive gameplay. Compare random, linear, and binary search strategies!',
tags: ['algorithms', 'search', 'educational', 'interactive', 'binary-search', 'computer-science'],
component: GuessingGame
},
{
id: 'gold-coin',
title: 'The Gold Coin Game',

View file

@ -0,0 +1,536 @@
import React, { useState, useEffect, useCallback } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Slider } from '@/components/ui/slider';
import { Badge } from '@/components/ui/badge';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { Brain, User, Trophy, Target, HelpCircle, CheckCircle, XCircle } from 'lucide-react';
import SocialShare from '@/components/SocialShare';
interface GuessingGameProps {
showSocialShare?: boolean;
}
type GameMode = 'user-guesses' | 'computer-asks';
type ComputerStrategy = 'random' | 'linear' | 'binary';
type GameState = 'setup' | 'playing' | 'won' | 'lost';
interface Question {
text: string;
isCorrect: (num: number) => boolean;
}
const GuessingGame: React.FC<GuessingGameProps> = ({ showSocialShare = true }) => {
// Game settings
const [range, setRange] = useState([50]);
const [gameMode, setGameMode] = useState<GameMode>('user-guesses');
const [computerStrategy, setComputerStrategy] = useState<ComputerStrategy>('binary');
// Game state
const [gameState, setGameState] = useState<GameState>('setup');
const [secretNumber, setSecretNumber] = useState<number | null>(null);
const [userGuess, setUserGuess] = useState('');
const [guesses, setGuesses] = useState<number[]>([]);
const [feedback, setFeedback] = useState<string>('');
// Computer asking mode
const [possibleNumbers, setPossibleNumbers] = useState<number[]>([]);
const [currentQuestion, setCurrentQuestion] = useState<Question | null>(null);
const [questionsAsked, setQuestionsAsked] = useState<string[]>([]);
const [userScore, setUserScore] = useState(0);
const [currentSearchIndex, setCurrentSearchIndex] = useState(0);
const maxRange = range[0];
// Prime checker utility
const isPrime = (num: number): boolean => {
if (num < 2) return false;
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) return false;
}
return true;
};
// Random question generator
const generateRandomQuestion = useCallback((): Question => {
const questions = [
{
text: "Is your number prime?",
isCorrect: (num: number) => isPrime(num)
},
{
text: "Is your number even?",
isCorrect: (num: number) => num % 2 === 0
},
{
text: "Is your number divisible by 3?",
isCorrect: (num: number) => num % 3 === 0
},
{
text: "Is your number divisible by 5?",
isCorrect: (num: number) => num % 5 === 0
},
{
text: "Is your number a perfect square?",
isCorrect: (num: number) => Math.sqrt(num) % 1 === 0
},
{
text: "Is your number greater than half the maximum range?",
isCorrect: (num: number) => num > maxRange / 2
},
{
text: "Does your number contain the digit 5?",
isCorrect: (num: number) => num.toString().includes('5')
}
];
return questions[Math.floor(Math.random() * questions.length)];
}, [maxRange]);
// Linear search question generator
const generateLinearQuestion = useCallback((): Question => {
const currentNum = currentSearchIndex + 1;
return {
text: `Is your number ${currentNum}?`,
isCorrect: (num: number) => num === currentNum
};
}, [currentSearchIndex]);
// Binary search question generator
const generateBinaryQuestion = useCallback((): Question => {
if (possibleNumbers.length <= 1) {
return {
text: `Is your number ${possibleNumbers[0]}?`,
isCorrect: (num: number) => num === possibleNumbers[0]
};
}
const midIndex = Math.floor(possibleNumbers.length / 2);
const midValue = possibleNumbers[midIndex];
return {
text: `Is your number ${midValue} or less?`,
isCorrect: (num: number) => num <= midValue
};
}, [possibleNumbers]);
// Start new game
const startGame = () => {
if (gameMode === 'user-guesses') {
const secret = Math.floor(Math.random() * maxRange) + 1;
setSecretNumber(secret);
setGuesses([]);
setFeedback('');
} else {
const numbers = Array.from({ length: maxRange }, (_, i) => i + 1);
setPossibleNumbers(numbers);
setQuestionsAsked([]);
setUserScore(0);
setCurrentSearchIndex(0);
setSecretNumber(null);
}
setGameState('playing');
setUserGuess('');
};
// Generate next question for computer
const generateNextQuestion = useCallback(() => {
if (possibleNumbers.length <= 1) {
setGameState('won');
return;
}
let question: Question;
switch (computerStrategy) {
case 'random':
question = generateRandomQuestion();
break;
case 'linear':
question = generateLinearQuestion();
break;
case 'binary':
question = generateBinaryQuestion();
break;
}
setCurrentQuestion(question);
}, [computerStrategy, possibleNumbers, generateRandomQuestion, generateLinearQuestion, generateBinaryQuestion]);
// Handle user's guess
const handleGuess = () => {
const guess = parseInt(userGuess);
if (isNaN(guess) || guess < 1 || guess > maxRange) {
setFeedback('Please enter a valid number within the range.');
return;
}
const newGuesses = [...guesses, guess];
setGuesses(newGuesses);
if (guess === secretNumber) {
setGameState('won');
setFeedback(`Congratulations! You found the number in ${newGuesses.length} guesses!`);
} else if (guess < secretNumber!) {
setFeedback('Too low! Try a higher number.');
} else {
setFeedback('Too high! Try a lower number.');
}
setUserGuess('');
};
// Handle computer's question answer
const handleQuestionAnswer = (answer: boolean) => {
if (!currentQuestion) return;
const questionText = `${currentQuestion.text}${answer ? 'Yes' : 'No'}`;
setQuestionsAsked([...questionsAsked, questionText]);
setUserScore(userScore + 1);
let newPossibleNumbers = [...possibleNumbers];
if (computerStrategy === 'random') {
// Filter based on the answer to the random question
newPossibleNumbers = possibleNumbers.filter(num =>
currentQuestion.isCorrect(num) === answer
);
} else if (computerStrategy === 'linear') {
if (answer) {
// Found the number
setGameState('won');
return;
} else {
// Move to next number in linear search
setCurrentSearchIndex(currentSearchIndex + 1);
newPossibleNumbers = possibleNumbers.filter(num => num > currentSearchIndex + 1);
}
} else if (computerStrategy === 'binary') {
const midIndex = Math.floor(possibleNumbers.length / 2);
const midValue = possibleNumbers[midIndex];
if (possibleNumbers.length === 1) {
setGameState('won');
return;
}
if (answer) {
// Number is <= midValue
newPossibleNumbers = possibleNumbers.slice(0, midIndex + 1);
} else {
// Number is > midValue
newPossibleNumbers = possibleNumbers.slice(midIndex + 1);
}
}
setPossibleNumbers(newPossibleNumbers);
};
// Generate question when computer is asking
useEffect(() => {
if (gameState === 'playing' && gameMode === 'computer-asks') {
generateNextQuestion();
}
}, [gameState, gameMode, possibleNumbers, generateNextQuestion]);
const resetGame = () => {
setGameState('setup');
setSecretNumber(null);
setUserGuess('');
setGuesses([]);
setFeedback('');
setPossibleNumbers([]);
setCurrentQuestion(null);
setQuestionsAsked([]);
setUserScore(0);
setCurrentSearchIndex(0);
};
const getStrategyDescription = (strategy: ComputerStrategy) => {
switch (strategy) {
case 'random':
return 'Asks random mathematical questions about your number';
case 'linear':
return 'Checks each number sequentially from 1 upward';
case 'binary':
return 'Uses binary search to efficiently narrow down the range';
}
};
const getOptimalGuesses = () => {
return Math.ceil(Math.log2(maxRange));
};
return (
<div className="space-y-6">
<Card>
<CardHeader className="text-center">
<CardTitle className="text-3xl font-bold flex items-center justify-center gap-2">
<Target className="w-8 h-8 text-accent" />
Number Guessing Game
</CardTitle>
<CardDescription className="text-lg">
Experience different search strategies in this interactive guessing game
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{gameState === 'setup' && (
<div className="space-y-6">
{/* Range Selection */}
<div className="space-y-4">
<Label className="text-lg font-semibold">Number Range: 1 to {maxRange}</Label>
<div className="space-y-2">
<Slider
value={range}
onValueChange={setRange}
min={10}
max={512}
step={1}
className="w-full"
/>
<div className="flex justify-between text-sm text-muted-foreground">
<span>Min: 10</span>
<span>Current: {maxRange}</span>
<span>Max: 512</span>
</div>
</div>
</div>
{/* Game Mode Selection */}
<div className="space-y-4">
<Label className="text-lg font-semibold">Game Mode</Label>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card
className={`cursor-pointer transition-all ${gameMode === 'user-guesses' ? 'ring-2 ring-accent' : ''}`}
onClick={() => setGameMode('user-guesses')}
>
<CardContent className="p-4 text-center space-y-2">
<User className="w-8 h-8 text-accent mx-auto" />
<h3 className="font-semibold">You Guess</h3>
<p className="text-sm text-muted-foreground">
Computer thinks of a number, you try to guess it
</p>
<Badge variant="secondary" className="text-xs">
Optimal: {getOptimalGuesses()} guesses
</Badge>
</CardContent>
</Card>
<Card
className={`cursor-pointer transition-all ${gameMode === 'computer-asks' ? 'ring-2 ring-accent' : ''}`}
onClick={() => setGameMode('computer-asks')}
>
<CardContent className="p-4 text-center space-y-2">
<Brain className="w-8 h-8 text-accent mx-auto" />
<h3 className="font-semibold">Computer Asks</h3>
<p className="text-sm text-muted-foreground">
You think of a number, computer asks questions
</p>
<Badge variant="secondary" className="text-xs">
Earn points for each question
</Badge>
</CardContent>
</Card>
</div>
</div>
{/* Computer Strategy Selection */}
{gameMode === 'computer-asks' && (
<div className="space-y-4">
<Label className="text-lg font-semibold">Computer Strategy</Label>
<div className="grid grid-cols-1 gap-3">
{(['random', 'linear', 'binary'] as ComputerStrategy[]).map(strategy => (
<Card
key={strategy}
className={`cursor-pointer transition-all ${computerStrategy === strategy ? 'ring-2 ring-accent' : ''}`}
onClick={() => setComputerStrategy(strategy)}
>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<h4 className="font-semibold capitalize">{strategy} Search</h4>
<p className="text-sm text-muted-foreground">
{getStrategyDescription(strategy)}
</p>
</div>
<Badge variant={computerStrategy === strategy ? "default" : "outline"}>
{strategy === 'binary' ? `~${getOptimalGuesses()} questions` :
strategy === 'linear' ? `~${Math.ceil(maxRange/2)} questions` :
'? questions'}
</Badge>
</div>
</CardContent>
</Card>
))}
</div>
</div>
)}
<Button onClick={startGame} className="w-full" size="lg">
Start Game
</Button>
</div>
)}
{gameState === 'playing' && gameMode === 'user-guesses' && (
<div className="space-y-6">
<div className="text-center space-y-2">
<h3 className="text-xl font-semibold">Guess the number between 1 and {maxRange}</h3>
<p className="text-muted-foreground">Guesses made: {guesses.length}</p>
{feedback && (
<div className={`p-3 rounded-lg ${feedback.includes('Congratulations') ? 'bg-success/10 text-success' : 'bg-muted'}`}>
{feedback}
</div>
)}
</div>
<div className="flex gap-2">
<Input
type="number"
placeholder="Enter your guess"
value={userGuess}
onChange={(e) => setUserGuess(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleGuess()}
min={1}
max={maxRange}
/>
<Button onClick={handleGuess} disabled={!userGuess}>
Guess
</Button>
</div>
{guesses.length > 0 && (
<div className="space-y-2">
<h4 className="font-semibold">Previous Guesses:</h4>
<div className="flex flex-wrap gap-2">
{guesses.map((guess, index) => (
<Badge key={index} variant="outline">
{guess}
</Badge>
))}
</div>
</div>
)}
<Button onClick={resetGame} variant="outline" className="w-full">
New Game
</Button>
</div>
)}
{gameState === 'playing' && gameMode === 'computer-asks' && (
<div className="space-y-6">
<div className="text-center space-y-2">
<h3 className="text-xl font-semibold">Think of a number between 1 and {maxRange}</h3>
<p className="text-muted-foreground">
Strategy: {computerStrategy.charAt(0).toUpperCase() + computerStrategy.slice(1)} Search
</p>
<div className="flex justify-center gap-4">
<Badge variant="outline">
<Trophy className="w-4 h-4 mr-1" />
Score: {userScore}
</Badge>
<Badge variant="outline">
Remaining: {possibleNumbers.length}
</Badge>
</div>
</div>
{currentQuestion && (
<Card className="text-center">
<CardContent className="p-6 space-y-4">
<HelpCircle className="w-12 h-12 text-accent mx-auto" />
<h4 className="text-lg font-semibold">{currentQuestion.text}</h4>
<div className="flex gap-3 justify-center">
<Button
onClick={() => handleQuestionAnswer(true)}
className="bg-success hover:bg-success/90"
>
<CheckCircle className="w-4 h-4 mr-1" />
Yes
</Button>
<Button
onClick={() => handleQuestionAnswer(false)}
variant="destructive"
>
<XCircle className="w-4 h-4 mr-1" />
No
</Button>
</div>
</CardContent>
</Card>
)}
{questionsAsked.length > 0 && (
<div className="space-y-2">
<h4 className="font-semibold">Questions Asked:</h4>
<div className="space-y-1 max-h-32 overflow-y-auto">
{questionsAsked.map((question, index) => (
<div key={index} className="text-sm p-2 bg-muted rounded">
{question}
</div>
))}
</div>
</div>
)}
<Button onClick={resetGame} variant="outline" className="w-full">
New Game
</Button>
</div>
)}
{gameState === 'won' && (
<div className="text-center space-y-4">
<div className="space-y-2">
<CheckCircle className="w-16 h-16 text-success mx-auto" />
<h3 className="text-2xl font-bold text-success">Game Complete!</h3>
{gameMode === 'user-guesses' ? (
<p className="text-lg">
You found the number <span className="font-bold">{secretNumber}</span> in{' '}
<span className="font-bold">{guesses.length}</span> guesses!
</p>
) : (
<div className="space-y-2">
<p className="text-lg">
The computer identified your number using the{' '}
<span className="font-bold">{computerStrategy}</span> search strategy!
</p>
<div className="flex justify-center gap-4">
<Badge variant="outline" className="text-lg p-2">
<Trophy className="w-5 h-5 mr-2" />
Final Score: {userScore} points
</Badge>
</div>
{possibleNumbers.length === 1 && (
<p className="text-muted-foreground">
Your number was: <span className="font-bold">{possibleNumbers[0]}</span>
</p>
)}
</div>
)}
</div>
<Button onClick={resetGame} size="lg" className="w-full">
Play Again
</Button>
</div>
)}
</CardContent>
</Card>
{showSocialShare && (
<SocialShare
title="Number Guessing Game - Learn Search Algorithms"
description="Experience different search strategies through this interactive guessing game. Compare random, linear, and binary search approaches!"
/>
)}
</div>
);
};
export default GuessingGame;

View file

@ -0,0 +1,13 @@
import GuessingGame from '@/components/GuessingGame';
const GuessingGamePage = () => {
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<GuessingGame showSocialShare={true} />
</div>
</div>
);
};
export default GuessingGamePage;