Skip to content

Commit 9227993

Browse files
committed
Simon Says
1 parent d7b0c0f commit 9227993

File tree

3 files changed

+362
-0
lines changed

3 files changed

+362
-0
lines changed

src/data/content.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Jitter } from "../pages/games/Jitter";
99
import { RandomMeme } from "../pages/activities/RandomMemes";
1010
import { RandomJoke } from "../pages/activities/RandomJoke";
1111
import { RandomAnimeQuote } from "../pages/activities/RandomAnimeQuote";
12+
import { SimonSays } from "../pages/games/SimonSays";
1213
import meme from "../assets/activities/meme.jpg";
1314
import dog from "../assets/activities/dogimage.jpeg";
1415
import cat from "../assets/activities/cat.jpg";
@@ -121,4 +122,11 @@ export const games = [
121122
urlTerm: "GuessTheFlag",
122123
element: <GuessTheFlag />,
123124
},
125+
{
126+
title: "Simon Says",
127+
description: "Memory game - repeat the color sequence!",
128+
icon: "https://cdn-icons-png.flaticon.com/512/2991/2991148.png",
129+
urlTerm: "simon-says",
130+
element: <SimonSays />,
131+
},
124132
];

src/pages/games/SimonSays.js

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import React, { useState, useEffect, useRef } from "react";
2+
import "../../styles/pages/games/SimonSays.css";
3+
4+
export const SimonSays = () => {
5+
const [sequence, setSequence] = useState([]);
6+
const [userSequence, setUserSequence] = useState([]);
7+
const [isPlaying, setIsPlaying] = useState(false);
8+
const [isUserTurn, setIsUserTurn] = useState(false);
9+
const [level, setLevel] = useState(0);
10+
const [gameOver, setGameOver] = useState(false);
11+
const [activeButton, setActiveButton] = useState(null);
12+
const [message, setMessage] = useState("Press Start to Play!");
13+
14+
const colors = ["red", "blue", "green", "yellow"];
15+
const audioContextRef = useRef(null);
16+
17+
// Initialize audio context
18+
useEffect(() => {
19+
audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)();
20+
return () => {
21+
if (audioContextRef.current) {
22+
audioContextRef.current.close();
23+
}
24+
};
25+
}, []);
26+
27+
// Play sound for each color
28+
const playSound = (color) => {
29+
if (!audioContextRef.current) return;
30+
31+
const frequencies = {
32+
red: 329.63, // E4
33+
blue: 261.63, // C4
34+
green: 392.00, // G4
35+
yellow: 440.00 // A4
36+
};
37+
38+
const oscillator = audioContextRef.current.createOscillator();
39+
const gainNode = audioContextRef.current.createGain();
40+
41+
oscillator.connect(gainNode);
42+
gainNode.connect(audioContextRef.current.destination);
43+
44+
oscillator.frequency.value = frequencies[color];
45+
oscillator.type = "sine";
46+
47+
gainNode.gain.setValueAtTime(0.3, audioContextRef.current.currentTime);
48+
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContextRef.current.currentTime + 0.3);
49+
50+
oscillator.start(audioContextRef.current.currentTime);
51+
oscillator.stop(audioContextRef.current.currentTime + 0.3);
52+
};
53+
54+
// Flash button animation
55+
const flashButton = (color, duration = 500) => {
56+
return new Promise((resolve) => {
57+
setActiveButton(color);
58+
playSound(color);
59+
setTimeout(() => {
60+
setActiveButton(null);
61+
setTimeout(resolve, 200);
62+
}, duration);
63+
});
64+
};
65+
66+
// Play the sequence
67+
const playSequence = async (seq) => {
68+
setIsUserTurn(false);
69+
setMessage("Watch the sequence...");
70+
71+
for (let color of seq) {
72+
await flashButton(color);
73+
}
74+
75+
setIsUserTurn(true);
76+
setMessage("Your turn! Repeat the sequence.");
77+
};
78+
79+
// Start new game
80+
const startGame = () => {
81+
setGameOver(false);
82+
setLevel(1);
83+
setUserSequence([]);
84+
const newSequence = [colors[Math.floor(Math.random() * 4)]];
85+
setSequence(newSequence);
86+
setIsPlaying(true);
87+
playSequence(newSequence);
88+
};
89+
90+
// Handle user button click
91+
const handleColorClick = async (color) => {
92+
if (!isUserTurn || gameOver || !isPlaying) return;
93+
94+
await flashButton(color, 300);
95+
96+
const newUserSequence = [...userSequence, color];
97+
setUserSequence(newUserSequence);
98+
99+
// Check if the user's input matches the sequence so far
100+
const currentIndex = newUserSequence.length - 1;
101+
102+
if (newUserSequence[currentIndex] !== sequence[currentIndex]) {
103+
// Wrong color - game over
104+
setGameOver(true);
105+
setIsPlaying(false);
106+
setIsUserTurn(false);
107+
setMessage(`Game Over! You reached level ${level}. Press Start to try again.`);
108+
return;
109+
}
110+
111+
// Check if user completed the full sequence
112+
if (newUserSequence.length === sequence.length) {
113+
// User successfully completed the sequence
114+
setUserSequence([]);
115+
setLevel(level + 1);
116+
setIsUserTurn(false);
117+
setMessage("Great! Get ready for the next level...");
118+
119+
// Add new color to sequence after a delay
120+
setTimeout(() => {
121+
const newColor = colors[Math.floor(Math.random() * 4)];
122+
const newSequence = [...sequence, newColor];
123+
setSequence(newSequence);
124+
playSequence(newSequence);
125+
}, 1000);
126+
}
127+
};
128+
129+
// Reset game
130+
const resetGame = () => {
131+
setSequence([]);
132+
setUserSequence([]);
133+
setIsPlaying(false);
134+
setIsUserTurn(false);
135+
setLevel(0);
136+
setGameOver(false);
137+
setActiveButton(null);
138+
setMessage("Press Start to Play!");
139+
};
140+
141+
return (
142+
<div className="simon-says-container">
143+
<h1>Simon Says</h1>
144+
<div className="game-info">
145+
<p className="level">Level: {level}</p>
146+
<p className="message">{message}</p>
147+
</div>
148+
149+
<div className="simon-board">
150+
{colors.map((color) => (
151+
<button
152+
key={color}
153+
className={`simon-button ${color} ${activeButton === color ? "active" : ""}`}
154+
onClick={() => handleColorClick(color)}
155+
disabled={!isUserTurn}
156+
/>
157+
))}
158+
</div>
159+
160+
<div className="controls">
161+
{!isPlaying ? (
162+
<button className="control-btn start-btn" onClick={startGame}>
163+
{gameOver ? "Play Again" : "Start Game"}
164+
</button>
165+
) : (
166+
<button className="control-btn reset-btn" onClick={resetGame}>
167+
Reset
168+
</button>
169+
)}
170+
</div>
171+
</div>
172+
);
173+
};
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
.simon-says-container {
2+
text-align: center;
3+
padding: 2rem;
4+
max-width: 600px;
5+
margin: 0 auto;
6+
}
7+
8+
.simon-says-container h1 {
9+
font-size: 2.5rem;
10+
margin-bottom: 1rem;
11+
color: #333;
12+
}
13+
14+
.game-info {
15+
margin-bottom: 2rem;
16+
}
17+
18+
.level {
19+
font-size: 1.5rem;
20+
font-weight: bold;
21+
color: #2c3e50;
22+
margin-bottom: 0.5rem;
23+
}
24+
25+
.message {
26+
font-size: 1.1rem;
27+
color: #555;
28+
min-height: 30px;
29+
}
30+
31+
.simon-board {
32+
display: grid;
33+
grid-template-columns: repeat(2, 150px);
34+
grid-template-rows: repeat(2, 150px);
35+
gap: 15px;
36+
justify-content: center;
37+
margin: 2rem auto;
38+
padding: 20px;
39+
background: #2c3e50;
40+
border-radius: 50%;
41+
width: fit-content;
42+
}
43+
44+
.simon-button {
45+
width: 150px;
46+
height: 150px;
47+
border: 5px solid #2c3e50;
48+
cursor: pointer;
49+
transition: all 0.1s ease;
50+
opacity: 0.7;
51+
}
52+
53+
.simon-button:disabled {
54+
cursor: not-allowed;
55+
}
56+
57+
.simon-button:hover:not(:disabled) {
58+
opacity: 0.85;
59+
transform: scale(1.02);
60+
}
61+
62+
.simon-button.red {
63+
background-color: #e74c3c;
64+
border-top-left-radius: 100%;
65+
}
66+
67+
.simon-button.blue {
68+
background-color: #3498db;
69+
border-top-right-radius: 100%;
70+
}
71+
72+
.simon-button.green {
73+
background-color: #2ecc71;
74+
border-bottom-left-radius: 100%;
75+
}
76+
77+
.simon-button.yellow {
78+
background-color: #f1c40f;
79+
border-bottom-right-radius: 100%;
80+
}
81+
82+
.simon-button.active {
83+
opacity: 1;
84+
transform: scale(1.05);
85+
box-shadow: 0 0 30px rgba(255, 255, 255, 0.8);
86+
}
87+
88+
.simon-button.red.active {
89+
background-color: #ff6b5a;
90+
box-shadow: 0 0 30px #ff6b5a;
91+
}
92+
93+
.simon-button.blue.active {
94+
background-color: #5dade2;
95+
box-shadow: 0 0 30px #5dade2;
96+
}
97+
98+
.simon-button.green.active {
99+
background-color: #58d68d;
100+
box-shadow: 0 0 30px #58d68d;
101+
}
102+
103+
.simon-button.yellow.active {
104+
background-color: #f4d03f;
105+
box-shadow: 0 0 30px #f4d03f;
106+
}
107+
108+
.controls {
109+
margin-top: 2rem;
110+
}
111+
112+
.control-btn {
113+
padding: 0.8rem 2rem;
114+
font-size: 1.1rem;
115+
font-weight: bold;
116+
border: none;
117+
border-radius: 8px;
118+
cursor: pointer;
119+
transition: all 0.3s ease;
120+
min-width: 150px;
121+
}
122+
123+
.start-btn {
124+
background-color: #27ae60;
125+
color: white;
126+
}
127+
128+
.start-btn:hover {
129+
background-color: #229954;
130+
transform: translateY(-2px);
131+
box-shadow: 0 4px 12px rgba(39, 174, 96, 0.3);
132+
}
133+
134+
.reset-btn {
135+
background-color: #e67e22;
136+
color: white;
137+
}
138+
139+
.reset-btn:hover {
140+
background-color: #d35400;
141+
transform: translateY(-2px);
142+
box-shadow: 0 4px 12px rgba(230, 126, 34, 0.3);
143+
}
144+
145+
@media (max-width: 600px) {
146+
.simon-board {
147+
grid-template-columns: repeat(2, 120px);
148+
grid-template-rows: repeat(2, 120px);
149+
gap: 10px;
150+
}
151+
152+
.simon-button {
153+
width: 120px;
154+
height: 120px;
155+
}
156+
157+
.simon-says-container h1 {
158+
font-size: 2rem;
159+
}
160+
161+
.level {
162+
font-size: 1.3rem;
163+
}
164+
165+
.message {
166+
font-size: 1rem;
167+
}
168+
}
169+
170+
@media (max-width: 400px) {
171+
.simon-board {
172+
grid-template-columns: repeat(2, 100px);
173+
grid-template-rows: repeat(2, 100px);
174+
gap: 8px;
175+
}
176+
177+
.simon-button {
178+
width: 100px;
179+
height: 100px;
180+
}
181+
}

0 commit comments

Comments
 (0)