Spaces:
Sleeping
Sleeping
equal step 2 game
Browse files- Dockerfile +2 -2
- README.md +163 -157
- index.html +1 -1
- package-lock.json +4 -4
- package.json +2 -2
- src/App.css +224 -90
- src/App.tsx +227 -252
Dockerfile
CHANGED
@@ -23,5 +23,5 @@ RUN npm install -g serve
|
|
23 |
COPY --from=build /app/dist ./dist
|
24 |
|
25 |
# Expose port and start server
|
26 |
-
EXPOSE
|
27 |
-
CMD ["serve", "-s", "dist", "-l", "
|
|
|
23 |
COPY --from=build /app/dist ./dist
|
24 |
|
25 |
# Expose port and start server
|
26 |
+
EXPOSE 7860
|
27 |
+
CMD ["serve", "-s", "dist", "-l", "7860"]
|
README.md
CHANGED
@@ -1,185 +1,191 @@
|
|
1 |
---
|
2 |
-
title: Equal
|
3 |
-
emoji:
|
4 |
colorFrom: blue
|
5 |
-
colorTo:
|
6 |
sdk: docker
|
7 |
pinned: false
|
8 |
-
app_port:
|
9 |
---
|
10 |
-
# Colorful Semantics - To Whom? Game
|
11 |
|
12 |
-
|
13 |
|
14 |
-
|
15 |
|
16 |
-
|
17 |
-
- Visual and animated feedback for correct answers
|
18 |
-
- Support for both images and videos
|
19 |
-
- Customizable questions and answers
|
20 |
|
21 |
-
|
22 |
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
|
|
27 |
|
28 |
-
|
|
|
|
|
|
|
|
|
|
|
29 |
|
30 |
-
|
31 |
-
|
|
|
32 |
|
33 |
-
|
34 |
-
{
|
35 |
-
"file": "media/your_image.jpg",
|
36 |
-
"who": "The boy",
|
37 |
-
"doing": "is giving",
|
38 |
-
"what": "a book",
|
39 |
-
"to_whom": "to the teacher",
|
40 |
-
"distractors": ["to Mom", "to the dog", "to Grandma"]
|
41 |
-
}
|
42 |
-
```
|
43 |
|
44 |
-
|
|
|
|
|
|
|
|
|
|
|
45 |
|
46 |
-
|
47 |
-
- `who`: The subject of the sentence
|
48 |
-
- `doing`: The verb phrase
|
49 |
-
- `what`: The object
|
50 |
-
- `to_whom`: The correct answer (recipient)
|
51 |
-
- `distractors`: Array of incorrect options
|
52 |
-
4. Restart the application to see your new questions
|
53 |
|
54 |
-
|
55 |
|
56 |
```bash
|
|
|
|
|
|
|
|
|
57 |
# Install dependencies
|
58 |
npm install
|
59 |
|
60 |
# Start development server
|
61 |
npm run dev
|
|
|
62 |
|
|
|
|
|
|
|
|
|
|
|
63 |
# Build for production
|
64 |
npm run build
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
```
|
66 |
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
fly
|
136 |
-
eat
|
137 |
-
dry
|
138 |
-
play
|
139 |
-
climb
|
140 |
-
scratch
|
141 |
-
cut
|
142 |
-
brush
|
143 |
-
bounce
|
144 |
-
argue
|
145 |
-
work
|
146 |
-
fight
|
147 |
-
practice
|
148 |
-
throw
|
149 |
-
take
|
150 |
-
bring
|
151 |
-
take
|
152 |
-
wipe
|
153 |
-
drink
|
154 |
-
|
155 |
-
## what
|
156 |
-
|
157 |
-
flower
|
158 |
-
icecream
|
159 |
-
pen
|
160 |
-
pencil
|
161 |
-
socks
|
162 |
-
underwear
|
163 |
-
coat
|
164 |
-
wellies
|
165 |
-
shoes
|
166 |
-
spoon
|
167 |
-
fork
|
168 |
-
food
|
169 |
-
bread
|
170 |
-
butter
|
171 |
-
salt
|
172 |
-
chilly
|
173 |
-
bathroom
|
174 |
-
towel
|
175 |
-
coffee
|
176 |
-
tea
|
177 |
-
milk
|
178 |
-
almond
|
179 |
-
monkey nuts
|
180 |
-
upstairs
|
181 |
-
downstairs
|
182 |
-
|
183 |
-
## License
|
184 |
-
|
185 |
-
MIT
|
|
|
1 |
---
|
2 |
+
title: Equal 2-Step Game
|
3 |
+
emoji: ⚖️
|
4 |
colorFrom: blue
|
5 |
+
colorTo: purple
|
6 |
sdk: docker
|
7 |
pinned: false
|
8 |
+
app_port: 7860
|
9 |
---
|
|
|
10 |
|
11 |
+
# Equal 2-Step Game ⚖️
|
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 consists of **two sequential steps** that help children develop comparison and counting skills:
|
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 |
+
### Game Flow
|
33 |
+
1. **Equal sides**: Step 1 → Correct answer → New question
|
34 |
+
2. **Unequal sides**: Step 1 → Step 2 → Correct answer → New question
|
35 |
|
36 |
+
## ✨ Features
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
|
38 |
+
- **Visual Learning**: Uses colorful emojis from different categories (animals, fruits, objects, hearts, stars)
|
39 |
+
- **Two-Step Logic**: Teaches both equality and comparison concepts
|
40 |
+
- **Immediate Feedback**: Shows "Correct! 🎉" or "Try again! 🤔" messages
|
41 |
+
- **Auto-Generated Questions**: Random emoji selection and counts (1-10 items)
|
42 |
+
- **Responsive Design**: Works on desktop, tablet, and mobile devices
|
43 |
+
- **Clean UI**: Simple, child-friendly interface with large buttons and clear visuals
|
44 |
|
45 |
+
## 🚀 How to Run
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
47 |
+
### Option 1: Development Mode
|
48 |
|
49 |
```bash
|
50 |
+
# Clone the repository
|
51 |
+
git clone <repository-url>
|
52 |
+
cd equal-2-step-game
|
53 |
+
|
54 |
# Install dependencies
|
55 |
npm install
|
56 |
|
57 |
# Start development server
|
58 |
npm run dev
|
59 |
+
```
|
60 |
|
61 |
+
The game will be available at `http://localhost:5173`
|
62 |
+
|
63 |
+
### Option 2: Production Build
|
64 |
+
|
65 |
+
```bash
|
66 |
# Build for production
|
67 |
npm run build
|
68 |
+
|
69 |
+
# Preview production build
|
70 |
+
npm run preview
|
71 |
+
```
|
72 |
+
|
73 |
+
### Option 3: Docker
|
74 |
+
|
75 |
+
```bash
|
76 |
+
# Build Docker image
|
77 |
+
docker build -t equal-2-step-game .
|
78 |
+
|
79 |
+
# Run container
|
80 |
+
docker run -p 3000:3000 equal-2-step-game
|
81 |
+
```
|
82 |
+
|
83 |
+
The game will be available at `http://localhost:3000`
|
84 |
+
|
85 |
+
## 🎯 Educational Benefits
|
86 |
+
|
87 |
+
- **Counting Skills**: Children practice counting objects up to 10
|
88 |
+
- **Equality Concepts**: Understanding when quantities are the same
|
89 |
+
- **Comparison Skills**: Learning "more than" and "fewer than" concepts
|
90 |
+
- **Visual Recognition**: Identifying and categorizing different emoji types
|
91 |
+
- **Logical Thinking**: Following two-step problem-solving processes
|
92 |
+
|
93 |
+
## 🎨 Game Categories
|
94 |
+
|
95 |
+
The game uses emojis from 5 different categories:
|
96 |
+
|
97 |
+
- **Animals**: 🐶 🐱 🐭 🐹 🐰 🦊 🐻 🐼 🐸 🐷
|
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 |
+
### Project Structure
|
112 |
+
```
|
113 |
+
src/
|
114 |
+
├── App.tsx # Main game component
|
115 |
+
├── App.css # Game styling
|
116 |
+
├── main.tsx # App entry point
|
117 |
+
└── index.css # Global styles
|
118 |
+
|
119 |
+
public/
|
120 |
+
├── favicon.svg # App icon
|
121 |
+
└── index.html # HTML template
|
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 fully responsive and optimized for:
|
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 |
+
1. Fork the repository
|
166 |
+
2. Create a feature branch: `git checkout -b feature-name`
|
167 |
+
3. Make your changes
|
168 |
+
4. Test thoroughly
|
169 |
+
5. Submit a pull request
|
170 |
+
|
171 |
+
## 📄 License
|
172 |
+
|
173 |
+
MIT License - feel free to use this project for educational purposes!
|
174 |
+
|
175 |
+
## 🎓 Age Recommendation
|
176 |
+
|
177 |
+
This game is designed for children ages **3-8** who are learning:
|
178 |
+
- Basic counting (1-10)
|
179 |
+
- Equality concepts
|
180 |
+
- Comparison skills
|
181 |
+
- Visual recognition
|
182 |
+
|
183 |
+
Perfect for:
|
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!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
index.html
CHANGED
@@ -4,7 +4,7 @@
|
|
4 |
<meta charset="UTF-8" />
|
5 |
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7 |
-
<title>
|
8 |
</head>
|
9 |
<body>
|
10 |
<div id="root"></div>
|
|
|
4 |
<meta charset="UTF-8" />
|
5 |
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7 |
+
<title>Equal 2-Step Game</title>
|
8 |
</head>
|
9 |
<body>
|
10 |
<div id="root"></div>
|
package-lock.json
CHANGED
@@ -1,12 +1,12 @@
|
|
1 |
{
|
2 |
-
"name": "
|
3 |
-
"version": "
|
4 |
"lockfileVersion": 3,
|
5 |
"requires": true,
|
6 |
"packages": {
|
7 |
"": {
|
8 |
-
"name": "
|
9 |
-
"version": "
|
10 |
"dependencies": {
|
11 |
"react": "^19.0.0",
|
12 |
"react-dom": "^19.0.0"
|
|
|
1 |
{
|
2 |
+
"name": "equal-2-step-game",
|
3 |
+
"version": "1.0.0",
|
4 |
"lockfileVersion": 3,
|
5 |
"requires": true,
|
6 |
"packages": {
|
7 |
"": {
|
8 |
+
"name": "equal-2-step-game",
|
9 |
+
"version": "1.0.0",
|
10 |
"dependencies": {
|
11 |
"react": "^19.0.0",
|
12 |
"react-dom": "^19.0.0"
|
package.json
CHANGED
@@ -1,9 +1,9 @@
|
|
1 |
{
|
2 |
-
"name": "
|
3 |
"private": true,
|
4 |
"version": "1.0.0",
|
5 |
"type": "module",
|
6 |
-
"description": "
|
7 |
"scripts": {
|
8 |
"dev": "vite",
|
9 |
"build": "vite build",
|
|
|
1 |
{
|
2 |
+
"name": "equal-2-step-game",
|
3 |
"private": true,
|
4 |
"version": "1.0.0",
|
5 |
"type": "module",
|
6 |
+
"description": "A 2-step educational game for teaching equality comparison using emojis",
|
7 |
"scripts": {
|
8 |
"dev": "vite",
|
9 |
"build": "vite build",
|
src/App.css
CHANGED
@@ -1,15 +1,16 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
padding: 2rem;
|
5 |
-
text-align: center;
|
6 |
-
font-family: 'Arial', sans-serif;
|
7 |
display: flex;
|
8 |
flex-direction: column;
|
9 |
align-items: center;
|
10 |
-
justify-content:
|
|
|
|
|
|
|
11 |
}
|
12 |
|
|
|
13 |
.loading {
|
14 |
display: flex;
|
15 |
justify-content: center;
|
@@ -17,132 +18,265 @@
|
|
17 |
height: 100vh;
|
18 |
font-size: 1.5rem;
|
19 |
font-weight: bold;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
}
|
21 |
|
22 |
-
|
23 |
-
|
24 |
-
width: 100%;
|
25 |
-
max-width: 900px;
|
26 |
-
height: 55vh;
|
27 |
-
max-height: 500px;
|
28 |
-
margin: 0 auto 3rem;
|
29 |
display: flex;
|
|
|
30 |
justify-content: center;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
align-items: center;
|
32 |
-
|
33 |
-
|
34 |
-
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.25);
|
35 |
-
background-color: #f0f0f0;
|
36 |
}
|
37 |
|
38 |
-
.
|
39 |
-
|
40 |
-
max-height: 100%;
|
41 |
-
object-fit: contain;
|
42 |
}
|
43 |
|
44 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
display: flex;
|
46 |
-
|
47 |
justify-content: center;
|
48 |
-
|
49 |
-
margin-bottom: 2rem;
|
50 |
}
|
51 |
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
min-width: 140px;
|
56 |
-
padding: 1.4rem 1.6rem;
|
57 |
-
border-radius: 10px;
|
58 |
-
border: 3px solid #000;
|
59 |
-
font-size: 1.6rem;
|
60 |
font-weight: bold;
|
61 |
-
|
|
|
|
|
|
|
|
|
62 |
display: flex;
|
63 |
-
justify-content: center;
|
64 |
align-items: center;
|
65 |
-
|
66 |
-
text-
|
67 |
-
white-space: nowrap; /* prevent text from wrapping to a new line */
|
68 |
}
|
69 |
|
70 |
-
|
|
|
71 |
display: flex;
|
72 |
-
gap:
|
73 |
justify-content: center;
|
74 |
flex-wrap: wrap;
|
75 |
-
margin-top: 2rem;
|
76 |
}
|
77 |
|
78 |
-
.
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
cursor: pointer;
|
85 |
-
transition:
|
86 |
-
min-width:
|
87 |
-
box-shadow: 0
|
88 |
}
|
89 |
|
90 |
-
.
|
91 |
-
|
92 |
-
|
93 |
-
}
|
94 |
-
|
95 |
-
.flying-option {
|
96 |
-
padding: 0.8rem 1.2rem;
|
97 |
-
border: 2px solid #000;
|
98 |
-
border-radius: 6px;
|
99 |
-
font-size: 1.1rem;
|
100 |
-
pointer-events: none;
|
101 |
-
z-index: 100;
|
102 |
-
min-width: 120px;
|
103 |
-
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
104 |
-
white-space: nowrap;
|
105 |
-
display: flex;
|
106 |
-
justify-content: center;
|
107 |
-
align-items: center;
|
108 |
}
|
109 |
|
110 |
-
.
|
111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
font-size: 1.2rem;
|
113 |
-
|
114 |
-
|
115 |
-
|
|
|
|
|
|
|
|
|
116 |
}
|
117 |
|
118 |
-
|
|
|
119 |
position: fixed;
|
120 |
-
|
121 |
-
|
122 |
-
|
|
|
|
|
|
|
123 |
font-weight: bold;
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
z-index:
|
|
|
128 |
}
|
129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
@media (max-width: 768px) {
|
131 |
-
.
|
132 |
-
|
133 |
-
|
134 |
}
|
135 |
|
136 |
-
.
|
137 |
-
|
|
|
138 |
}
|
139 |
|
140 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
141 |
flex-direction: column;
|
142 |
align-items: center;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
143 |
}
|
144 |
|
145 |
-
.
|
146 |
-
|
147 |
}
|
148 |
}
|
|
|
1 |
+
/* Main container */
|
2 |
+
.equal-game {
|
3 |
+
min-height: 100vh;
|
|
|
|
|
|
|
4 |
display: flex;
|
5 |
flex-direction: column;
|
6 |
align-items: center;
|
7 |
+
justify-content: center;
|
8 |
+
padding: 2rem;
|
9 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
10 |
+
font-family: 'Arial', sans-serif;
|
11 |
}
|
12 |
|
13 |
+
/* Loading state */
|
14 |
.loading {
|
15 |
display: flex;
|
16 |
justify-content: center;
|
|
|
18 |
height: 100vh;
|
19 |
font-size: 1.5rem;
|
20 |
font-weight: bold;
|
21 |
+
color: white;
|
22 |
+
}
|
23 |
+
|
24 |
+
/* Question title */
|
25 |
+
.question-title {
|
26 |
+
font-size: 3rem;
|
27 |
+
font-weight: bold;
|
28 |
+
color: white;
|
29 |
+
text-align: center;
|
30 |
+
margin-bottom: 2rem;
|
31 |
+
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
32 |
}
|
33 |
|
34 |
+
/* Comparison container */
|
35 |
+
.comparison-container {
|
|
|
|
|
|
|
|
|
|
|
36 |
display: flex;
|
37 |
+
align-items: center;
|
38 |
justify-content: center;
|
39 |
+
gap: 3rem;
|
40 |
+
margin-bottom: 3rem;
|
41 |
+
flex-wrap: wrap;
|
42 |
+
}
|
43 |
+
|
44 |
+
/* Side container */
|
45 |
+
.side-container {
|
46 |
+
background: white;
|
47 |
+
border-radius: 20px;
|
48 |
+
padding: 2rem;
|
49 |
+
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
50 |
+
min-width: 300px;
|
51 |
+
max-width: 400px;
|
52 |
+
display: flex;
|
53 |
+
flex-direction: column;
|
54 |
align-items: center;
|
55 |
+
gap: 1rem;
|
56 |
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
|
|
|
57 |
}
|
58 |
|
59 |
+
.side-container.clickable {
|
60 |
+
cursor: pointer;
|
|
|
|
|
61 |
}
|
62 |
|
63 |
+
.side-container.clickable:hover {
|
64 |
+
transform: scale(1.05);
|
65 |
+
box-shadow: 0 12px 35px rgba(0,0,0,0.2);
|
66 |
+
}
|
67 |
+
|
68 |
+
/* Emoji grid */
|
69 |
+
.emoji-grid {
|
70 |
+
display: grid;
|
71 |
+
grid-template-columns: repeat(auto-fit, minmax(40px, 1fr));
|
72 |
+
gap: 8px;
|
73 |
+
max-width: 300px;
|
74 |
+
justify-items: center;
|
75 |
+
}
|
76 |
+
|
77 |
+
.emoji-item {
|
78 |
+
font-size: 2rem;
|
79 |
+
display: block;
|
80 |
+
animation: popIn 0.3s ease-out;
|
81 |
+
}
|
82 |
+
|
83 |
+
@keyframes popIn {
|
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 |
+
/* Count display */
|
96 |
+
.count-display {
|
97 |
+
font-size: 2.5rem;
|
98 |
+
font-weight: bold;
|
99 |
+
color: #333;
|
100 |
+
background: #f0f0f0;
|
101 |
+
border-radius: 50%;
|
102 |
+
width: 80px;
|
103 |
+
height: 80px;
|
104 |
display: flex;
|
105 |
+
align-items: center;
|
106 |
justify-content: center;
|
107 |
+
border: 3px solid #ddd;
|
|
|
108 |
}
|
109 |
|
110 |
+
/* VS divider */
|
111 |
+
.vs-divider {
|
112 |
+
font-size: 2rem;
|
|
|
|
|
|
|
|
|
|
|
113 |
font-weight: bold;
|
114 |
+
color: white;
|
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 |
+
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
|
|
|
123 |
}
|
124 |
|
125 |
+
/* Answer buttons for step 1 */
|
126 |
+
.answer-buttons {
|
127 |
display: flex;
|
128 |
+
gap: 2rem;
|
129 |
justify-content: center;
|
130 |
flex-wrap: wrap;
|
|
|
131 |
}
|
132 |
|
133 |
+
.answer-btn {
|
134 |
+
padding: 1.5rem 3rem;
|
135 |
+
font-size: 1.5rem;
|
136 |
+
font-weight: bold;
|
137 |
+
border: none;
|
138 |
+
border-radius: 15px;
|
139 |
cursor: pointer;
|
140 |
+
transition: all 0.3s ease;
|
141 |
+
min-width: 150px;
|
142 |
+
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
143 |
}
|
144 |
|
145 |
+
.equal-btn {
|
146 |
+
background: linear-gradient(45deg, #4CAF50, #45a049);
|
147 |
+
color: white;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
148 |
}
|
149 |
|
150 |
+
.equal-btn:hover {
|
151 |
+
background: linear-gradient(45deg, #45a049, #4CAF50);
|
152 |
+
transform: translateY(-2px);
|
153 |
+
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
|
154 |
+
}
|
155 |
+
|
156 |
+
.not-equal-btn {
|
157 |
+
background: linear-gradient(45deg, #f44336, #d32f2f);
|
158 |
+
color: white;
|
159 |
+
}
|
160 |
+
|
161 |
+
.not-equal-btn:hover {
|
162 |
+
background: linear-gradient(45deg, #d32f2f, #f44336);
|
163 |
+
transform: translateY(-2px);
|
164 |
+
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
|
165 |
+
}
|
166 |
+
|
167 |
+
/* Step 2 instruction */
|
168 |
+
.step2-instruction {
|
169 |
font-size: 1.2rem;
|
170 |
+
color: white;
|
171 |
+
text-align: center;
|
172 |
+
margin-top: 1rem;
|
173 |
+
background: rgba(255,255,255,0.1);
|
174 |
+
padding: 1rem 2rem;
|
175 |
+
border-radius: 10px;
|
176 |
+
backdrop-filter: blur(10px);
|
177 |
}
|
178 |
|
179 |
+
/* Feedback message */
|
180 |
+
.feedback-message {
|
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 |
+
@keyframes feedbackPop {
|
197 |
+
0% {
|
198 |
+
transform: translate(-50%, -50%) scale(0);
|
199 |
+
}
|
200 |
+
80% {
|
201 |
+
transform: translate(-50%, -50%) scale(1.1);
|
202 |
+
}
|
203 |
+
100% {
|
204 |
+
transform: translate(-50%, -50%) scale(1);
|
205 |
+
}
|
206 |
+
}
|
207 |
+
|
208 |
+
/* Responsive design */
|
209 |
@media (max-width: 768px) {
|
210 |
+
.question-title {
|
211 |
+
font-size: 2rem;
|
212 |
+
margin-bottom: 1.5rem;
|
213 |
}
|
214 |
|
215 |
+
.comparison-container {
|
216 |
+
gap: 2rem;
|
217 |
+
flex-direction: column;
|
218 |
}
|
219 |
|
220 |
+
.side-container {
|
221 |
+
min-width: 250px;
|
222 |
+
max-width: 300px;
|
223 |
+
padding: 1.5rem;
|
224 |
+
}
|
225 |
+
|
226 |
+
.vs-divider {
|
227 |
+
width: 60px;
|
228 |
+
height: 60px;
|
229 |
+
font-size: 1.5rem;
|
230 |
+
}
|
231 |
+
|
232 |
+
.emoji-item {
|
233 |
+
font-size: 1.5rem;
|
234 |
+
}
|
235 |
+
|
236 |
+
.count-display {
|
237 |
+
width: 60px;
|
238 |
+
height: 60px;
|
239 |
+
font-size: 2rem;
|
240 |
+
}
|
241 |
+
|
242 |
+
.answer-buttons {
|
243 |
flex-direction: column;
|
244 |
align-items: center;
|
245 |
+
gap: 1rem;
|
246 |
+
}
|
247 |
+
|
248 |
+
.answer-btn {
|
249 |
+
padding: 1rem 2rem;
|
250 |
+
font-size: 1.2rem;
|
251 |
+
min-width: 200px;
|
252 |
+
}
|
253 |
+
|
254 |
+
.feedback-message {
|
255 |
+
font-size: 1.5rem;
|
256 |
+
padding: 1.5rem 2rem;
|
257 |
+
}
|
258 |
+
}
|
259 |
+
|
260 |
+
@media (max-width: 480px) {
|
261 |
+
.equal-game {
|
262 |
+
padding: 1rem;
|
263 |
+
}
|
264 |
+
|
265 |
+
.question-title {
|
266 |
+
font-size: 1.5rem;
|
267 |
+
}
|
268 |
+
|
269 |
+
.side-container {
|
270 |
+
min-width: 200px;
|
271 |
+
max-width: 250px;
|
272 |
+
padding: 1rem;
|
273 |
+
}
|
274 |
+
|
275 |
+
.emoji-grid {
|
276 |
+
max-width: 200px;
|
277 |
}
|
278 |
|
279 |
+
.emoji-item {
|
280 |
+
font-size: 1.2rem;
|
281 |
}
|
282 |
}
|
src/App.tsx
CHANGED
@@ -1,286 +1,261 @@
|
|
1 |
-
import { useState, useEffect
|
2 |
import './App.css'
|
3 |
|
4 |
-
//
|
5 |
-
interface
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
to_whom: string;
|
11 |
-
distractors: string[];
|
12 |
}
|
13 |
|
14 |
-
//
|
15 |
-
|
16 |
-
frames: string[];
|
17 |
-
frameIndex: number;
|
18 |
-
who: string;
|
19 |
-
doing: string;
|
20 |
-
what: string;
|
21 |
-
answer: string;
|
22 |
-
choices: string[];
|
23 |
-
}
|
24 |
|
25 |
function App() {
|
26 |
-
const [
|
27 |
-
const [
|
28 |
-
const [
|
29 |
-
const [
|
30 |
-
|
31 |
-
|
32 |
-
const
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
const
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
// Load questions on mount
|
46 |
-
useEffect(() => {
|
47 |
-
const loadQuestions = async () => {
|
48 |
-
try {
|
49 |
-
const response = await fetch('/questions.json');
|
50 |
-
if (!response.ok) {
|
51 |
-
throw new Error('Failed to load questions');
|
52 |
-
}
|
53 |
-
const data: Question[] = await response.json();
|
54 |
-
// Shuffle questions
|
55 |
-
const shuffled = [...data].sort(() => Math.random() - 0.5);
|
56 |
-
setQuestions(shuffled);
|
57 |
-
} catch (error) {
|
58 |
-
console.error('Error loading questions:', error);
|
59 |
-
}
|
60 |
-
};
|
61 |
|
62 |
-
|
|
|
63 |
|
64 |
-
//
|
65 |
-
const
|
66 |
-
|
67 |
-
width: window.innerWidth,
|
68 |
-
height: window.innerHeight
|
69 |
-
});
|
70 |
-
};
|
71 |
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
// Prepare game state when questions load or change
|
77 |
-
useEffect(() => {
|
78 |
-
if (questions.length > 0) {
|
79 |
-
prepareRound(questions[currentQuestionIndex]);
|
80 |
}
|
81 |
-
}, [questions, currentQuestionIndex]);
|
82 |
-
|
83 |
-
// Handle video/image frame updates
|
84 |
-
useEffect(() => {
|
85 |
-
if (!gameState || !gameState.frames.length) return;
|
86 |
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
if (!prev) return prev;
|
91 |
-
const nextIndex = (prev.frameIndex + 1) % prev.frames.length;
|
92 |
-
return { ...prev, frameIndex: nextIndex };
|
93 |
-
});
|
94 |
-
}
|
95 |
-
}, 100); // Adjust frame rate as needed
|
96 |
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
useEffect(() => {
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
setIsFilled(false);
|
107 |
-
setFeedbackMsg('');
|
108 |
-
setAnimationElement(null);
|
109 |
-
}, 1000);
|
110 |
-
|
111 |
-
return () => clearTimeout(timer);
|
112 |
-
}
|
113 |
-
}, [isFilled, questions.length]);
|
114 |
-
|
115 |
-
// Animation effect
|
116 |
useEffect(() => {
|
117 |
-
if (
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
anim.style.transition = 'transform 500ms linear';
|
126 |
-
anim.style.transform = `translate(${targetPosition.x - animationElement.position.x}px, ${targetPosition.y - animationElement.position.y}px)`;
|
127 |
-
|
128 |
-
const handleAnimationEnd = () => {
|
129 |
-
setIsAnimating(false);
|
130 |
-
setIsFilled(true);
|
131 |
-
};
|
132 |
-
|
133 |
-
anim.addEventListener('transitionend', handleAnimationEnd);
|
134 |
-
return () => anim.removeEventListener('transitionend', handleAnimationEnd);
|
135 |
}
|
136 |
-
}, [
|
137 |
-
|
138 |
-
//
|
139 |
-
const
|
140 |
-
|
141 |
-
// Shuffle choices
|
142 |
-
const shuffledChoices = [...choices].sort(() => Math.random() - 0.5);
|
143 |
-
|
144 |
-
// Determine if media is image or video
|
145 |
-
const isVideo = question.file.endsWith('.mp4');
|
146 |
-
const frames = isVideo
|
147 |
-
? [question.file] // For simplicity, we'll just use the path for videos
|
148 |
-
: [question.file]; // Same for images
|
149 |
|
150 |
-
|
151 |
-
frames,
|
152 |
-
frameIndex: 0,
|
153 |
-
who: question.who,
|
154 |
-
doing: question.doing,
|
155 |
-
what: question.what,
|
156 |
-
answer: question.to_whom,
|
157 |
-
choices: shuffledChoices
|
158 |
-
});
|
159 |
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
164 |
};
|
165 |
-
|
166 |
-
// Handle
|
167 |
-
const
|
168 |
-
if (
|
169 |
|
170 |
-
const
|
171 |
-
const
|
172 |
-
x: rect.left + rect.width / 2,
|
173 |
-
y: rect.top + rect.height / 2
|
174 |
-
};
|
175 |
|
176 |
-
if
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
setIsAnimating(true);
|
183 |
-
setFeedbackMsg('');
|
184 |
-
} else {
|
185 |
-
// Wrong answer
|
186 |
-
setFeedbackMsg(wrongMessages[Math.floor(Math.random() * wrongMessages.length)]);
|
187 |
}
|
188 |
-
};
|
189 |
-
|
190 |
-
// Render current media (image or video)
|
191 |
-
const renderMedia = () => {
|
192 |
-
if (!gameState || !gameState.frames.length) return null;
|
193 |
|
194 |
-
|
195 |
-
const isVideo = currentFrame.endsWith('.mp4');
|
196 |
|
197 |
-
if (
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
} else {
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
);
|
215 |
}
|
216 |
};
|
217 |
-
|
218 |
-
|
219 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
220 |
}
|
221 |
-
|
222 |
return (
|
223 |
-
<div className="
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
<div className="semantic-box" style={{ backgroundColor: GREEN }}>
|
242 |
-
{gameState.what}
|
243 |
-
</div>
|
244 |
-
|
245 |
-
{/* To Whom - Pink */}
|
246 |
-
<div
|
247 |
-
className="semantic-box"
|
248 |
-
style={{ backgroundColor: PINK }}
|
249 |
-
ref={destinationRef}
|
250 |
-
>
|
251 |
-
{isFilled ? gameState.answer : "to whom?"}
|
252 |
-
</div>
|
253 |
-
</div>
|
254 |
-
|
255 |
-
<div className="options-container">
|
256 |
-
{!isFilled && gameState.choices.map((option, index) => (
|
257 |
-
<div
|
258 |
-
key={index}
|
259 |
-
className="option"
|
260 |
-
onClick={(e) => handleOptionClick(option, e)}
|
261 |
-
>
|
262 |
-
{option}
|
263 |
</div>
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
269 |
)}
|
270 |
|
271 |
-
{
|
272 |
-
<div
|
273 |
-
|
274 |
-
ref={animationRef}
|
275 |
-
style={{
|
276 |
-
backgroundColor: PINK,
|
277 |
-
position: 'fixed',
|
278 |
-
left: animationElement.position.x,
|
279 |
-
top: animationElement.position.y,
|
280 |
-
transform: 'translate(-50%, -50%)'
|
281 |
-
}}
|
282 |
-
>
|
283 |
-
{animationElement.content}
|
284 |
</div>
|
285 |
)}
|
286 |
</div>
|
|
|
1 |
+
import { useState, useEffect } from 'react'
|
2 |
import './App.css'
|
3 |
|
4 |
+
// Game question structure
|
5 |
+
interface ComparisonQuestion {
|
6 |
+
leftSide: string[];
|
7 |
+
rightSide: string[];
|
8 |
+
isEqual: boolean;
|
9 |
+
secondStepQuestion: 'more' | 'fewer';
|
|
|
|
|
10 |
}
|
11 |
|
12 |
+
// Game states
|
13 |
+
type GameStep = 'step1' | 'step2' | 'correct-step1' | 'correct-step2';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
function App() {
|
16 |
+
const [currentQuestion, setCurrentQuestion] = useState<ComparisonQuestion | null>(null);
|
17 |
+
const [gameStep, setGameStep] = useState<GameStep>('step1');
|
18 |
+
const [feedback, setFeedback] = useState('');
|
19 |
+
const [showFeedback, setShowFeedback] = useState(false);
|
20 |
+
|
21 |
+
// Emoji pools for different categories
|
22 |
+
const emojiCategories = {
|
23 |
+
animals: ['🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐸', '🐷'],
|
24 |
+
fruits: ['🍎', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🫐', '🍒', '🥝'],
|
25 |
+
objects: ['⚽', '🏀', '🎾', '🏈', '🎱', '🎪', '🎨', '🎭', '🎪', '🎯'],
|
26 |
+
hearts: ['💙', '💚', '💛', '💜', '🤍', '🖤', '🤎', '💗', '💖', '💕'],
|
27 |
+
stars: ['⭐', '🌟', '💫', '✨', '🌠', '⚡', '🔥', '❄️', '☀️', '🌙']
|
28 |
+
};
|
29 |
+
|
30 |
+
// Generate a random question
|
31 |
+
const generateQuestion = (): ComparisonQuestion => {
|
32 |
+
const categories = Object.keys(emojiCategories) as (keyof typeof emojiCategories)[];
|
33 |
+
const selectedCategory = categories[Math.floor(Math.random() * categories.length)];
|
34 |
+
const emojis = emojiCategories[selectedCategory];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
|
36 |
+
// Pick a random emoji for this question
|
37 |
+
const emoji = emojis[Math.floor(Math.random() * emojis.length)];
|
38 |
|
39 |
+
// Generate counts (1-10)
|
40 |
+
const leftCount = Math.floor(Math.random() * 10) + 1;
|
41 |
+
let rightCount = Math.floor(Math.random() * 10) + 1;
|
|
|
|
|
|
|
|
|
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 |
+
// Initialize with first question
|
65 |
useEffect(() => {
|
66 |
+
setCurrentQuestion(generateQuestion());
|
67 |
+
}, []);
|
68 |
+
|
69 |
+
// Safety effect: if we're in step 2 but sides are equal, generate new question
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
70 |
useEffect(() => {
|
71 |
+
if (currentQuestion &&
|
72 |
+
(gameStep === 'step2' || gameStep === 'correct-step2') &&
|
73 |
+
currentQuestion.leftSide.length === currentQuestion.rightSide.length) {
|
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 |
+
// Handle step 1 answer (equal/not equal)
|
83 |
+
const handleStep1Answer = (answer: 'equal' | 'not-equal') => {
|
84 |
+
if (!currentQuestion) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
|
86 |
+
const isCorrect = (answer === 'equal') === currentQuestion.isEqual;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
|
88 |
+
if (isCorrect) {
|
89 |
+
setFeedback('Correct! 🎉');
|
90 |
+
setShowFeedback(true);
|
91 |
+
setGameStep('correct-step1');
|
92 |
+
|
93 |
+
// If they correctly identified as "equal", generate new question
|
94 |
+
// If they correctly identified as "not equal", proceed to step 2
|
95 |
+
if (answer === 'equal') {
|
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 |
+
// Handle step 2 answer (more/fewer)
|
122 |
+
const handleStep2Answer = (answer: 'left' | 'right') => {
|
123 |
+
if (!currentQuestion) return;
|
124 |
|
125 |
+
const leftCount = currentQuestion.leftSide.length;
|
126 |
+
const rightCount = currentQuestion.rightSide.length;
|
|
|
|
|
|
|
127 |
|
128 |
+
// Safety check: if sides are equal, this shouldn't happen in step 2
|
129 |
+
if (leftCount === rightCount) {
|
130 |
+
// Generate new question and go back to step 1
|
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 |
+
if (isCorrect) {
|
147 |
+
setFeedback('Excellent! 🌟');
|
148 |
+
setShowFeedback(true);
|
149 |
+
setGameStep('correct-step2');
|
150 |
+
|
151 |
+
// Generate new question after delay
|
152 |
+
setTimeout(() => {
|
153 |
+
setCurrentQuestion(generateQuestion());
|
154 |
+
setGameStep('step1');
|
155 |
+
setShowFeedback(false);
|
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="equal-game">
|
188 |
+
{gameStep === 'step1' || gameStep === 'correct-step1' ? (
|
189 |
+
// Step 1: Are they equal?
|
190 |
+
<>
|
191 |
+
<h1 className="question-title">Are they equal?</h1>
|
192 |
+
|
193 |
+
<div className="comparison-container">
|
194 |
+
<div className="side-container">
|
195 |
+
{renderEmojiSide(currentQuestion.leftSide, 'left')}
|
196 |
+
<div className="count-display">{currentQuestion.leftSide.length}</div>
|
197 |
+
</div>
|
198 |
+
|
199 |
+
<div className="vs-divider">VS</div>
|
200 |
+
|
201 |
+
<div className="side-container">
|
202 |
+
{renderEmojiSide(currentQuestion.rightSide, 'right')}
|
203 |
+
<div className="count-display">{currentQuestion.rightSide.length}</div>
|
204 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
205 |
</div>
|
206 |
+
|
207 |
+
{gameStep === 'step1' && (
|
208 |
+
<div className="answer-buttons">
|
209 |
+
<button
|
210 |
+
className="answer-btn equal-btn"
|
211 |
+
onClick={() => handleStep1Answer('equal')}
|
212 |
+
>
|
213 |
+
Equal
|
214 |
+
</button>
|
215 |
+
<button
|
216 |
+
className="answer-btn not-equal-btn"
|
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 |
+
<div className="vs-divider">VS</div>
|
239 |
+
|
240 |
+
<div className="side-container clickable" onClick={() => handleStep2Answer('right')}>
|
241 |
+
{renderEmojiSide(currentQuestion.rightSide, 'right')}
|
242 |
+
<div className="count-display">{currentQuestion.rightSide.length}</div>
|
243 |
+
</div>
|
244 |
+
</div>
|
245 |
+
|
246 |
+
<div className="step2-instruction">
|
247 |
+
Click on the side with {currentQuestion.secondStepQuestion} items
|
248 |
+
</div>
|
249 |
+
</>
|
250 |
+
) : (
|
251 |
+
// If somehow we get to step 2 with equal sides, generate new question
|
252 |
+
<div className="loading">Generating new question...</div>
|
253 |
+
)
|
254 |
)}
|
255 |
|
256 |
+
{showFeedback && (
|
257 |
+
<div className="feedback-message">
|
258 |
+
{feedback}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
259 |
</div>
|
260 |
)}
|
261 |
</div>
|