Spaces:
Sleeping
Sleeping
addition game
Browse files- README.md +79 -120
- package-lock.json +2 -2
- src/App.css +251 -199
- src/App.tsx +163 -224
README.md
CHANGED
|
@@ -1,55 +1,53 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
-
app_port: 7860
|
| 9 |
---
|
|
|
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
An interactive educational game that teaches equality comparison and counting skills through engaging emoji-based questions. Perfect for children learning basic math concepts!
|
| 14 |
|
| 15 |
## 🎮 How to Play
|
| 16 |
|
| 17 |
-
The game
|
| 18 |
-
|
| 19 |
-
### Step 1: Equality Check
|
| 20 |
-
- Look at the two sides with emojis
|
| 21 |
-
- Count the items on each side
|
| 22 |
-
- Answer the question: **"Are they equal?"**
|
| 23 |
-
- Choose between **"Equal"** or **"Not Equal"**
|
| 24 |
-
|
| 25 |
-
### Step 2: Comparison (Only if Not Equal)
|
| 26 |
-
- If you correctly answered "Not Equal" in Step 1, you proceed to Step 2
|
| 27 |
-
- The game asks either:
|
| 28 |
-
- **"Which side has more?"** or
|
| 29 |
-
- **"Which side has fewer?"**
|
| 30 |
-
- Click on the correct side to answer
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
## ✨ Features
|
| 37 |
|
| 38 |
-
- **Visual Learning**: Uses
|
| 39 |
-
- **
|
| 40 |
-
- **
|
| 41 |
-
- **
|
| 42 |
-
- **
|
| 43 |
-
- **
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
## 🚀 How to Run
|
| 46 |
|
| 47 |
-
|
|
|
|
|
|
|
| 48 |
|
| 49 |
```bash
|
| 50 |
-
# Clone the repository
|
| 51 |
-
git clone <repository-url>
|
| 52 |
-
cd
|
| 53 |
|
| 54 |
# Install dependencies
|
| 55 |
npm install
|
|
@@ -58,9 +56,9 @@ npm install
|
|
| 58 |
npm run dev
|
| 59 |
```
|
| 60 |
|
| 61 |
-
The game will be available at `http://localhost:5173`
|
| 62 |
|
| 63 |
-
###
|
| 64 |
|
| 65 |
```bash
|
| 66 |
# Build for production
|
|
@@ -70,122 +68,83 @@ npm run build
|
|
| 70 |
npm run preview
|
| 71 |
```
|
| 72 |
|
| 73 |
-
###
|
|
|
|
|
|
|
| 74 |
|
| 75 |
```bash
|
| 76 |
-
# Build Docker image
|
| 77 |
-
docker build -t
|
| 78 |
|
| 79 |
-
# Run container
|
| 80 |
-
docker run -p 3000:
|
| 81 |
```
|
| 82 |
|
| 83 |
-
The game will be available at `http://localhost:3000`
|
| 84 |
-
|
| 85 |
## 🎯 Educational Benefits
|
| 86 |
|
| 87 |
-
- **
|
| 88 |
-
- **
|
| 89 |
-
- **
|
| 90 |
-
- **
|
| 91 |
-
- **
|
| 92 |
|
| 93 |
-
## 🎨
|
| 94 |
|
| 95 |
-
The game uses emojis
|
|
|
|
| 96 |
|
| 97 |
-
|
| 98 |
-
- **Fruits**: 🍎 🍊 🍋 🍌 🍉 🍇 🍓 🫐 🍒 🥝
|
| 99 |
-
- **Objects**: ⚽ 🏀 🎾 🏈 🎱 🎪 🎨 🎭 🎪 🎯
|
| 100 |
-
- **Hearts**: 💙 💚 💛 💜 🤍 🖤 🤎 💗 💖 💕
|
| 101 |
-
- **Stars**: ⭐ 🌟 💫 ✨ 🌠 ⚡ 🔥 ❄️ ☀️ 🌙
|
| 102 |
|
| 103 |
## 🛠️ Technical Details
|
| 104 |
|
| 105 |
### Technologies Used
|
| 106 |
-
- **React 19** with TypeScript
|
| 107 |
-
- **Vite** for build tooling
|
| 108 |
-
- **CSS3** with modern features (Grid, Flexbox, Animations)
|
| 109 |
-
- **Docker** for deployment
|
| 110 |
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
```
|
| 113 |
src/
|
| 114 |
-
├── App.tsx # Main game component
|
| 115 |
-
├── App.css #
|
| 116 |
-
├── main.tsx #
|
| 117 |
-
└── index.css # Global styles
|
| 118 |
|
| 119 |
public/
|
| 120 |
-
├──
|
| 121 |
-
└──
|
| 122 |
-
```
|
| 123 |
-
|
| 124 |
-
### Game Logic
|
| 125 |
-
- **Question Generation**: Random emoji selection from categories
|
| 126 |
-
- **Count Generation**: Random numbers 1-10 for each side
|
| 127 |
-
- **Equality Control**: 50% chance for equal vs unequal questions
|
| 128 |
-
- **Step Control**: Step 2 only appears for unequal quantities
|
| 129 |
-
- **Safety Checks**: Prevents step 2 from showing with equal sides
|
| 130 |
-
|
| 131 |
-
## 🎨 Customization
|
| 132 |
-
|
| 133 |
-
You can easily customize the game by modifying the emoji categories in `src/App.tsx`:
|
| 134 |
-
|
| 135 |
-
```typescript
|
| 136 |
-
const emojiCategories = {
|
| 137 |
-
animals: ['🐶', '🐱', ...], // Add your animal emojis
|
| 138 |
-
fruits: ['🍎', '🍊', ...], // Add your fruit emojis
|
| 139 |
-
// Add more categories...
|
| 140 |
-
};
|
| 141 |
```
|
| 142 |
|
| 143 |
## 📱 Mobile Support
|
| 144 |
|
| 145 |
-
The game is
|
| 146 |
-
- **Desktop**: Full-featured experience with hover effects
|
| 147 |
-
- **Tablet**: Touch-friendly interface with optimized sizing
|
| 148 |
-
- **Mobile**: Compact layout with large touch targets
|
| 149 |
-
|
| 150 |
-
## 🚀 Deployment
|
| 151 |
-
|
| 152 |
-
### Hugging Face Spaces
|
| 153 |
-
This project is configured for easy deployment to Hugging Face Spaces using Docker.
|
| 154 |
-
|
| 155 |
-
### Other Platforms
|
| 156 |
-
The built project can be deployed to any static hosting service:
|
| 157 |
-
- Vercel
|
| 158 |
-
- Netlify
|
| 159 |
-
- GitHub Pages
|
| 160 |
-
- AWS S3
|
| 161 |
-
- And more...
|
| 162 |
|
| 163 |
## 🤝 Contributing
|
| 164 |
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
|
|
|
|
|
|
| 170 |
|
| 171 |
## 📄 License
|
| 172 |
|
| 173 |
-
MIT License
|
| 174 |
|
| 175 |
## 🎓 Age Recommendation
|
| 176 |
|
| 177 |
-
This game is
|
| 178 |
-
|
| 179 |
-
-
|
| 180 |
-
-
|
| 181 |
-
-
|
| 182 |
|
| 183 |
-
|
| 184 |
-
- **Preschool** mathematics preparation
|
| 185 |
-
- **Kindergarten** counting practice
|
| 186 |
-
- **Early elementary** comparison skills
|
| 187 |
-
- **Special education** visual learning support
|
| 188 |
|
| 189 |
---
|
| 190 |
|
| 191 |
-
Made with ❤️ for young learners everywhere!
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Addition Game
|
| 3 |
+
emoji: ➕
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
+
app_port: 7860 # Assuming Vite default port, adjust if different after changes
|
| 9 |
---
|
| 10 |
+
# Simple Addition Game ➕
|
| 11 |
|
| 12 |
+
An interactive educational game designed to teach basic addition (sums up to 5, extendable) to young children. It uses numbers, colorful emojis, or a combination of both to make learning fun and engaging!
|
|
|
|
|
|
|
| 13 |
|
| 14 |
## 🎮 How to Play
|
| 15 |
|
| 16 |
+
The game presents a simple addition problem (e.g., `1 + 2 = ?`).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
+
1. **Observe the Problem**: See the two numbers to be added.
|
| 19 |
+
2. **Choose Display Mode**: At the top, select how you want to see the numbers:
|
| 20 |
+
* **Number**: Shows only numerals (e.g., `1` and `2`).
|
| 21 |
+
* **Object**: Shows only emojis (e.g., 🍎 and 🍎🍎).
|
| 22 |
+
* **Both**: Shows numerals in boxes with emojis underneath each.
|
| 23 |
+
3. **Solve**: Determine the sum of the two numbers.
|
| 24 |
+
4. **Select Answer**: Click on one of the three answer options provided at the bottom of the screen. One of these options is the correct sum.
|
| 25 |
+
5. **Feedback**: The game will tell you if your answer is "Correct! 🎉" or "Try again! 🤔".
|
| 26 |
+
6. **New Question**: After a correct answer, a new addition problem is automatically generated.
|
| 27 |
|
| 28 |
## ✨ Features
|
| 29 |
|
| 30 |
+
- **Visual Learning**: Uses numbers, a variety of emojis (fruits, animals, objects, etc.), or both to represent numbers.
|
| 31 |
+
- **Interactive Display Modes**: Children can switch between seeing problems as numbers, objects, or a combination, catering to different learning preferences.
|
| 32 |
+
- **Clear Question Format**: Presents addition problems in a clear, visual layout (e.g., `Num1 + Num2 = ?`).
|
| 33 |
+
- **Multiple Choice Answers**: Provides three options, making it easy for young children to select their answer.
|
| 34 |
+
- **Immediate Feedback**: Instant confirmation of correct or incorrect answers.
|
| 35 |
+
- **Score Tracking**: Keeps a simple score of correct answers.
|
| 36 |
+
- **Randomized Questions**: Generates addition problems with numbers from 0 to 5, ensuring the sum does not exceed 5 (configurable).
|
| 37 |
+
- **Reduced Zero Probability**: The chance of 0 appearing as an operand is intentionally lowered to provide more varied practice.
|
| 38 |
+
- **Responsive Design**: Adapts to different screen sizes (desktop, tablet, mobile).
|
| 39 |
+
- **Child-Friendly UI**: Large, easy-to-click buttons and clear, colorful visuals.
|
| 40 |
|
| 41 |
## 🚀 How to Run
|
| 42 |
|
| 43 |
+
This project is built with React (using Vite) and TypeScript.
|
| 44 |
+
|
| 45 |
+
### Development Mode
|
| 46 |
|
| 47 |
```bash
|
| 48 |
+
# Clone the repository (if you haven't already)
|
| 49 |
+
# git clone <repository-url>
|
| 50 |
+
# cd <repository-name>
|
| 51 |
|
| 52 |
# Install dependencies
|
| 53 |
npm install
|
|
|
|
| 56 |
npm run dev
|
| 57 |
```
|
| 58 |
|
| 59 |
+
The game will typically be available at `http://localhost:5173` (Vite's default port).
|
| 60 |
|
| 61 |
+
### Production Build
|
| 62 |
|
| 63 |
```bash
|
| 64 |
# Build for production
|
|
|
|
| 68 |
npm run preview
|
| 69 |
```
|
| 70 |
|
| 71 |
+
### Docker
|
| 72 |
+
|
| 73 |
+
If a `Dockerfile` is configured (the one from the previous game might need adjustments for port or build steps if they changed significantly):
|
| 74 |
|
| 75 |
```bash
|
| 76 |
+
# Build Docker image (example name)
|
| 77 |
+
docker build -t addition-game .
|
| 78 |
|
| 79 |
+
# Run container (example mapping to port 3000 on host)
|
| 80 |
+
docker run -p 3000:5173 addition-game
|
| 81 |
```
|
| 82 |
|
|
|
|
|
|
|
| 83 |
## 🎯 Educational Benefits
|
| 84 |
|
| 85 |
+
- **Basic Addition Skills**: Children practice adding numbers with sums typically up to 5 (can be easily extended).
|
| 86 |
+
- **Number Recognition**: Reinforces the visual representation of numbers.
|
| 87 |
+
- **Object Counting**: Connects numerals with quantities of objects.
|
| 88 |
+
- **Problem Solving**: Encourages children to think through and solve simple math problems.
|
| 89 |
+
- **Visual-Numeric Association**: Helps bridge the gap between abstract numbers and concrete objects, especially in "Both" mode.
|
| 90 |
|
| 91 |
+
## 🎨 Emoji Pool
|
| 92 |
|
| 93 |
+
The game uses a diverse pool of emojis to represent objects, including:
|
| 94 |
+
🍎 🍊 🍋 🍌 🍉 🍇 🍓 🫐 🍒 🥝 🐶 🐱 🐭 🐹 🐰 🦊 🐻 🐼 🐸 🐷 ⚽ 🏀 🎾 🏈 🎱 🎨 🎭 🎯 🚗 🚕 🚓 🚑 🚁 🚀
|
| 95 |
|
| 96 |
+
This pool can be easily modified in `src/App.tsx`.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
## 🛠️ Technical Details
|
| 99 |
|
| 100 |
### Technologies Used
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
+
- **React** with TypeScript
|
| 103 |
+
- **Vite** for build tooling and development server
|
| 104 |
+
- **CSS3** for styling
|
| 105 |
+
|
| 106 |
+
### Project Structure (Key Files)
|
| 107 |
+
|
| 108 |
```
|
| 109 |
src/
|
| 110 |
+
├── App.tsx # Main game component, logic, and JSX structure
|
| 111 |
+
├── App.css # Styling for the game
|
| 112 |
+
├── main.tsx # Application entry point
|
| 113 |
+
└── index.css # Global styles (if any beyond App.css)
|
| 114 |
|
| 115 |
public/
|
| 116 |
+
├── index.html # HTML template
|
| 117 |
+
└── ... # Other static assets like favicons
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
```
|
| 119 |
|
| 120 |
## 📱 Mobile Support
|
| 121 |
|
| 122 |
+
The game is designed to be responsive and playable on various devices, including desktops, tablets, and mobile phones.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
## 🤝 Contributing
|
| 125 |
|
| 126 |
+
Contributions are welcome! Please follow standard Git practices:
|
| 127 |
+
|
| 128 |
+
1. Fork the repository.
|
| 129 |
+
2. Create a new branch for your feature (`git checkout -b feature/YourFeature`).
|
| 130 |
+
3. Commit your changes (`git commit -m 'Add some feature'`).
|
| 131 |
+
4. Push to the branch (`git push origin feature/YourFeature`).
|
| 132 |
+
5. Open a Pull Request.
|
| 133 |
|
| 134 |
## 📄 License
|
| 135 |
|
| 136 |
+
This project is typically licensed under the MIT License (please verify if a `LICENSE` file exists or add one as appropriate).
|
| 137 |
|
| 138 |
## 🎓 Age Recommendation
|
| 139 |
|
| 140 |
+
This game is suitable for children typically in **Preschool to early Elementary grades (ages 3-7)** who are beginning to learn:
|
| 141 |
+
|
| 142 |
+
- Number identification
|
| 143 |
+
- Basic counting
|
| 144 |
+
- The concept of addition
|
| 145 |
|
| 146 |
+
It can serve as a fun tool for parents and educators to introduce and reinforce these early math skills.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
---
|
| 149 |
|
| 150 |
+
Made with ❤️ for young learners everywhere!
|
package-lock.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
| 1 |
{
|
| 2 |
-
"name": "
|
| 3 |
"version": "1.0.0",
|
| 4 |
"lockfileVersion": 3,
|
| 5 |
"requires": true,
|
| 6 |
"packages": {
|
| 7 |
"": {
|
| 8 |
-
"name": "
|
| 9 |
"version": "1.0.0",
|
| 10 |
"dependencies": {
|
| 11 |
"react": "^19.0.0",
|
|
|
|
| 1 |
{
|
| 2 |
+
"name": "preposition-learning-game",
|
| 3 |
"version": "1.0.0",
|
| 4 |
"lockfileVersion": 3,
|
| 5 |
"requires": true,
|
| 6 |
"packages": {
|
| 7 |
"": {
|
| 8 |
+
"name": "preposition-learning-game",
|
| 9 |
"version": "1.0.0",
|
| 10 |
"dependencies": {
|
| 11 |
"react": "^19.0.0",
|
src/App.css
CHANGED
|
@@ -1,282 +1,334 @@
|
|
| 1 |
-
/*
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
| 4 |
display: flex;
|
| 5 |
-
flex-direction: column;
|
| 6 |
-
align-items: center;
|
| 7 |
justify-content: center;
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
display: flex;
|
| 16 |
-
justify-content:
|
| 17 |
align-items: center;
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
}
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
font-size: 3rem;
|
| 27 |
font-weight: bold;
|
| 28 |
-
color:
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
}
|
| 33 |
|
| 34 |
-
|
| 35 |
-
.comparison-container {
|
| 36 |
display: flex;
|
|
|
|
| 37 |
align-items: center;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
justify-content: center;
|
| 39 |
-
|
| 40 |
-
margin-bottom: 3rem;
|
| 41 |
-
flex-wrap: wrap;
|
| 42 |
}
|
| 43 |
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
max-width: 400px;
|
| 52 |
display: flex;
|
| 53 |
-
flex-direction: column;
|
|
|
|
| 54 |
align-items: center;
|
| 55 |
-
gap:
|
| 56 |
-
|
|
|
|
| 57 |
}
|
| 58 |
|
| 59 |
-
.
|
| 60 |
-
|
|
|
|
| 61 |
}
|
| 62 |
|
| 63 |
-
.
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
}
|
| 67 |
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
|
|
|
| 75 |
}
|
| 76 |
|
| 77 |
-
.
|
| 78 |
-
|
| 79 |
-
display: block;
|
| 80 |
-
animation: popIn 0.3s ease-out;
|
| 81 |
}
|
| 82 |
|
| 83 |
-
|
| 84 |
-
0
|
| 85 |
-
transform: scale(0);
|
| 86 |
-
}
|
| 87 |
-
80% {
|
| 88 |
-
transform: scale(1.1);
|
| 89 |
-
}
|
| 90 |
-
100% {
|
| 91 |
-
transform: scale(1);
|
| 92 |
-
}
|
| 93 |
}
|
| 94 |
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
font-size: 2.5rem;
|
| 98 |
font-weight: bold;
|
| 99 |
-
color: #
|
| 100 |
-
background: #
|
|
|
|
| 101 |
border-radius: 50%;
|
| 102 |
-
width:
|
| 103 |
-
height:
|
| 104 |
display: flex;
|
| 105 |
-
align-items: center;
|
| 106 |
justify-content: center;
|
| 107 |
-
|
|
|
|
| 108 |
}
|
| 109 |
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
font-size: 2rem;
|
| 113 |
font-weight: bold;
|
| 114 |
-
color:
|
| 115 |
-
background: rgba(255,255,255,0.2);
|
| 116 |
-
border-radius: 50%;
|
| 117 |
-
width: 80px;
|
| 118 |
-
height: 80px;
|
| 119 |
display: flex;
|
| 120 |
-
align-items: center;
|
| 121 |
justify-content: center;
|
| 122 |
-
|
|
|
|
|
|
|
| 123 |
}
|
| 124 |
|
| 125 |
-
/*
|
| 126 |
-
.
|
| 127 |
display: flex;
|
| 128 |
-
gap: 2rem;
|
| 129 |
justify-content: center;
|
| 130 |
-
|
|
|
|
| 131 |
}
|
| 132 |
|
| 133 |
-
.
|
| 134 |
-
|
| 135 |
-
font-size: 1.5rem;
|
| 136 |
font-weight: bold;
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
| 138 |
border-radius: 15px;
|
| 139 |
cursor: pointer;
|
| 140 |
-
transition:
|
| 141 |
-
min-width:
|
| 142 |
-
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
| 143 |
}
|
| 144 |
|
| 145 |
-
.
|
| 146 |
-
background:
|
| 147 |
-
color:
|
|
|
|
| 148 |
}
|
| 149 |
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
color
|
| 159 |
}
|
| 160 |
|
| 161 |
-
.
|
| 162 |
-
|
| 163 |
-
transform: translateY(-2px);
|
| 164 |
-
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
|
| 165 |
}
|
| 166 |
|
| 167 |
-
/*
|
| 168 |
-
.
|
| 169 |
-
font-size:
|
| 170 |
-
color: white;
|
| 171 |
text-align: center;
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
padding: 1rem 2rem;
|
| 175 |
-
border-radius: 10px;
|
| 176 |
-
backdrop-filter: blur(10px);
|
| 177 |
}
|
| 178 |
|
| 179 |
-
/*
|
| 180 |
-
.
|
| 181 |
-
position: fixed;
|
| 182 |
-
top: 50%;
|
| 183 |
-
left: 50%;
|
| 184 |
-
transform: translate(-50%, -50%);
|
| 185 |
-
background: white;
|
| 186 |
-
color: #333;
|
| 187 |
-
font-size: 2rem;
|
| 188 |
-
font-weight: bold;
|
| 189 |
-
padding: 2rem 3rem;
|
| 190 |
-
border-radius: 20px;
|
| 191 |
-
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
| 192 |
-
z-index: 1000;
|
| 193 |
-
animation: feedbackPop 0.5s ease-out;
|
| 194 |
-
}
|
| 195 |
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
|
|
|
| 199 |
}
|
| 200 |
-
|
| 201 |
-
|
|
|
|
| 202 |
}
|
| 203 |
-
|
| 204 |
-
|
| 205 |
}
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
/*
|
| 209 |
-
|
| 210 |
-
.question-title {
|
| 211 |
-
font-size: 2rem;
|
| 212 |
-
margin-bottom: 1.5rem;
|
| 213 |
}
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
}
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
|
|
|
| 224 |
}
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
}
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
font-size: 1.5rem;
|
| 234 |
}
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
height: 60px;
|
| 239 |
-
font-size: 2rem;
|
| 240 |
}
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
flex-direction: column;
|
| 244 |
-
align-items: center;
|
| 245 |
-
gap: 1rem;
|
| 246 |
}
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
}
|
| 253 |
-
|
| 254 |
.feedback-message {
|
| 255 |
font-size: 1.5rem;
|
| 256 |
-
padding: 1.5rem 2rem;
|
| 257 |
}
|
| 258 |
}
|
| 259 |
|
| 260 |
@media (max-width: 480px) {
|
| 261 |
-
.
|
| 262 |
-
|
|
|
|
| 263 |
}
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
font-size: 1.5rem;
|
| 267 |
}
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
|
|
|
| 273 |
}
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
max-width: 200px;
|
| 277 |
}
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
|
|
|
| 281 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
}
|
|
|
|
| 1 |
+
/* General App Styles */
|
| 2 |
+
body {
|
| 3 |
+
font-family: 'Arial', sans-serif;
|
| 4 |
+
background-color: #f0f8ff; /* Light alice blue */
|
| 5 |
+
color: #333;
|
| 6 |
display: flex;
|
|
|
|
|
|
|
| 7 |
justify-content: center;
|
| 8 |
+
align-items: center;
|
| 9 |
+
min-height: 100vh;
|
| 10 |
+
margin: 0;
|
| 11 |
+
padding: 20px;
|
| 12 |
+
box-sizing: border-box;
|
| 13 |
}
|
| 14 |
|
| 15 |
+
.app-container {
|
| 16 |
+
background-color: #fff;
|
| 17 |
+
padding: 30px;
|
| 18 |
+
border-radius: 20px;
|
| 19 |
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
| 20 |
+
text-align: center;
|
| 21 |
+
width: 100%;
|
| 22 |
+
max-width: 900px; /* Max width adjusted for new layout */
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
.game-header {
|
| 26 |
+
margin-bottom: 30px;
|
| 27 |
display: flex;
|
| 28 |
+
justify-content: space-between;
|
| 29 |
align-items: center;
|
| 30 |
+
padding-bottom: 20px;
|
| 31 |
+
border-bottom: 2px solid #e0e0e0;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.game-header h1 {
|
| 35 |
+
font-size: 2.5rem;
|
| 36 |
+
color: #007bff;
|
| 37 |
+
margin: 0;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.display-mode-selector button {
|
| 41 |
+
font-size: 1.3rem;
|
| 42 |
+
padding: 10px 18px;
|
| 43 |
+
margin: 0 8px;
|
| 44 |
+
border: 2px solid #007bff;
|
| 45 |
+
background-color: #fff;
|
| 46 |
+
color: #007bff;
|
| 47 |
+
border-radius: 10px;
|
| 48 |
+
cursor: pointer;
|
| 49 |
+
transition: background-color 0.3s, color 0.3s;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.display-mode-selector button:hover {
|
| 53 |
+
background-color: #0056b3;
|
| 54 |
+
color: #fff;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.display-mode-selector button.active {
|
| 58 |
+
background-color: #007bff;
|
| 59 |
+
color: #fff;
|
| 60 |
}
|
| 61 |
|
| 62 |
+
.score-display {
|
| 63 |
+
font-size: 1.6rem;
|
|
|
|
| 64 |
font-weight: bold;
|
| 65 |
+
color: #28a745;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
/* Game Area Styles */
|
| 69 |
+
.game-area {
|
| 70 |
+
padding: 20px;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
/* New Question Container for 5-column layout */
|
| 74 |
+
.question-container-new {
|
| 75 |
+
display: flex;
|
| 76 |
+
justify-content: space-around; /* Distribute columns */
|
| 77 |
+
align-items: flex-start; /* Align tops of columns */
|
| 78 |
+
background-color: #fffacd; /* Light yellow background like image */
|
| 79 |
+
padding: 20px;
|
| 80 |
+
border-radius: 15px;
|
| 81 |
+
margin-bottom: 30px;
|
| 82 |
+
border: 1px solid #ddd; /* Soft border */
|
| 83 |
}
|
| 84 |
|
| 85 |
+
.operand-column, .result-column {
|
|
|
|
| 86 |
display: flex;
|
| 87 |
+
flex-direction: column;
|
| 88 |
align-items: center;
|
| 89 |
+
justify-content: center; /* Center content like the question mark */
|
| 90 |
+
gap: 15px;
|
| 91 |
+
flex: 1;
|
| 92 |
+
padding: 10px;
|
| 93 |
+
min-height: 150px; /* Ensure it has some height to center the ? vertically */
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.number-box {
|
| 97 |
+
background-color: #fff;
|
| 98 |
+
border: 1px solid #ccc;
|
| 99 |
+
border-radius: 10px;
|
| 100 |
+
padding: 20px 30px;
|
| 101 |
+
min-width: 80px; /* Minimum width for the number box */
|
| 102 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
| 103 |
+
display: inline-flex; /* To make it wrap content tightly */
|
| 104 |
justify-content: center;
|
| 105 |
+
align-items: center;
|
|
|
|
|
|
|
| 106 |
}
|
| 107 |
|
| 108 |
+
.number-text {
|
| 109 |
+
font-size: 3.5rem; /* Large numbers */
|
| 110 |
+
font-weight: bold;
|
| 111 |
+
color: #333;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
.objects-display {
|
|
|
|
| 115 |
display: flex;
|
| 116 |
+
flex-direction: column; /* Stack objects vertically as in image */
|
| 117 |
+
justify-content: center;
|
| 118 |
align-items: center;
|
| 119 |
+
gap: 8px;
|
| 120 |
+
min-height: 60px; /* Base height even if empty */
|
| 121 |
+
width: 100%;
|
| 122 |
}
|
| 123 |
|
| 124 |
+
.item-object {
|
| 125 |
+
font-size: 2.5rem !important; /* Adjusted emoji size */
|
| 126 |
+
padding: 2px;
|
| 127 |
}
|
| 128 |
|
| 129 |
+
.operator-cell {
|
| 130 |
+
display: flex;
|
| 131 |
+
flex-direction: column; /* To center operator and have line above/below */
|
| 132 |
+
align-items: center;
|
| 133 |
+
justify-content: center; /* Center the operator symbol vertically */
|
| 134 |
+
position: relative; /* For pseudo-elements for lines */
|
| 135 |
+
align-self: stretch; /* Make cell take full height of question-container-new */
|
| 136 |
+
padding: 0 20px; /* Horizontal padding around operator */
|
| 137 |
}
|
| 138 |
|
| 139 |
+
.operator-cell::before, .operator-cell::after {
|
| 140 |
+
content: '';
|
| 141 |
+
position: absolute;
|
| 142 |
+
left: 50%;
|
| 143 |
+
transform: translateX(-50%);
|
| 144 |
+
width: 2px; /* Width of the vertical line */
|
| 145 |
+
background-color: #bdbdbd; /* Color of the vertical line */
|
| 146 |
+
height: calc(50% - 25px); /* Adjust based on operator symbol size and desired gap */
|
| 147 |
}
|
| 148 |
|
| 149 |
+
.operator-cell::before {
|
| 150 |
+
top: 0;
|
|
|
|
|
|
|
| 151 |
}
|
| 152 |
|
| 153 |
+
.operator-cell::after {
|
| 154 |
+
bottom: 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
}
|
| 156 |
|
| 157 |
+
.operator-symbol {
|
| 158 |
+
font-size: 2rem;
|
|
|
|
| 159 |
font-weight: bold;
|
| 160 |
+
color: #555;
|
| 161 |
+
background-color: #fff;
|
| 162 |
+
border: 2px solid #bdbdbd;
|
| 163 |
border-radius: 50%;
|
| 164 |
+
width: 40px;
|
| 165 |
+
height: 40px;
|
| 166 |
display: flex;
|
|
|
|
| 167 |
justify-content: center;
|
| 168 |
+
align-items: center;
|
| 169 |
+
z-index: 1; /* To appear above the pseudo-element lines */
|
| 170 |
}
|
| 171 |
|
| 172 |
+
.answer-placeholder-new {
|
| 173 |
+
font-size: 4rem; /* Large question mark */
|
|
|
|
| 174 |
font-weight: bold;
|
| 175 |
+
color: #dc3545; /* Red color or similar to operand numbers */
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
display: flex;
|
|
|
|
| 177 |
justify-content: center;
|
| 178 |
+
align-items: center;
|
| 179 |
+
/* If it needs a box like numbers, we can add similar styling to .number-box here */
|
| 180 |
+
/* For now, just a large character */
|
| 181 |
}
|
| 182 |
|
| 183 |
+
/* Options Styles - Assuming these are still relevant */
|
| 184 |
+
.options-container {
|
| 185 |
display: flex;
|
|
|
|
| 186 |
justify-content: center;
|
| 187 |
+
gap: 20px;
|
| 188 |
+
margin-bottom: 30px;
|
| 189 |
}
|
| 190 |
|
| 191 |
+
.option-button {
|
| 192 |
+
font-size: 2.5rem;
|
|
|
|
| 193 |
font-weight: bold;
|
| 194 |
+
padding: 20px 35px;
|
| 195 |
+
border: 3px solid #28a745;
|
| 196 |
+
background-color: #fff;
|
| 197 |
+
color: #28a745;
|
| 198 |
border-radius: 15px;
|
| 199 |
cursor: pointer;
|
| 200 |
+
transition: background-color 0.3s, color 0.3s, transform 0.2s;
|
| 201 |
+
min-width: 90px;
|
|
|
|
| 202 |
}
|
| 203 |
|
| 204 |
+
.option-button:hover {
|
| 205 |
+
background-color: #218838;
|
| 206 |
+
color: #fff;
|
| 207 |
+
transform: translateY(-3px);
|
| 208 |
}
|
| 209 |
|
| 210 |
+
/* Feedback Message Styles */
|
| 211 |
+
.feedback-message {
|
| 212 |
+
font-size: 1.8rem;
|
| 213 |
+
font-weight: bold;
|
| 214 |
+
margin-top: 25px;
|
| 215 |
+
padding: 15px;
|
| 216 |
+
border-radius: 10px;
|
| 217 |
+
color: #fff;
|
| 218 |
+
/* Assuming JS still sets background color for correct/incorrect, or do it here */
|
| 219 |
}
|
| 220 |
|
| 221 |
+
.feedback-message:empty {
|
| 222 |
+
display: none;
|
|
|
|
|
|
|
| 223 |
}
|
| 224 |
|
| 225 |
+
/* Loading State */
|
| 226 |
+
.loading {
|
| 227 |
+
font-size: 2rem;
|
|
|
|
| 228 |
text-align: center;
|
| 229 |
+
padding: 50px;
|
| 230 |
+
color: #007bff;
|
|
|
|
|
|
|
|
|
|
| 231 |
}
|
| 232 |
|
| 233 |
+
/* Remove old styles that are no longer used */
|
| 234 |
+
/* .question-container, .operand-stack, .operator, .answer-placeholder (and their media queries if specific) */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
|
| 236 |
+
/* Responsive adjustments for the new layout */
|
| 237 |
+
@media (max-width: 768px) {
|
| 238 |
+
.game-header h1 {
|
| 239 |
+
font-size: 2rem;
|
| 240 |
}
|
| 241 |
+
.display-mode-selector button {
|
| 242 |
+
font-size: 1.1rem;
|
| 243 |
+
padding: 8px 12px;
|
| 244 |
}
|
| 245 |
+
.score-display {
|
| 246 |
+
font-size: 1.3rem;
|
| 247 |
}
|
| 248 |
+
.question-container-new {
|
| 249 |
+
flex-direction: column; /* Stack columns vertically on smaller screens */
|
| 250 |
+
align-items: center; /* Center columns when stacked */
|
| 251 |
+
padding: 15px;
|
|
|
|
|
|
|
|
|
|
| 252 |
}
|
| 253 |
+
.operand-column, .result-column {
|
| 254 |
+
width: 100%; /* Make columns take full width when stacked */
|
| 255 |
+
max-width: 300px; /* Limit width of operand columns */
|
| 256 |
+
gap: 10px;
|
| 257 |
}
|
| 258 |
+
.operator-cell {
|
| 259 |
+
flex-direction: row; /* Operators side by side with lines */
|
| 260 |
+
align-self: auto; /* Reset self-stretch */
|
| 261 |
+
padding: 10px 0;
|
| 262 |
+
width: 80%;
|
| 263 |
+
max-width: 250px;
|
| 264 |
}
|
| 265 |
+
.operator-cell::before, .operator-cell::after {
|
| 266 |
+
width: calc(50% - 25px); /* Horizontal lines */
|
| 267 |
+
height: 2px;
|
| 268 |
+
top: 50%;
|
| 269 |
+
transform: translateY(-50%);
|
| 270 |
}
|
| 271 |
+
.operator-cell::before {
|
| 272 |
+
left: 0;
|
|
|
|
| 273 |
}
|
| 274 |
+
.operator-cell::after {
|
| 275 |
+
right: 0;
|
| 276 |
+
left: auto; /* Override previous left: 50% */
|
|
|
|
|
|
|
| 277 |
}
|
| 278 |
+
.number-text {
|
| 279 |
+
font-size: 2.8rem;
|
|
|
|
|
|
|
|
|
|
| 280 |
}
|
| 281 |
+
.item-object {
|
| 282 |
+
font-size: 2rem !important;
|
| 283 |
+
}
|
| 284 |
+
.operator-symbol {
|
| 285 |
+
font-size: 1.5rem;
|
| 286 |
+
width: 35px;
|
| 287 |
+
height: 35px;
|
| 288 |
+
}
|
| 289 |
+
.options-container {
|
| 290 |
+
flex-direction: column;
|
| 291 |
+
gap: 10px;
|
| 292 |
+
}
|
| 293 |
+
.option-button {
|
| 294 |
+
font-size: 2rem;
|
| 295 |
+
padding: 15px 25px;
|
| 296 |
}
|
|
|
|
| 297 |
.feedback-message {
|
| 298 |
font-size: 1.5rem;
|
|
|
|
| 299 |
}
|
| 300 |
}
|
| 301 |
|
| 302 |
@media (max-width: 480px) {
|
| 303 |
+
.game-header {
|
| 304 |
+
flex-direction: column;
|
| 305 |
+
gap: 10px;
|
| 306 |
}
|
| 307 |
+
.game-header h1 {
|
| 308 |
+
font-size: 1.6rem;
|
|
|
|
| 309 |
}
|
| 310 |
+
.display-mode-selector button {
|
| 311 |
+
font-size: 1rem;
|
| 312 |
+
padding: 6px 10px;
|
| 313 |
+
}
|
| 314 |
+
.number-text {
|
| 315 |
+
font-size: 2.2rem;
|
| 316 |
}
|
| 317 |
+
.item-object {
|
| 318 |
+
font-size: 1.8rem !important;
|
|
|
|
| 319 |
}
|
| 320 |
+
.operator-symbol {
|
| 321 |
+
font-size: 1.3rem;
|
| 322 |
+
width: 30px;
|
| 323 |
+
height: 30px;
|
| 324 |
}
|
| 325 |
+
.option-button {
|
| 326 |
+
font-size: 1.6rem;
|
| 327 |
+
padding: 12px 20px;
|
| 328 |
+
}
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
/* Clean up old, unused styles - This should be done carefully after verifying */
|
| 332 |
+
.question-container, .operand-stack, .operator, .answer-placeholder, .result-objects-display {
|
| 333 |
+
display: none !important; /* Hide them if they are still in the DOM somehow */
|
| 334 |
}
|
src/App.tsx
CHANGED
|
@@ -1,263 +1,202 @@
|
|
| 1 |
import { useState, useEffect } from 'react'
|
| 2 |
import './App.css'
|
| 3 |
|
| 4 |
-
//
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
}
|
| 11 |
|
| 12 |
-
//
|
| 13 |
-
|
| 14 |
|
| 15 |
function App() {
|
| 16 |
-
const [currentQuestion, setCurrentQuestion] = useState<
|
| 17 |
-
const [
|
| 18 |
const [feedback, setFeedback] = useState('');
|
| 19 |
-
const [
|
| 20 |
|
| 21 |
-
//
|
| 22 |
-
const
|
| 23 |
-
|
| 24 |
-
fruits: ['🍎', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🫐', '🍒', '🥝'],
|
| 25 |
-
objects: ['⚽', '🏀', '🎾', '🏈', '🎱', '🎪', '🎨', '🎭', '🎪', '🎯'],
|
| 26 |
-
hearts: ['💙', '💚', '💛', '💜', '🤍', '🖤', '🤎', '💗', '💖', '💕'],
|
| 27 |
-
stars: ['⭐', '🌟', '💫', '✨', '🌠', '⚡', '🔥', '❄️', '☀️', '🌙']
|
| 28 |
};
|
| 29 |
|
| 30 |
-
//
|
| 31 |
-
const
|
| 32 |
-
const
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
// 50% chance of making them equal
|
| 44 |
-
const shouldBeEqual = Math.random() < 0.5;
|
| 45 |
-
if (shouldBeEqual) {
|
| 46 |
-
rightCount = leftCount;
|
| 47 |
}
|
| 48 |
-
|
| 49 |
-
// Create arrays with repeated emoji
|
| 50 |
-
const leftSide = Array(leftCount).fill(emoji);
|
| 51 |
-
const rightSide = Array(rightCount).fill(emoji);
|
| 52 |
-
|
| 53 |
-
// Determine second step question type
|
| 54 |
-
const secondStepQuestion: 'more' | 'fewer' = Math.random() < 0.5 ? 'more' : 'fewer';
|
| 55 |
-
|
| 56 |
-
return {
|
| 57 |
-
leftSide,
|
| 58 |
-
rightSide,
|
| 59 |
-
isEqual: leftCount === rightCount,
|
| 60 |
-
secondStepQuestion
|
| 61 |
-
};
|
| 62 |
};
|
| 63 |
|
| 64 |
-
//
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
}
|
| 68 |
|
| 69 |
-
//
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
// Generate new question and go back to step 1
|
| 75 |
-
setTimeout(() => {
|
| 76 |
-
setCurrentQuestion(generateQuestion());
|
| 77 |
-
setGameStep('step1');
|
| 78 |
-
}, 1000);
|
| 79 |
-
}
|
| 80 |
-
}, [currentQuestion, gameStep]);
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
// Generate new question after delay
|
| 97 |
-
setTimeout(() => {
|
| 98 |
-
setCurrentQuestion(generateQuestion());
|
| 99 |
-
setGameStep('step1');
|
| 100 |
-
setShowFeedback(false);
|
| 101 |
-
setFeedback('');
|
| 102 |
-
}, 2000);
|
| 103 |
-
} else {
|
| 104 |
-
// Proceed to step 2 only if they are NOT equal
|
| 105 |
-
setTimeout(() => {
|
| 106 |
-
setGameStep('step2');
|
| 107 |
-
setShowFeedback(false);
|
| 108 |
-
setFeedback('');
|
| 109 |
-
}, 1500);
|
| 110 |
}
|
| 111 |
-
} else {
|
| 112 |
-
setFeedback('Try again! 🤔');
|
| 113 |
-
setShowFeedback(true);
|
| 114 |
-
setTimeout(() => {
|
| 115 |
-
setShowFeedback(false);
|
| 116 |
-
setFeedback('');
|
| 117 |
-
}, 1000);
|
| 118 |
}
|
| 119 |
-
};
|
| 120 |
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
if
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
if (
|
| 130 |
-
|
| 131 |
-
setCurrentQuestion(generateQuestion());
|
| 132 |
-
setGameStep('step1');
|
| 133 |
-
return;
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
let isCorrect = false;
|
| 137 |
-
|
| 138 |
-
if (currentQuestion.secondStepQuestion === 'more') {
|
| 139 |
-
if (leftCount > rightCount && answer === 'left') isCorrect = true;
|
| 140 |
-
if (rightCount > leftCount && answer === 'right') isCorrect = true;
|
| 141 |
-
} else { // fewer
|
| 142 |
-
if (leftCount < rightCount && answer === 'left') isCorrect = true;
|
| 143 |
-
if (rightCount < leftCount && answer === 'right') isCorrect = true;
|
| 144 |
}
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
setTimeout(() => {
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
setFeedback('');
|
| 157 |
-
}, 2000);
|
| 158 |
-
} else {
|
| 159 |
setFeedback('Try again! 🤔');
|
| 160 |
-
setShowFeedback(true);
|
| 161 |
setTimeout(() => {
|
| 162 |
-
setShowFeedback(false);
|
| 163 |
setFeedback('');
|
| 164 |
}, 1000);
|
| 165 |
}
|
| 166 |
};
|
| 167 |
|
| 168 |
-
// Render emoji grid
|
| 169 |
-
const renderEmojiSide = (emojis: string[], side: 'left' | 'right') => {
|
| 170 |
-
const gridClass = `emoji-grid ${side}`;
|
| 171 |
-
return (
|
| 172 |
-
<div className={gridClass}>
|
| 173 |
-
{emojis.map((emoji, index) => (
|
| 174 |
-
<span key={index} className="emoji-item">
|
| 175 |
-
{emoji}
|
| 176 |
-
</span>
|
| 177 |
-
))}
|
| 178 |
-
</div>
|
| 179 |
-
);
|
| 180 |
-
};
|
| 181 |
-
|
| 182 |
if (!currentQuestion) {
|
| 183 |
-
return <div className="loading">Loading...</div>;
|
| 184 |
}
|
| 185 |
|
|
|
|
|
|
|
|
|
|
| 186 |
return (
|
| 187 |
-
<div className="
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
<
|
| 192 |
-
|
| 193 |
-
<
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
<div className="
|
| 204 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
</div>
|
| 206 |
-
|
| 207 |
-
{
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
<
|
| 216 |
-
className="
|
| 217 |
-
onClick={() => handleStep1Answer('not-equal')}
|
| 218 |
-
>
|
| 219 |
-
Not Equal
|
| 220 |
-
</button>
|
| 221 |
-
</div>
|
| 222 |
-
)}
|
| 223 |
-
</>
|
| 224 |
-
) : (
|
| 225 |
-
// Step 2: Which side has more/fewer? (Only if sides are NOT equal)
|
| 226 |
-
currentQuestion.leftSide.length !== currentQuestion.rightSide.length ? (
|
| 227 |
-
<>
|
| 228 |
-
<h1 className="question-title">
|
| 229 |
-
Which side has {currentQuestion.secondStepQuestion}?
|
| 230 |
-
</h1>
|
| 231 |
-
|
| 232 |
-
<div className="comparison-container">
|
| 233 |
-
<div className="side-container clickable" onClick={() => handleStep2Answer('left')}>
|
| 234 |
-
{renderEmojiSide(currentQuestion.leftSide, 'left')}
|
| 235 |
-
<div className="count-display">{currentQuestion.leftSide.length}</div>
|
| 236 |
</div>
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
</div>
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
{feedback}
|
| 259 |
</div>
|
| 260 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
</div>
|
| 262 |
);
|
| 263 |
}
|
|
|
|
| 1 |
import { useState, useEffect } from 'react'
|
| 2 |
import './App.css'
|
| 3 |
|
| 4 |
+
// Define the types for the game
|
| 5 |
+
type DisplayMode = 'numbers' | 'objects' | 'both';
|
| 6 |
+
|
| 7 |
+
interface AdditionQuestion {
|
| 8 |
+
num1: number;
|
| 9 |
+
num2: number;
|
| 10 |
+
answer: number;
|
| 11 |
+
options: number[];
|
| 12 |
+
object1: string;
|
| 13 |
+
object2: string;
|
| 14 |
}
|
| 15 |
|
| 16 |
+
// Emoji pool
|
| 17 |
+
const emojis = ['🍎', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🫐', '🍒', '🥝', '🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐸', '🐷', '⚽', '🏀', '🎾', '🏈', '🎱', '🎨', '🎭', '🎯', '🚗', '🚕', '🚓', '🚑', '🚁', '🚀'];
|
| 18 |
|
| 19 |
function App() {
|
| 20 |
+
const [currentQuestion, setCurrentQuestion] = useState<AdditionQuestion | null>(null);
|
| 21 |
+
const [displayMode, setDisplayMode] = useState<DisplayMode>('numbers');
|
| 22 |
const [feedback, setFeedback] = useState('');
|
| 23 |
+
const [score, setScore] = useState(0);
|
| 24 |
|
| 25 |
+
// Generate a random number between min and max (inclusive)
|
| 26 |
+
const getRandomNumber = (min: number, max: number) => {
|
| 27 |
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
};
|
| 29 |
|
| 30 |
+
// Helper function to get an operand (0-5) with a reduced chance of it being 0
|
| 31 |
+
const getOperandWithReducedZero = (maxValue: number): number => {
|
| 32 |
+
const CHANCE_OF_BEING_ZERO = 0.10; // 10% chance the operand is 0
|
| 33 |
+
|
| 34 |
+
if (maxValue < 0) maxValue = 0; // Ensure maxValue is not negative
|
| 35 |
+
if (maxValue === 0) return 0; // If max is 0, operand must be 0
|
| 36 |
+
|
| 37 |
+
if (Math.random() < CHANCE_OF_BEING_ZERO) {
|
| 38 |
+
return 0;
|
| 39 |
+
} else {
|
| 40 |
+
// Generate a number from 1 to maxValue
|
| 41 |
+
return getRandomNumber(1, maxValue);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
};
|
| 44 |
|
| 45 |
+
// Get a random emoji
|
| 46 |
+
const getRandomEmoji = () => {
|
| 47 |
+
return emojis[Math.floor(Math.random() * emojis.length)];
|
| 48 |
+
};
|
| 49 |
|
| 50 |
+
// Generate a new question
|
| 51 |
+
const generateQuestion = () => {
|
| 52 |
+
let num1, num2;
|
| 53 |
+
const MAX_OPERAND_VALUE = 5;
|
| 54 |
+
const MAX_SUM = 5;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
+
do {
|
| 57 |
+
num1 = getOperandWithReducedZero(MAX_OPERAND_VALUE);
|
| 58 |
+
num2 = getOperandWithReducedZero(MAX_OPERAND_VALUE);
|
| 59 |
+
} while (num1 + num2 > MAX_SUM);
|
| 60 |
+
|
| 61 |
+
const answer = num1 + num2;
|
| 62 |
+
|
| 63 |
+
// Generate options, one of which is the correct answer
|
| 64 |
+
const options = new Set<number>();
|
| 65 |
+
options.add(answer);
|
| 66 |
+
while (options.size < 3) {
|
| 67 |
+
const option = getRandomNumber(0, 10); // Options can be up to 10
|
| 68 |
+
if (option !== answer) { // Ensure options are not trivially easy if sum is 0 or 1
|
| 69 |
+
options.add(option);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
}
|
|
|
|
| 72 |
|
| 73 |
+
const object1 = getRandomEmoji();
|
| 74 |
+
let object2 = getRandomEmoji();
|
| 75 |
+
// Ensure objects are different if numbers are different, or if it's the same number, it's fine.
|
| 76 |
+
// This logic primarily ensures visual distinction when counts are different.
|
| 77 |
+
if (num1 > 0 && num2 > 0 && num1 !== num2) { // only ensure different emojis if counts are different and non-zero
|
| 78 |
+
while (object2 === object1) {
|
| 79 |
+
object2 = getRandomEmoji();
|
| 80 |
+
}
|
| 81 |
+
} else if (num1 > 0 && num2 > 0 && num1 === num2) { // if numbers are same and non-zero, make sure emojis are same for clarity
|
| 82 |
+
object2 = object1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
}
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
setCurrentQuestion({
|
| 87 |
+
num1,
|
| 88 |
+
num2,
|
| 89 |
+
answer,
|
| 90 |
+
options: Array.from(options).sort(() => Math.random() - 0.5), // Shuffle options
|
| 91 |
+
object1,
|
| 92 |
+
object2,
|
| 93 |
+
});
|
| 94 |
+
setFeedback('');
|
| 95 |
+
};
|
| 96 |
+
|
| 97 |
+
useEffect(() => {
|
| 98 |
+
generateQuestion();
|
| 99 |
+
}, []);
|
| 100 |
+
|
| 101 |
+
// Handle answer submission
|
| 102 |
+
const handleAnswer = (selectedOption: number) => {
|
| 103 |
+
if (currentQuestion && selectedOption === currentQuestion.answer) {
|
| 104 |
+
setFeedback('Correct! 🎉');
|
| 105 |
+
setScore(score + 1);
|
| 106 |
setTimeout(() => {
|
| 107 |
+
generateQuestion();
|
| 108 |
+
}, 1500);
|
| 109 |
+
} else if (currentQuestion) { // Added currentQuestion check here
|
|
|
|
|
|
|
|
|
|
| 110 |
setFeedback('Try again! 🤔');
|
|
|
|
| 111 |
setTimeout(() => {
|
|
|
|
| 112 |
setFeedback('');
|
| 113 |
}, 1000);
|
| 114 |
}
|
| 115 |
};
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
if (!currentQuestion) {
|
| 118 |
+
return <div className="loading">Loading game...</div>;
|
| 119 |
}
|
| 120 |
|
| 121 |
+
const { num1, num2, answer, options, object1, object2 } = currentQuestion;
|
| 122 |
+
// const sumObject = object1; // No longer needed as sum is not displayed with objects here
|
| 123 |
+
|
| 124 |
return (
|
| 125 |
+
<div className="app-container">
|
| 126 |
+
<header className="game-header">
|
| 127 |
+
<h1>Addition Game</h1>
|
| 128 |
+
<div className="display-mode-selector">
|
| 129 |
+
<button onClick={() => setDisplayMode('numbers')} className={displayMode === 'numbers' ? 'active' : ''}>Number</button>
|
| 130 |
+
<button onClick={() => setDisplayMode('objects')} className={displayMode === 'objects' ? 'active' : ''}>Object</button>
|
| 131 |
+
<button onClick={() => setDisplayMode('both')} className={displayMode === 'both' ? 'active' : ''}>Both</button>
|
| 132 |
+
</div>
|
| 133 |
+
<div className="score-display">Score: {score}</div>
|
| 134 |
+
</header>
|
| 135 |
+
|
| 136 |
+
<main className="game-area">
|
| 137 |
+
<div className="question-container-new">
|
| 138 |
+
{/* Operand 1 Column */}
|
| 139 |
+
<div className="operand-column">
|
| 140 |
+
{(displayMode === 'numbers' || displayMode === 'both') && (
|
| 141 |
+
<div className="number-box">
|
| 142 |
+
<span className="number-text">{num1}</span>
|
| 143 |
+
</div>
|
| 144 |
+
)}
|
| 145 |
+
{(displayMode === 'objects' || displayMode === 'both') && (
|
| 146 |
+
<div className="objects-display">
|
| 147 |
+
{Array(num1).fill(null).map((_, i) => (
|
| 148 |
+
<span key={`obj1-${i}`} className="item-object">{object1}</span>
|
| 149 |
+
))}
|
| 150 |
+
</div>
|
| 151 |
+
)}
|
| 152 |
+
{/* If only objects mode and num1 is 0, show nothing or a placeholder if desired*/}
|
| 153 |
+
{displayMode === 'objects' && num1 === 0 && <div className="objects-display" style={{minHeight: '4rem'}}></div>}
|
| 154 |
</div>
|
| 155 |
+
|
| 156 |
+
{/* Operator + Column */}
|
| 157 |
+
<div className="operator-cell">
|
| 158 |
+
<span className="operator-symbol">+</span>
|
| 159 |
+
</div>
|
| 160 |
+
|
| 161 |
+
{/* Operand 2 Column */}
|
| 162 |
+
<div className="operand-column">
|
| 163 |
+
{(displayMode === 'numbers' || displayMode === 'both') && (
|
| 164 |
+
<div className="number-box">
|
| 165 |
+
<span className="number-text">{num2}</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
</div>
|
| 167 |
+
)}
|
| 168 |
+
{(displayMode === 'objects' || displayMode === 'both') && (
|
| 169 |
+
<div className="objects-display">
|
| 170 |
+
{Array(num2).fill(null).map((_, i) => (
|
| 171 |
+
<span key={`obj2-${i}`} className="item-object">{object2}</span>
|
| 172 |
+
))}
|
| 173 |
</div>
|
| 174 |
+
)}
|
| 175 |
+
{displayMode === 'objects' && num2 === 0 && <div className="objects-display" style={{minHeight: '4rem'}}></div>}
|
| 176 |
+
</div>
|
| 177 |
+
|
| 178 |
+
{/* Operator = Column */}
|
| 179 |
+
<div className="operator-cell">
|
| 180 |
+
<span className="operator-symbol">=</span>
|
| 181 |
+
</div>
|
| 182 |
+
|
| 183 |
+
{/* Result Column - Now displays a question mark */}
|
| 184 |
+
<div className="result-column">
|
| 185 |
+
<span className="answer-placeholder-new">?</span>
|
| 186 |
+
</div>
|
| 187 |
+
|
|
|
|
| 188 |
</div>
|
| 189 |
+
|
| 190 |
+
<div className="options-container">
|
| 191 |
+
{options.map((option) => (
|
| 192 |
+
<button key={option} onClick={() => handleAnswer(option)} className="option-button">
|
| 193 |
+
{option}
|
| 194 |
+
</button>
|
| 195 |
+
))}
|
| 196 |
+
</div>
|
| 197 |
+
|
| 198 |
+
{feedback && <div className="feedback-message">{feedback}</div>}
|
| 199 |
+
</main>
|
| 200 |
</div>
|
| 201 |
);
|
| 202 |
}
|