addition / src /App.tsx
elyor-ml's picture
addition game
671ec71
import { useState, useEffect } from 'react'
import './App.css'
// Define the types for the game
type DisplayMode = 'numbers' | 'objects' | 'both';
interface AdditionQuestion {
num1: number;
num2: number;
answer: number;
options: number[];
object1: string;
object2: string;
}
// Emoji pool
const emojis = ['🍎', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🫐', '🍒', '🥝', '🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐸', '🐷', '⚽', '🏀', '🎾', '🏈', '🎱', '🎨', '🎭', '🎯', '🚗', '🚕', '🚓', '🚑', '🚁', '🚀'];
function App() {
const [currentQuestion, setCurrentQuestion] = useState<AdditionQuestion | null>(null);
const [displayMode, setDisplayMode] = useState<DisplayMode>('numbers');
const [feedback, setFeedback] = useState('');
const [score, setScore] = useState(0);
// Generate a random number between min and max (inclusive)
const getRandomNumber = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
// Helper function to get an operand (0-5) with a reduced chance of it being 0
const getOperandWithReducedZero = (maxValue: number): number => {
const CHANCE_OF_BEING_ZERO = 0.10; // 10% chance the operand is 0
if (maxValue < 0) maxValue = 0; // Ensure maxValue is not negative
if (maxValue === 0) return 0; // If max is 0, operand must be 0
if (Math.random() < CHANCE_OF_BEING_ZERO) {
return 0;
} else {
// Generate a number from 1 to maxValue
return getRandomNumber(1, maxValue);
}
};
// Get a random emoji
const getRandomEmoji = () => {
return emojis[Math.floor(Math.random() * emojis.length)];
};
// Generate a new question
const generateQuestion = () => {
let num1, num2;
const MAX_OPERAND_VALUE = 5;
const MAX_SUM = 5;
do {
num1 = getOperandWithReducedZero(MAX_OPERAND_VALUE);
num2 = getOperandWithReducedZero(MAX_OPERAND_VALUE);
} while (num1 + num2 > MAX_SUM);
const answer = num1 + num2;
// Generate options, one of which is the correct answer
const options = new Set<number>();
options.add(answer);
while (options.size < 3) {
const option = getRandomNumber(0, 10); // Options can be up to 10
if (option !== answer) { // Ensure options are not trivially easy if sum is 0 or 1
options.add(option);
}
}
const object1 = getRandomEmoji();
let object2 = getRandomEmoji();
// Ensure objects are different if numbers are different, or if it's the same number, it's fine.
// This logic primarily ensures visual distinction when counts are different.
if (num1 > 0 && num2 > 0 && num1 !== num2) { // only ensure different emojis if counts are different and non-zero
while (object2 === object1) {
object2 = getRandomEmoji();
}
} else if (num1 > 0 && num2 > 0 && num1 === num2) { // if numbers are same and non-zero, make sure emojis are same for clarity
object2 = object1;
}
setCurrentQuestion({
num1,
num2,
answer,
options: Array.from(options).sort(() => Math.random() - 0.5), // Shuffle options
object1,
object2,
});
setFeedback('');
};
useEffect(() => {
generateQuestion();
}, []);
// Handle answer submission
const handleAnswer = (selectedOption: number) => {
if (currentQuestion && selectedOption === currentQuestion.answer) {
setFeedback('Correct! 🎉');
setScore(score + 1);
setTimeout(() => {
generateQuestion();
}, 1500);
} else if (currentQuestion) { // Added currentQuestion check here
setFeedback('Try again! 🤔');
setTimeout(() => {
setFeedback('');
}, 1000);
}
};
if (!currentQuestion) {
return <div className="loading">Loading game...</div>;
}
const { num1, num2, answer, options, object1, object2 } = currentQuestion;
// const sumObject = object1; // No longer needed as sum is not displayed with objects here
return (
<div className="app-container">
<header className="game-header">
<h1>Addition Game</h1>
<div className="display-mode-selector">
<button onClick={() => setDisplayMode('numbers')} className={displayMode === 'numbers' ? 'active' : ''}>Number</button>
<button onClick={() => setDisplayMode('objects')} className={displayMode === 'objects' ? 'active' : ''}>Object</button>
<button onClick={() => setDisplayMode('both')} className={displayMode === 'both' ? 'active' : ''}>Both</button>
</div>
<div className="score-display">Score: {score}</div>
</header>
<main className="game-area">
<div className="question-container-new">
{/* Operand 1 Column */}
<div className="operand-column">
{(displayMode === 'numbers' || displayMode === 'both') && (
<div className="number-box">
<span className="number-text">{num1}</span>
</div>
)}
{(displayMode === 'objects' || displayMode === 'both') && (
<div className="objects-display">
{Array(num1).fill(null).map((_, i) => (
<span key={`obj1-${i}`} className="item-object">{object1}</span>
))}
</div>
)}
{/* If only objects mode and num1 is 0, show nothing or a placeholder if desired*/}
{displayMode === 'objects' && num1 === 0 && <div className="objects-display" style={{minHeight: '4rem'}}></div>}
</div>
{/* Operator + Column */}
<div className="operator-cell">
<span className="operator-symbol">+</span>
</div>
{/* Operand 2 Column */}
<div className="operand-column">
{(displayMode === 'numbers' || displayMode === 'both') && (
<div className="number-box">
<span className="number-text">{num2}</span>
</div>
)}
{(displayMode === 'objects' || displayMode === 'both') && (
<div className="objects-display">
{Array(num2).fill(null).map((_, i) => (
<span key={`obj2-${i}`} className="item-object">{object2}</span>
))}
</div>
)}
{displayMode === 'objects' && num2 === 0 && <div className="objects-display" style={{minHeight: '4rem'}}></div>}
</div>
{/* Operator = Column */}
<div className="operator-cell">
<span className="operator-symbol">=</span>
</div>
{/* Result Column - Now displays a question mark */}
<div className="result-column">
<span className="answer-placeholder-new">?</span>
</div>
</div>
<div className="options-container">
{options.map((option) => (
<button key={option} onClick={() => handleAnswer(option)} className="option-button">
{option}
</button>
))}
</div>
{feedback && <div className="feedback-message">{feedback}</div>}
</main>
</div>
);
}
export default App;