#Simon Says#React

Simon Says

Build the classic Simon Says memory game in React with color sequence generation, player input validation, sound effects, increasing difficulty, and high score tracking.

By Pratik RaiMedium

Build a Simon Says game where players must repeat sequences of colored pads. The key challenges are generating random sequences, playing sequences with visual feedback, validating user input, and managing game state transitions. This classic memory game is perfect for practicing async/await patterns in React.

Overview

A Simon Says game displays a 3×3 grid of 9 colored pads. The game plays a sequence of colors, and players must repeat the sequence by clicking the pads in the correct order. Each round adds one more color to the sequence, making it progressively harder. The game ends when the player makes a mistake.

Game Setup

Color Configuration

The game uses 9 colors arranged in a 3×3 grid:

TSXcomponent.tsx
1const COLORS = ["green", "red", "yellow", "blue", "purple", "orange", "pink", "brown", "gray"]; 2 3const getRandomColor = () => 4 COLORS[Math.floor(Math.random() * COLORS.length)];

How it works:

  • COLORS array defines all available colors
  • getRandomColor() selects a random color from the array
  • Each round adds one random color to the sequence

Component Architecture

The implementation uses a single component with state-driven game flow:

TSXcomponent.tsx
1const SimonSays = () => { 2 const [sequence, setSequence] = useState<string[]>([]); 3 const [userSequence, setUserSequence] = useState<string[]>([]); 4 const [activeColor, setActiveColor] = useState<string | null>(null); 5 const [isUserTurn, setIsUserTurn] = useState(false); 6 const [level, setLevel] = useState(0); 7 const [isPlaying, setIsPlaying] = useState(false); 8 9 const timeoutRef = useRef<number | null>(null); 10};

State Management:

  • sequence: The complete sequence the player must repeat (grows each round)
  • userSequence: The colors the player has clicked so far
  • activeColor: Currently flashing color (for visual feedback)
  • isUserTurn: Whether player can click pads
  • level: Current level (equals sequence length)
  • isPlaying: Whether game is active
  • timeoutRef: Stores timeout ID for cleanup (using useRef)

Game Flow

The game follows a sequence-play-input-validation pattern:

1. Start Game

TSXcomponent.tsx
1const startGame = () => { 2 resetGame(); 3 setIsPlaying(true); 4 nextRound([]); 5};

Flow:

  1. Reset all game state
  2. Set playing state to true
  3. Start first round with empty sequence

2. Next Round

Each round adds one color to the sequence:

TSXcomponent.tsx
1const nextRound = (prevSequence: string[]) => { 2 const next = [...prevSequence, getRandomColor()]; 3 setSequence(next); 4 setLevel(next.length); 5 playSequence(next); 6};

How it works:

  1. Create new sequence by adding random color to previous sequence
  2. Update sequence state
  3. Set level to sequence length
  4. Play the sequence for player to see

Example progression:

  • Round 1: ["green"] (level 1)
  • Round 2: ["green", "red"] (level 2)
  • Round 3: ["green", "red", "blue"] (level 3)
  • And so on...

3. Play Sequence

The game plays the sequence with visual flashes:

TSXcomponent.tsx
1const playSequence = async (seq: string[]) => { 2 setIsUserTurn(false); // Disable clicks during playback 3 4 for (let i = 0; i < seq.length; i++) { 5 await flash(seq[i]); 6 } 7 8 setUserSequence([]); // Reset user input 9 setIsUserTurn(true); // Enable clicks 10};

Flow:

  1. Disable user input (isUserTurn = false)
  2. Flash each color in sequence sequentially
  3. Wait for each flash to complete before next
  4. Reset user sequence
  5. Enable user input (isUserTurn = true)

Why async/await?

  • Ensures colors flash one at a time
  • Prevents overlapping flashes
  • Creates clear, sequential visual pattern

4. Flash Animation

Each color flashes with timing:

TSXcomponent.tsx
1const flash = (color: string) => { 2 return new Promise<void>((resolve) => { 3 setActiveColor(color); // Show color 4 5 timeoutRef.current = window.setTimeout(() => { 6 setActiveColor(null); // Hide color 7 8 timeoutRef.current = window.setTimeout(() => { 9 resolve(); // Continue to next color 10 }, 200); 11 }, 500); 12 }); 13};

Timing breakdown:

  • 0ms: Set active color (pad lights up)
  • 500ms: Clear active color (pad dims)
  • 700ms: Resolve promise (200ms gap before next flash)

Visual effect:

  • Pad flashes for 500ms
  • 200ms gap between flashes
  • Creates clear separation between colors

User Input Handling

Visual Feedback on Click

When player clicks a pad, it flashes briefly:

TSXcomponent.tsx
1const flashUserClick = (color: string) => { 2 setActiveColor(color); 3 if (timeoutRef.current) clearTimeout(timeoutRef.current); 4 timeoutRef.current = window.setTimeout(() => { 5 setActiveColor(null); 6 }, 200); 7};

Features:

  • Immediate visual feedback (200ms flash)
  • Clears any existing timeout to prevent conflicts
  • Uses same activeColor state as sequence playback

Input Validation

Player clicks are validated against the sequence:

TSXcomponent.tsx
1const handleClick = (color: string) => { 2 if (!isUserTurn) return; // Ignore clicks during sequence playback 3 4 flashUserClick(color); // Visual feedback 5 6 const nextInput = [...userSequence, color]; 7 setUserSequence(nextInput); 8 9 const currentIndex = nextInput.length - 1; 10 11 // Wrong input 12 if (sequence[currentIndex] !== color) { 13 gameOver(); 14 return; 15 } 16 17 // Completed round 18 if (nextInput.length === sequence.length) { 19 setIsUserTurn(false); 20 setTimeout(() => { 21 nextRound(sequence); 22 }, 800); 23 } 24};

Validation logic:

  1. Guard check: Ignore clicks if not user's turn
  2. Visual feedback: Flash clicked pad
  3. Update input: Add color to user sequence
  4. Check match: Compare clicked color with sequence at current index
  5. Wrong input: End game if colors don't match
  6. Round complete: If all colors matched, start next round after 800ms delay

Example:

  • Sequence: ["green", "red", "blue"]
  • User clicks: ["green", "red"]
  • User clicks: ["green", "red", "yellow"] ✗ (game over)

Game Over

When player makes a mistake:

TSXcomponent.tsx
1const gameOver = () => { 2 alert("Game Over"); 3 resetGame(); 4 setIsPlaying(false); 5};

Actions:

  • Show game over alert
  • Reset all game state
  • Set playing state to false (shows "Start Game" button)

Reset Game

Resets all state to initial values:

TSXcomponent.tsx
1const resetGame = () => { 2 if (timeoutRef.current) clearTimeout(timeoutRef.current); 3 setSequence([]); 4 setUserSequence([]); 5 setActiveColor(null); 6 setIsUserTurn(false); 7 setLevel(0); 8};

Cleanup:

  • Clear any pending timeouts
  • Reset sequence to empty
  • Clear user input
  • Clear active color
  • Disable user input
  • Reset level to 0

Styling

Grid Layout

The game uses CSS Grid for the 3×3 pad layout:

CSSstyles.css
1.board { 2 display: grid; 3 grid-template-columns: repeat(3, 120px); 4 gap: 16px; 5 justify-content: center; 6 margin-top: 20px; 7}

Features:

  • 3 columns, each 120px wide
  • 16px gap between pads
  • Centered grid
  • 20px top margin

Pad Styling

Each pad has base styling and active state:

CSSstyles.css
1.pad { 2 width: 120px; 3 height: 120px; 4 border-radius: 12px; 5 opacity: 0.6; 6 cursor: pointer; 7 transition: opacity 0.15s ease, transform 0.1s ease; 8} 9 10.pad.active { 11 opacity: 1; 12 transform: scale(1.08); 13 box-shadow: 0 0 20px rgba(255,255,255,0.6); 14}

Visual states:

  • Default: 60% opacity, normal size
  • Active: 100% opacity, 8% larger, white glow shadow

Color classes: Each color has its own background class:

  • .green, .red, .yellow, .blue, .purple, .orange, .pink, .brown, .gray

Key Takeaways

  1. Sequence Generation: Each round adds one random color to growing sequence
  2. Async Sequence Playback: Use async/await with Promise to flash colors sequentially
  3. State Management: Separate sequence (game's sequence) and userSequence (player's input)
  4. Turn Management: Use isUserTurn to control when player can interact
  5. Visual Feedback: Flash pads during both sequence playback and user clicks
  6. Input Validation: Compare user input index-by-index with sequence
  7. Level Tracking: Level equals sequence length (increases each round)
  8. Timeout Cleanup: Always clear timeouts to prevent memory leaks and conflicts

The beauty of this implementation is its clear separation of concerns. The sequence playback is completely separate from user input validation. The async/await pattern creates smooth, sequential animations. The turn-based system (isUserTurn) ensures players can't click during sequence playback, preventing confusion and errors.

The progressive difficulty (one color per round) creates an engaging challenge that scales naturally. The visual feedback system provides clear indication of both game actions and player actions, making the game intuitive and satisfying to play.

Goal: Implement a Simon Says game component with sequence generation and player input validation.

Frequently asked questions

What makes the playback the hard part?
"Show these four pads one after another" is not something a render can express — it is a sequence of side effects over time. It belongs in an effect with its own local index, a piece of bookkeeping that exists only for the duration of one playback and would be noise in state.
Why does the playback effect need a cleanup?
Because a restart mid-sequence leaves the old walk running. Without `clearTimeout`, two sequences light pads over each other and the player sees a pattern that was never in either one. This is the single most common bug in the problem.
How do you check the player's input?
Against `sequence[step]`, where `step` is how far they have got. A wrong pad ends the round. A right pad on the last index means the round is complete: reset the step, append a new random pad, and hand control back to the playback.
Why disable the pads during playback?
It stops more than cheating. Presses queued during playback would be checked against a `step` the playback is about to reset, so the guard is about correctness as much as fairness. It shows you thought about the states the component can be in, not just the happy path.

Related Challenges

Continue learning with these related challenges

View All
React

Whack A Mole

Build a fun Whack-a-Mole game in React with random mole spawning, click detection, score tracking, countdown timer, and increasing difficulty levels. Perfect for React practice.

React · JavaScriptPratik Rai ·

React

Memory Game

Create a card-matching memory game in React with flip animations, pair detection logic, move counter, timer, and win condition handling. A classic frontend coding challenge.

React · JavaScriptPratik Rai ·

React

Image Carousel

Create an interactive image carousel in React with smooth slide transitions, navigation arrows, dot indicators, autoplay, and touch/swipe support for mobile devices.

React · JavaScriptPratik Rai ·