Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/data/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Jitter } from "../pages/games/Jitter";
import { RandomMeme } from "../pages/activities/RandomMemes";
import { RandomJoke } from "../pages/activities/RandomJoke";
import { RandomAnimeQuote } from "../pages/activities/RandomAnimeQuote";
import { SimonSays } from "../pages/games/SimonSays";
import meme from "../assets/activities/meme.jpg";
import dog from "../assets/activities/dogimage.jpeg";
import cat from "../assets/activities/cat.jpg";
Expand Down Expand Up @@ -121,4 +122,11 @@ export const games = [
urlTerm: "GuessTheFlag",
element: <GuessTheFlag />,
},
{
title: "Simon Says",
description: "Memory game - repeat the color sequence!",
icon: "https://cdn-icons-png.flaticon.com/512/2991/2991148.png",
urlTerm: "simon-says",
element: <SimonSays />,
},
];
173 changes: 173 additions & 0 deletions src/pages/games/SimonSays.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import React, { useState, useEffect, useRef } from "react";
import "../../styles/pages/games/SimonSays.css";

export const SimonSays = () => {
const [sequence, setSequence] = useState([]);
const [userSequence, setUserSequence] = useState([]);
const [isPlaying, setIsPlaying] = useState(false);
const [isUserTurn, setIsUserTurn] = useState(false);
const [level, setLevel] = useState(0);
const [gameOver, setGameOver] = useState(false);
const [activeButton, setActiveButton] = useState(null);
const [message, setMessage] = useState("Press Start to Play!");

const colors = ["red", "blue", "green", "yellow"];
const audioContextRef = useRef(null);

// Initialize audio context
useEffect(() => {
audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)();
return () => {
if (audioContextRef.current) {
audioContextRef.current.close();
}
};
}, []);

// Play sound for each color
const playSound = (color) => {
if (!audioContextRef.current) return;

const frequencies = {
red: 329.63, // E4
blue: 261.63, // C4
green: 392.00, // G4
yellow: 440.00 // A4
};

const oscillator = audioContextRef.current.createOscillator();
const gainNode = audioContextRef.current.createGain();

oscillator.connect(gainNode);
gainNode.connect(audioContextRef.current.destination);

oscillator.frequency.value = frequencies[color];
oscillator.type = "sine";

gainNode.gain.setValueAtTime(0.3, audioContextRef.current.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContextRef.current.currentTime + 0.3);

oscillator.start(audioContextRef.current.currentTime);
oscillator.stop(audioContextRef.current.currentTime + 0.3);
};

// Flash button animation
const flashButton = (color, duration = 500) => {
return new Promise((resolve) => {
setActiveButton(color);
playSound(color);
setTimeout(() => {
setActiveButton(null);
setTimeout(resolve, 200);
}, duration);
});
};

// Play the sequence
const playSequence = async (seq) => {
setIsUserTurn(false);
setMessage("Watch the sequence...");

for (let color of seq) {
await flashButton(color);
}

setIsUserTurn(true);
setMessage("Your turn! Repeat the sequence.");
};

// Start new game
const startGame = () => {
setGameOver(false);
setLevel(1);
setUserSequence([]);
const newSequence = [colors[Math.floor(Math.random() * 4)]];
setSequence(newSequence);
setIsPlaying(true);
playSequence(newSequence);
};

// Handle user button click
const handleColorClick = async (color) => {
if (!isUserTurn || gameOver || !isPlaying) return;

await flashButton(color, 300);

const newUserSequence = [...userSequence, color];
setUserSequence(newUserSequence);

// Check if the user's input matches the sequence so far
const currentIndex = newUserSequence.length - 1;

if (newUserSequence[currentIndex] !== sequence[currentIndex]) {
// Wrong color - game over
setGameOver(true);
setIsPlaying(false);
setIsUserTurn(false);
setMessage(`Game Over! You reached level ${level}. Press Start to try again.`);
return;
}

// Check if user completed the full sequence
if (newUserSequence.length === sequence.length) {
// User successfully completed the sequence
setUserSequence([]);
setLevel(level + 1);
setIsUserTurn(false);
setMessage("Great! Get ready for the next level...");

// Add new color to sequence after a delay
setTimeout(() => {
const newColor = colors[Math.floor(Math.random() * 4)];
const newSequence = [...sequence, newColor];
setSequence(newSequence);
playSequence(newSequence);
}, 1000);
}
};

// Reset game
const resetGame = () => {
setSequence([]);
setUserSequence([]);
setIsPlaying(false);
setIsUserTurn(false);
setLevel(0);
setGameOver(false);
setActiveButton(null);
setMessage("Press Start to Play!");
};

return (
<div className="simon-says-container">
<h1>Simon Says</h1>
<div className="game-info">
<p className="level">Level: {level}</p>
<p className="message">{message}</p>
</div>

<div className="simon-board">
{colors.map((color) => (
<button
key={color}
className={`simon-button ${color} ${activeButton === color ? "active" : ""}`}
onClick={() => handleColorClick(color)}
disabled={!isUserTurn}
/>
))}
</div>

<div className="controls">
{!isPlaying ? (
<button className="control-btn start-btn" onClick={startGame}>
{gameOver ? "Play Again" : "Start Game"}
</button>
) : (
<button className="control-btn reset-btn" onClick={resetGame}>
Reset
</button>
)}
</div>
</div>
);
};
181 changes: 181 additions & 0 deletions src/styles/pages/games/SimonSays.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
.simon-says-container {
text-align: center;
padding: 2rem;
max-width: 600px;
margin: 0 auto;
}

.simon-says-container h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
color: #333;
}

.game-info {
margin-bottom: 2rem;
}

.level {
font-size: 1.5rem;
font-weight: bold;
color: #2c3e50;
margin-bottom: 0.5rem;
}

.message {
font-size: 1.1rem;
color: #555;
min-height: 30px;
}

.simon-board {
display: grid;
grid-template-columns: repeat(2, 150px);
grid-template-rows: repeat(2, 150px);
gap: 15px;
justify-content: center;
margin: 2rem auto;
padding: 20px;
background: #2c3e50;
border-radius: 50%;
width: fit-content;
}

.simon-button {
width: 150px;
height: 150px;
border: 5px solid #2c3e50;
cursor: pointer;
transition: all 0.1s ease;
opacity: 0.7;
}

.simon-button:disabled {
cursor: not-allowed;
}

.simon-button:hover:not(:disabled) {
opacity: 0.85;
transform: scale(1.02);
}

.simon-button.red {
background-color: #e74c3c;
border-top-left-radius: 100%;
}

.simon-button.blue {
background-color: #3498db;
border-top-right-radius: 100%;
}

.simon-button.green {
background-color: #2ecc71;
border-bottom-left-radius: 100%;
}

.simon-button.yellow {
background-color: #f1c40f;
border-bottom-right-radius: 100%;
}

.simon-button.active {
opacity: 1;
transform: scale(1.05);
box-shadow: 0 0 30px rgba(255, 255, 255, 0.8);
}

.simon-button.red.active {
background-color: #ff6b5a;
box-shadow: 0 0 30px #ff6b5a;
}

.simon-button.blue.active {
background-color: #5dade2;
box-shadow: 0 0 30px #5dade2;
}

.simon-button.green.active {
background-color: #58d68d;
box-shadow: 0 0 30px #58d68d;
}

.simon-button.yellow.active {
background-color: #f4d03f;
box-shadow: 0 0 30px #f4d03f;
}

.controls {
margin-top: 2rem;
}

.control-btn {
padding: 0.8rem 2rem;
font-size: 1.1rem;
font-weight: bold;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
min-width: 150px;
}

.start-btn {
background-color: #27ae60;
color: white;
}

.start-btn:hover {
background-color: #229954;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(39, 174, 96, 0.3);
}

.reset-btn {
background-color: #e67e22;
color: white;
}

.reset-btn:hover {
background-color: #d35400;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(230, 126, 34, 0.3);
}

@media (max-width: 600px) {
.simon-board {
grid-template-columns: repeat(2, 120px);
grid-template-rows: repeat(2, 120px);
gap: 10px;
}

.simon-button {
width: 120px;
height: 120px;
}

.simon-says-container h1 {
font-size: 2rem;
}

.level {
font-size: 1.3rem;
}

.message {
font-size: 1rem;
}
}

@media (max-width: 400px) {
.simon-board {
grid-template-columns: repeat(2, 100px);
grid-template-rows: repeat(2, 100px);
gap: 8px;
}

.simon-button {
width: 100px;
height: 100px;
}
}