From a981f2ec877ee342595ff236678f73eb42d38d13 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 10 Aug 2025 13:38:45 +0000 Subject: [PATCH] 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. --- src/App.tsx | 2 + src/components/GamesGallery.tsx | 8 + src/components/GuessingGame.tsx | 536 ++++++++++++++++++++++++++++++++ src/pages/GuessingGamePage.tsx | 13 + 4 files changed, 559 insertions(+) create mode 100644 src/components/GuessingGame.tsx create mode 100644 src/pages/GuessingGamePage.tsx diff --git a/src/App.tsx b/src/App.tsx index fadaae4..8e2483e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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 = () => ( } /> } /> } /> + } /> {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} } /> diff --git a/src/components/GamesGallery.tsx b/src/components/GamesGallery.tsx index 0f28046..d148fb4 100644 --- a/src/components/GamesGallery.tsx +++ b/src/components/GamesGallery.tsx @@ -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', diff --git a/src/components/GuessingGame.tsx b/src/components/GuessingGame.tsx new file mode 100644 index 0000000..c657875 --- /dev/null +++ b/src/components/GuessingGame.tsx @@ -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 = ({ showSocialShare = true }) => { + // Game settings + const [range, setRange] = useState([50]); + const [gameMode, setGameMode] = useState('user-guesses'); + const [computerStrategy, setComputerStrategy] = useState('binary'); + + // Game state + const [gameState, setGameState] = useState('setup'); + const [secretNumber, setSecretNumber] = useState(null); + const [userGuess, setUserGuess] = useState(''); + const [guesses, setGuesses] = useState([]); + const [feedback, setFeedback] = useState(''); + + // Computer asking mode + const [possibleNumbers, setPossibleNumbers] = useState([]); + const [currentQuestion, setCurrentQuestion] = useState(null); + const [questionsAsked, setQuestionsAsked] = useState([]); + 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 ( +
+ + + + + Number Guessing Game + + + Experience different search strategies in this interactive guessing game + + + + + {gameState === 'setup' && ( +
+ {/* Range Selection */} +
+ +
+ +
+ Min: 10 + Current: {maxRange} + Max: 512 +
+
+
+ + {/* Game Mode Selection */} +
+ +
+ setGameMode('user-guesses')} + > + + +

You Guess

+

+ Computer thinks of a number, you try to guess it +

+ + Optimal: {getOptimalGuesses()} guesses + +
+
+ + setGameMode('computer-asks')} + > + + +

Computer Asks

+

+ You think of a number, computer asks questions +

+ + Earn points for each question + +
+
+
+
+ + {/* Computer Strategy Selection */} + {gameMode === 'computer-asks' && ( +
+ +
+ {(['random', 'linear', 'binary'] as ComputerStrategy[]).map(strategy => ( + setComputerStrategy(strategy)} + > + +
+
+

{strategy} Search

+

+ {getStrategyDescription(strategy)} +

+
+ + {strategy === 'binary' ? `~${getOptimalGuesses()} questions` : + strategy === 'linear' ? `~${Math.ceil(maxRange/2)} questions` : + '? questions'} + +
+
+
+ ))} +
+
+ )} + + +
+ )} + + {gameState === 'playing' && gameMode === 'user-guesses' && ( +
+
+

Guess the number between 1 and {maxRange}

+

Guesses made: {guesses.length}

+ {feedback && ( +
+ {feedback} +
+ )} +
+ +
+ setUserGuess(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleGuess()} + min={1} + max={maxRange} + /> + +
+ + {guesses.length > 0 && ( +
+

Previous Guesses:

+
+ {guesses.map((guess, index) => ( + + {guess} + + ))} +
+
+ )} + + +
+ )} + + {gameState === 'playing' && gameMode === 'computer-asks' && ( +
+
+

Think of a number between 1 and {maxRange}

+

+ Strategy: {computerStrategy.charAt(0).toUpperCase() + computerStrategy.slice(1)} Search +

+
+ + + Score: {userScore} + + + Remaining: {possibleNumbers.length} + +
+
+ + {currentQuestion && ( + + + +

{currentQuestion.text}

+
+ + +
+
+
+ )} + + {questionsAsked.length > 0 && ( +
+

Questions Asked:

+
+ {questionsAsked.map((question, index) => ( +
+ {question} +
+ ))} +
+
+ )} + + +
+ )} + + {gameState === 'won' && ( +
+
+ +

Game Complete!

+ {gameMode === 'user-guesses' ? ( +

+ You found the number {secretNumber} in{' '} + {guesses.length} guesses! +

+ ) : ( +
+

+ The computer identified your number using the{' '} + {computerStrategy} search strategy! +

+
+ + + Final Score: {userScore} points + +
+ {possibleNumbers.length === 1 && ( +

+ Your number was: {possibleNumbers[0]} +

+ )} +
+ )} +
+ + +
+ )} +
+
+ + {showSocialShare && ( + + )} +
+ ); +}; + +export default GuessingGame; \ No newline at end of file diff --git a/src/pages/GuessingGamePage.tsx b/src/pages/GuessingGamePage.tsx new file mode 100644 index 0000000..75edef6 --- /dev/null +++ b/src/pages/GuessingGamePage.tsx @@ -0,0 +1,13 @@ +import GuessingGame from '@/components/GuessingGame'; + +const GuessingGamePage = () => { + return ( +
+
+ +
+
+ ); +}; + +export default GuessingGamePage; \ No newline at end of file