From 92279930bd327a4a539f6c59725a314780081a88 Mon Sep 17 00:00:00 2001 From: Keshav Date: Sun, 26 Oct 2025 16:03:48 +0530 Subject: [PATCH] Simon Says --- src/data/content.js | 8 ++ src/pages/games/SimonSays.js | 173 +++++++++++++++++++++++++ src/styles/pages/games/SimonSays.css | 181 +++++++++++++++++++++++++++ 3 files changed, 362 insertions(+) create mode 100644 src/pages/games/SimonSays.js create mode 100644 src/styles/pages/games/SimonSays.css diff --git a/src/data/content.js b/src/data/content.js index fd08275..d430828 100644 --- a/src/data/content.js +++ b/src/data/content.js @@ -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"; @@ -121,4 +122,11 @@ export const games = [ urlTerm: "GuessTheFlag", element: , }, + { + 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: , + }, ]; diff --git a/src/pages/games/SimonSays.js b/src/pages/games/SimonSays.js new file mode 100644 index 0000000..83dd468 --- /dev/null +++ b/src/pages/games/SimonSays.js @@ -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 ( +
+

Simon Says

+
+

Level: {level}

+

{message}

+
+ +
+ {colors.map((color) => ( +
+ +
+ {!isPlaying ? ( + + ) : ( + + )} +
+
+ ); +}; diff --git a/src/styles/pages/games/SimonSays.css b/src/styles/pages/games/SimonSays.css new file mode 100644 index 0000000..d77727b --- /dev/null +++ b/src/styles/pages/games/SimonSays.css @@ -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; + } +}