Add more guessing game modes
Add two new modes to the guessing game: one where the user guesses the number using arbitrary yes/no questions, and another where the computer guesses the number using random, linear, or binary search.
This commit is contained in:
parent
9e899131fd
commit
ec168be51d
3 changed files with 410 additions and 72 deletions
|
|
@ -8,12 +8,15 @@ import { Switch } from '@/components/ui/switch';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Brain, User, Trophy, Target, HelpCircle, CheckCircle, XCircle } from 'lucide-react';
|
import { Brain, User, Trophy, Target, HelpCircle, CheckCircle, XCircle } from 'lucide-react';
|
||||||
import SocialShare from '@/components/SocialShare';
|
import SocialShare from '@/components/SocialShare';
|
||||||
|
import StandardGuessingMode from '@/components/guessing-game/StandardGuessingMode';
|
||||||
|
import QuestionGuessingMode from '@/components/guessing-game/QuestionGuessingMode';
|
||||||
|
|
||||||
interface GuessingGameProps {
|
interface GuessingGameProps {
|
||||||
showSocialShare?: boolean;
|
showSocialShare?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type GameMode = 'user-guesses' | 'computer-asks';
|
type GameMode = 'user-guesses' | 'computer-asks';
|
||||||
|
type UserGuessingMode = 'standard' | 'questions';
|
||||||
type ComputerStrategy = 'random' | 'linear' | 'binary';
|
type ComputerStrategy = 'random' | 'linear' | 'binary';
|
||||||
type GameState = 'setup' | 'playing' | 'won' | 'lost';
|
type GameState = 'setup' | 'playing' | 'won' | 'lost';
|
||||||
|
|
||||||
|
|
@ -26,6 +29,7 @@ const GuessingGame: React.FC<GuessingGameProps> = ({ showSocialShare = true }) =
|
||||||
// Game settings
|
// Game settings
|
||||||
const [range, setRange] = useState([50]);
|
const [range, setRange] = useState([50]);
|
||||||
const [gameMode, setGameMode] = useState<GameMode>('user-guesses');
|
const [gameMode, setGameMode] = useState<GameMode>('user-guesses');
|
||||||
|
const [userGuessingMode, setUserGuessingMode] = useState<UserGuessingMode>('standard');
|
||||||
const [computerStrategy, setComputerStrategy] = useState<ComputerStrategy>('binary');
|
const [computerStrategy, setComputerStrategy] = useState<ComputerStrategy>('binary');
|
||||||
|
|
||||||
// Game state
|
// Game state
|
||||||
|
|
@ -383,6 +387,40 @@ const GuessingGame: React.FC<GuessingGameProps> = ({ showSocialShare = true }) =
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* User Guessing Mode Selection */}
|
||||||
|
{gameMode === 'user-guesses' && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Label className="text-lg font-semibold">Guessing Style</Label>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<Card
|
||||||
|
className={`cursor-pointer transition-all ${userGuessingMode === 'standard' ? 'ring-2 ring-accent' : ''}`}
|
||||||
|
onClick={() => setUserGuessingMode('standard')}
|
||||||
|
>
|
||||||
|
<CardContent className="p-4 text-center space-y-2">
|
||||||
|
<Target className="w-6 h-6 text-accent mx-auto" />
|
||||||
|
<h4 className="font-semibold">Standard (High/Low)</h4>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Traditional guessing with "too high" or "too low" feedback
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
className={`cursor-pointer transition-all ${userGuessingMode === 'questions' ? 'ring-2 ring-accent' : ''}`}
|
||||||
|
onClick={() => setUserGuessingMode('questions')}
|
||||||
|
>
|
||||||
|
<CardContent className="p-4 text-center space-y-2">
|
||||||
|
<HelpCircle className="w-6 h-6 text-accent mx-auto" />
|
||||||
|
<h4 className="font-semibold">Ask Questions</h4>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Ask yes/no questions about mathematical properties
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Computer Strategy Selection */}
|
{/* Computer Strategy Selection */}
|
||||||
{gameMode === 'computer-asks' && (
|
{gameMode === 'computer-asks' && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|
@ -421,79 +459,30 @@ const GuessingGame: React.FC<GuessingGameProps> = ({ showSocialShare = true }) =
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{gameState === 'playing' && gameMode === 'user-guesses' && (
|
{gameState === 'playing' && gameMode === 'user-guesses' && userGuessingMode === 'standard' && (
|
||||||
<div className="space-y-6">
|
<StandardGuessingMode
|
||||||
<div className="text-center space-y-2">
|
maxRange={maxRange}
|
||||||
<h3 className="text-xl font-semibold">Guess the number between 1 and {maxRange}</h3>
|
secretNumber={secretNumber!}
|
||||||
<p className="text-muted-foreground">Guesses made: {guesses.length}</p>
|
guesses={guesses}
|
||||||
{feedback && (
|
feedback={feedback}
|
||||||
<div className={`p-3 rounded-lg ${feedback.includes('Congratulations') ? 'bg-success/10 text-success' : 'bg-muted'}`}>
|
validNumbers={validNumbers}
|
||||||
{feedback}
|
userGuess={userGuess}
|
||||||
</div>
|
setUserGuess={setUserGuess}
|
||||||
)}
|
onGuess={handleGuess}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Number Grid Display */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h4 className="font-semibold text-center">Click a number or type your guess:</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">
|
|
||||||
{Array.from({ length: maxRange }, (_, i) => i + 1).map(num => {
|
|
||||||
const isValid = validNumbers.has(num);
|
|
||||||
const isGuessed = guesses.includes(num);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
key={num}
|
|
||||||
size="sm"
|
|
||||||
variant={isGuessed ? "default" : isValid ? "outline" : "ghost"}
|
|
||||||
className={`
|
|
||||||
h-8 p-1 text-xs
|
|
||||||
${!isValid && !isGuessed ? 'opacity-30 cursor-not-allowed text-muted-foreground' : ''}
|
|
||||||
${isGuessed ? 'bg-accent text-accent-foreground' : ''}
|
|
||||||
${isValid && !isGuessed ? 'hover:bg-accent/10' : ''}
|
|
||||||
`}
|
|
||||||
onClick={() => isValid && !isGuessed && handleGuess(num)}
|
|
||||||
disabled={!isValid || isGuessed}
|
|
||||||
>
|
|
||||||
{num}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
placeholder="Or type 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">
|
{gameState === 'playing' && gameMode === 'user-guesses' && userGuessingMode === 'questions' && (
|
||||||
New Game
|
<QuestionGuessingMode
|
||||||
</Button>
|
maxRange={maxRange}
|
||||||
</div>
|
secretNumber={secretNumber!}
|
||||||
|
guesses={guesses}
|
||||||
|
feedback={feedback}
|
||||||
|
validNumbers={validNumbers}
|
||||||
|
userGuess={userGuess}
|
||||||
|
setUserGuess={setUserGuess}
|
||||||
|
onGuess={handleGuess}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{gameState === 'playing' && gameMode === 'computer-asks' && (
|
{gameState === 'playing' && gameMode === 'computer-asks' && (
|
||||||
|
|
|
||||||
264
src/components/guessing-game/QuestionGuessingMode.tsx
Normal file
264
src/components/guessing-game/QuestionGuessingMode.tsx
Normal file
|
|
@ -0,0 +1,264 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { HelpCircle, CheckCircle, XCircle } from 'lucide-react';
|
||||||
|
|
||||||
|
interface QuestionGuessingModeProps {
|
||||||
|
maxRange: number;
|
||||||
|
secretNumber: number;
|
||||||
|
guesses: number[];
|
||||||
|
feedback: string;
|
||||||
|
validNumbers: Set<number>;
|
||||||
|
userGuess: string;
|
||||||
|
setUserGuess: (value: string) => void;
|
||||||
|
onGuess: (guessValue?: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
|
||||||
|
maxRange,
|
||||||
|
secretNumber,
|
||||||
|
guesses,
|
||||||
|
feedback,
|
||||||
|
validNumbers,
|
||||||
|
userGuess,
|
||||||
|
setUserGuess,
|
||||||
|
onGuess
|
||||||
|
}) => {
|
||||||
|
const [question, setQuestion] = useState('');
|
||||||
|
const [questionHistory, setQuestionHistory] = useState<{question: string, answer: string, understood: boolean}[]>([]);
|
||||||
|
const [questionFeedback, setQuestionFeedback] = useState('');
|
||||||
|
|
||||||
|
// Function to interpret and answer yes/no questions
|
||||||
|
const interpretQuestion = (question: string, secretNumber: number): { understood: boolean, answer: boolean, explanation?: string } => {
|
||||||
|
const q = question.toLowerCase().trim();
|
||||||
|
|
||||||
|
// Remove question marks and normalize
|
||||||
|
const normalizedQ = q.replace(/\?/g, '').trim();
|
||||||
|
|
||||||
|
// Check for various question patterns
|
||||||
|
if (normalizedQ.includes('is') && normalizedQ.includes('number')) {
|
||||||
|
// Is the number...
|
||||||
|
if (normalizedQ.includes('even')) {
|
||||||
|
return { understood: true, answer: secretNumber % 2 === 0, explanation: `${secretNumber} is ${secretNumber % 2 === 0 ? 'even' : 'odd'}` };
|
||||||
|
}
|
||||||
|
if (normalizedQ.includes('odd')) {
|
||||||
|
return { understood: true, answer: secretNumber % 2 !== 0, explanation: `${secretNumber} is ${secretNumber % 2 !== 0 ? 'odd' : 'even'}` };
|
||||||
|
}
|
||||||
|
if (normalizedQ.includes('prime')) {
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
const prime = isPrime(secretNumber);
|
||||||
|
return { understood: true, answer: prime, explanation: `${secretNumber} is ${prime ? 'prime' : 'not prime'}` };
|
||||||
|
}
|
||||||
|
if (normalizedQ.includes('perfect square')) {
|
||||||
|
const isPerfectSquare = Math.sqrt(secretNumber) % 1 === 0;
|
||||||
|
return { understood: true, answer: isPerfectSquare, explanation: `${secretNumber} is ${isPerfectSquare ? 'a perfect square' : 'not a perfect square'}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for divisibility questions
|
||||||
|
if (normalizedQ.includes('divisible by') || normalizedQ.includes('multiple of')) {
|
||||||
|
const matches = normalizedQ.match(/(?:divisible by|multiple of)\s+(\d+)/);
|
||||||
|
if (matches) {
|
||||||
|
const divisor = parseInt(matches[1]);
|
||||||
|
const isDivisible = secretNumber % divisor === 0;
|
||||||
|
return { understood: true, answer: isDivisible, explanation: `${secretNumber} is ${isDivisible ? '' : 'not '}divisible by ${divisor}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (matches) {
|
||||||
|
const threshold = parseInt(matches[1]);
|
||||||
|
const isGreater = secretNumber > threshold;
|
||||||
|
return { understood: true, answer: isGreater, explanation: `${secretNumber} is ${isGreater ? 'greater than' : 'not greater than'} ${threshold}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedQ.includes('less than') || normalizedQ.includes('smaller than')) {
|
||||||
|
const matches = normalizedQ.match(/(?:less than|smaller than)\s+(\d+)/);
|
||||||
|
if (matches) {
|
||||||
|
const threshold = parseInt(matches[1]);
|
||||||
|
const isLess = secretNumber < threshold;
|
||||||
|
return { understood: true, answer: isLess, explanation: `${secretNumber} is ${isLess ? 'less than' : 'not less than'} ${threshold}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (matches) {
|
||||||
|
const digit = matches[1];
|
||||||
|
const contains = secretNumber.toString().includes(digit);
|
||||||
|
return { understood: true, answer: contains, explanation: `${secretNumber} ${contains ? 'contains' : 'does not contain'} the digit ${digit}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (matches) {
|
||||||
|
const num = parseInt(matches[1]);
|
||||||
|
const isEqual = secretNumber === num;
|
||||||
|
return { understood: true, answer: isEqual, explanation: `The number is ${secretNumber}, ${isEqual ? 'which is' : 'which is not'} ${num}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { understood: false, answer: false };
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuestionSubmit = () => {
|
||||||
|
if (!question.trim()) return;
|
||||||
|
|
||||||
|
const result = interpretQuestion(question, secretNumber);
|
||||||
|
|
||||||
|
if (result.understood) {
|
||||||
|
const answer = result.answer ? 'Yes' : 'No';
|
||||||
|
setQuestionHistory([...questionHistory, {
|
||||||
|
question: question,
|
||||||
|
answer: answer + (result.explanation ? ` (${result.explanation})` : ''),
|
||||||
|
understood: true
|
||||||
|
}]);
|
||||||
|
setQuestionFeedback(`${answer}! ${result.explanation || ''}`);
|
||||||
|
|
||||||
|
// Update valid numbers based on the question and answer
|
||||||
|
// This is simplified - in a full implementation, you'd need to parse and apply the logic
|
||||||
|
|
||||||
|
} else {
|
||||||
|
setQuestionHistory([...questionHistory, {
|
||||||
|
question: question,
|
||||||
|
answer: "I don't understand this question",
|
||||||
|
understood: false
|
||||||
|
}]);
|
||||||
|
setQuestionFeedback("I don't understand that question. Please try rephrasing it as a clear yes/no question about mathematical properties, ranges, or specific values.");
|
||||||
|
}
|
||||||
|
|
||||||
|
setQuestion('');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
<h3 className="text-xl font-semibold">Ask yes/no questions to find the number between 1 and {maxRange}</h3>
|
||||||
|
<p className="text-muted-foreground">Questions asked: {questionHistory.length}</p>
|
||||||
|
{feedback && (
|
||||||
|
<div className={`p-3 rounded-lg ${feedback.includes('Congratulations') ? 'bg-success/10 text-success' : 'bg-muted'}`}>
|
||||||
|
{feedback}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Question Input */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<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?'"
|
||||||
|
value={question}
|
||||||
|
onChange={(e) => setQuestion(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleQuestionSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
rows={2}
|
||||||
|
/>
|
||||||
|
<Button onClick={handleQuestionSubmit} disabled={!question.trim()}>
|
||||||
|
Ask
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{questionFeedback && (
|
||||||
|
<div className="p-3 rounded-lg bg-muted border">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<HelpCircle className="w-4 h-4" />
|
||||||
|
<span className="font-medium">Answer:</span>
|
||||||
|
<span>{questionFeedback}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Question History */}
|
||||||
|
{questionHistory.length > 0 && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h4 className="font-semibold">Question History:</h4>
|
||||||
|
<div className="space-y-2 max-h-48 overflow-y-auto">
|
||||||
|
{questionHistory.map((item, index) => (
|
||||||
|
<div key={index} className="p-3 rounded-lg border bg-card">
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
{item.understood ? (
|
||||||
|
<CheckCircle className="w-4 h-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||||
|
) : (
|
||||||
|
<XCircle className="w-4 h-4 text-red-500 mt-0.5 flex-shrink-0" />
|
||||||
|
)}
|
||||||
|
<div className="flex-1 space-y-1">
|
||||||
|
<p className="text-sm"><strong>Q:</strong> {item.question}</p>
|
||||||
|
<p className="text-sm text-muted-foreground"><strong>A:</strong> {item.answer}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Number Grid Display */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="font-semibold text-center">Click a number to make your final guess:</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">
|
||||||
|
{Array.from({ length: maxRange }, (_, i) => i + 1).map(num => {
|
||||||
|
const isValid = validNumbers.has(num);
|
||||||
|
const isGuessed = guesses.includes(num);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={num}
|
||||||
|
size="sm"
|
||||||
|
variant={isGuessed ? "default" : isValid ? "outline" : "ghost"}
|
||||||
|
className={`
|
||||||
|
h-8 p-1 text-xs
|
||||||
|
${!isValid && !isGuessed ? 'opacity-30 cursor-not-allowed text-muted-foreground' : ''}
|
||||||
|
${isGuessed ? 'bg-accent text-accent-foreground' : ''}
|
||||||
|
${isValid && !isGuessed ? 'hover:bg-accent/10' : ''}
|
||||||
|
`}
|
||||||
|
onClick={() => isValid && !isGuessed && onGuess(num)}
|
||||||
|
disabled={!isValid || isGuessed}
|
||||||
|
>
|
||||||
|
{num}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="Or type your final guess"
|
||||||
|
value={userGuess}
|
||||||
|
onChange={(e) => setUserGuess(e.target.value)}
|
||||||
|
onKeyDown={(e) => e.key === 'Enter' && onGuess()}
|
||||||
|
min={1}
|
||||||
|
max={maxRange}
|
||||||
|
/>
|
||||||
|
<Button onClick={() => onGuess()} disabled={!userGuess}>
|
||||||
|
Guess
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QuestionGuessingMode;
|
||||||
85
src/components/guessing-game/StandardGuessingMode.tsx
Normal file
85
src/components/guessing-game/StandardGuessingMode.tsx
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
|
||||||
|
interface StandardGuessingModeProps {
|
||||||
|
maxRange: number;
|
||||||
|
secretNumber: number;
|
||||||
|
guesses: number[];
|
||||||
|
feedback: string;
|
||||||
|
validNumbers: Set<number>;
|
||||||
|
userGuess: string;
|
||||||
|
setUserGuess: (value: string) => void;
|
||||||
|
onGuess: (guessValue?: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StandardGuessingMode: React.FC<StandardGuessingModeProps> = ({
|
||||||
|
maxRange,
|
||||||
|
secretNumber,
|
||||||
|
guesses,
|
||||||
|
feedback,
|
||||||
|
validNumbers,
|
||||||
|
userGuess,
|
||||||
|
setUserGuess,
|
||||||
|
onGuess
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Number Grid Display */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="font-semibold text-center">Click a number or type your guess:</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">
|
||||||
|
{Array.from({ length: maxRange }, (_, i) => i + 1).map(num => {
|
||||||
|
const isValid = validNumbers.has(num);
|
||||||
|
const isGuessed = guesses.includes(num);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={num}
|
||||||
|
size="sm"
|
||||||
|
variant={isGuessed ? "default" : isValid ? "outline" : "ghost"}
|
||||||
|
className={`
|
||||||
|
h-8 p-1 text-xs
|
||||||
|
${!isValid && !isGuessed ? 'opacity-30 cursor-not-allowed text-muted-foreground' : ''}
|
||||||
|
${isGuessed ? 'bg-accent text-accent-foreground' : ''}
|
||||||
|
${isValid && !isGuessed ? 'hover:bg-accent/10' : ''}
|
||||||
|
`}
|
||||||
|
onClick={() => isValid && !isGuessed && onGuess(num)}
|
||||||
|
disabled={!isValid || isGuessed}
|
||||||
|
>
|
||||||
|
{num}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="Or type your guess"
|
||||||
|
value={userGuess}
|
||||||
|
onChange={(e) => setUserGuess(e.target.value)}
|
||||||
|
onKeyDown={(e) => e.key === 'Enter' && onGuess()}
|
||||||
|
min={1}
|
||||||
|
max={maxRange}
|
||||||
|
/>
|
||||||
|
<Button onClick={() => onGuess()} disabled={!userGuess}>
|
||||||
|
Guess
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StandardGuessingMode;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue