Simon Says Color Sequence Game — HTML CSS JS Snippet

Simon Says Color Sequence Game · Animations · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Async/await sequence playback using a Promise-based lightPad() helper instead of nested setTimeout callbacks
Growing memory pattern: sequence.push() adds one new random color per completed round
Input locking during playback via a playingBack guard and disabled pad buttons, preventing premature clicks
Immediate-fail validation: a single wrong click at any point ends the game on that exact click
localStorage-persisted best score that survives page refreshes and future browser sessions
CSS-only quadrant circle layout using independent per-corner border-radius values, no image assets
Distinct per-color glow on lit pads via filter brightness/saturate and a currentColor inset box-shadow
Round and score tracked and displayed live, decoupled cleanly from the sequence array's own length

About this UI Snippet

Simon Says Color Sequence Game — Async Sequence Playback, Growing Memory Pattern & localStorage High Score

Screenshot of the Simon Says Color Sequence Game snippet rendered live

Simon is one of the most enduring memory-game formats in consumer electronics history, and rebuilding it in the browser is a genuinely useful exercise in managing asynchronous timing, game state, and user input validation together. This snippet implements the complete classic loop with four colored quadrant pads: the game plays back a growing sequence of color flashes, the player must repeat it back exactly, a correct repeat extends the sequence by one more random color, and a wrong input ends the game immediately with a final score. The best score persists across browser sessions using localStorage, so returning players always see their personal record.

Modeling the sequence as an array and driving playback with async/await

The entire game state lives in one growing array, sequence, which starts empty and gains one new random color (chosen from COLORS = ['red', 'green', 'blue', 'yellow'] via Math.floor(Math.random() * COLORS.length)) every round. Rather than chaining setTimeout callbacks — which quickly becomes unreadable once you need to flash four, five, or ten pads in order — the snippet wraps each pad flash in a Promise-returning helper, lightPad(color, duration), and awaits them one at a time inside an async function playSequence(). This means the playback logic reads top-to-bottom like synchronous code (for (let i = 0; i < sequence.length; i++) { await lightPad(sequence[i], 480); }) while still being fully non-blocking, and it is trivial to insert a pause between flashes or before the sequence starts by awaiting a small wait(ms) promise-timeout helper.

Locking input during playback

A critical correctness detail is that the four pad buttons must be genuinely unclickable while the sequence is animating — otherwise a fast or accidental click during playback could be misread as the player's first move. The snippet handles this with a combination of a playingBack boolean guard checked at the top of handlePadClick() and setting the actual disabled attribute on every pad button via setPadsEnabled(false) for the duration of playSequence(). Only after the full sequence has finished flashing does acceptingInput flip to true, playerIndex reset to 0, and the pads re-enable, ensuring player input can only ever be interpreted against a sequence that has fully finished displaying.

Validating player input against the sequence

As the player clicks pads, handlePadClick(color) compares the clicked color against sequence[playerIndex] — the expected next color in the sequence. A match increments playerIndex; a mismatch immediately calls endGame(), ending the round on the very first wrong click rather than waiting for the player to finish an incorrect attempt. If playerIndex reaches sequence.length, the player has correctly repeated the entire sequence, so the pads disable again and, after a brief pause, nextRound() pushes one more random color onto the sequence and replays the whole extended sequence from the beginning — the defining "growing memory pattern" mechanic that makes Simon progressively harder.

Best score persistence with localStorage

On page load, loadBest() reads a stored value from localStorage.getItem('simon-best-score'), parses it as an integer (defaulting to 0 if nothing is stored or the value is invalid), and displays it in the Best Score stat tile. Whenever a game ends with a score higher than the currently stored best, saveBest() writes the new value back to localStorage immediately, so the record persists across page refreshes and future browser sessions without needing any backend. The score itself is calculated as sequence.length - 1, since a wrong click on round N means the player successfully completed N-1 full rounds before failing.

Visual feedback: quadrant layout and lit-pad glow

The four pads are arranged as CSS-rounded quadrants of a circle using border-radius set independently on each corner (100% 0 0 0 for the top-left pad, and so on), recreating the classic circular Simon console shape entirely with CSS, no image assets required. A center circle houses the Start button. When a pad is part of an active flash — whether during automated playback or as instant feedback on a player click — it gains a .lit class that boosts opacity, applies a filter: brightness(1.35) saturate(1.2), and adds an inset glow using box-shadow: 0 0 24px currentColor inset, giving each color pad a distinct colored glow that matches its own hue.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain how the playingBack and acceptingInput flags together prevent race conditions between the automated sequence playback and player clicks, and how the async/await pattern in playSequence() replaces what would otherwise be deeply nested setTimeout callbacks. It's also a good snippet to extend with an assistant's help — ask for a difficulty setting that speeds up the flash duration as rounds increase, a strict mode where a single mistake resets the whole game rather than just ending the current run, or a sound-based version using the Web Audio API to play a distinct tone per color alongside the visual flash, closer to the original electronic toy. Because the game state is fully contained in a handful of well-named variables, it is also straightforward to ask for a version refactored into a reusable class or React hook.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a Simon Says style color sequence memory game in plain HTML, CSS, and JavaScript with four colored pads, growing sequences, and a persisted best score — no external libraries or sound files required.

Requirements:
- Four distinct colored pad buttons arranged as quadrants (for example in a circle or 2x2 grid), plus a Start control that begins a new game.
- On starting a game, generate a sequence of one random color, then play it back by visually lighting each pad in order (using a class toggle or similar, not opacity 0 tricks that break click targets) with a brief pause between each flash, during which all pads must be genuinely unclickable/disabled.
- After playback finishes, allow the player to click the pads to repeat the sequence; each click must be checked immediately against the expected next color, ending the game right away on the first incorrect click rather than waiting for the full attempt to finish.
- On a fully correct repeat of the current sequence, append one additional random color to the sequence and replay the entire new, longer sequence from the beginning, so each round is strictly one step harder than the last.
- Track and display the current round or score during play, and on game over show a clear "Game Over — Score: N" message along with a way to immediately start a new game.
- Persist the best score achieved across sessions using localStorage, loading it on page start and updating it only when a new run's score exceeds the previously stored best.
- Ensure rapid or mistimed clicks never cause the game state (sequence position, playback lock) to desync, even if the player clicks a pad the instant before or after playback finishes.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Click Start to beginClicking the center Start button calls startGame(), which resets the sequence array, round counter, and player index, then immediately calls nextRound() to add the first random color and begin playback.
  2. 2
    Watch the sequence playplaySequence() disables all pads and lights each color in sequence order using async/await over the lightPad() promise helper, with a short pause between flashes so each color is clearly distinguishable before the next one lights up.
  3. 3
    Repeat the sequence backOnce playback finishes, the pads re-enable and cardSub changes to "Your turn". Click the pads in the same order they flashed — handlePadClick() checks each click against sequence[playerIndex] and advances only on a correct match.
  4. 4
    Advance to the next roundCompleting the full sequence correctly triggers nextRound() after a short delay, which appends one more random color and replays the entire extended sequence from the start — this is what makes each round strictly harder than the last.
  5. 5
    See your score on a wrong clickClicking the wrong pad at any point immediately ends the game via endGame(), which shows a "Game Over — Score: N" banner where N is the number of rounds successfully completed, and updates the Best Score tile if this run set a new personal record.
  6. 6
    Restart and check your persisted best scoreClick "Play Again" on the game-over banner to call startGame() again. The Best Score tile is read from localStorage on page load via loadBest(), so your personal record survives page refreshes and future visits to the page.

Real-world uses

Common Use Cases

Standalone memory game or arcade-style mini-game feature
Ship this as a nostalgic, dependency-free memory game for a games portal, a loading-screen distraction, or a standalone page targeting search traffic for "Simon says game online". The async playback and immediate-fail validation reproduce the exact feel of the original electronic toy.
Cognitive training and working-memory practice tool
Simon-style sequence games are a well-established simple test of short-term working memory and sequential recall. Educational or brain-training platforms can embed this component as a quick daily memory exercise, tracking the persisted best score as a rough proxy for improvement over time.
Gamified waiting-room or engagement filler during idle time
Apps with unavoidable idle moments — matchmaking queues, onboarding tutorials, checkout confirmations — can drop in a quick game like this to keep users engaged, similar to how the Reaction Time Tester Game turns dead time into a moment of light interaction rather than a blank spinner.
Teaching example for async/await sequencing in UI animation
This snippet is a clean, self-contained demonstration of replacing nested setTimeout callback chains with async/await over small Promise-returning helper functions, a pattern broadly useful anywhere a UI needs to play a timed sequence of visual states in strict order.
Onboarding tutorial pattern reused for feature walkthroughs
The core "highlight one element at a time in sequence, then require the user to interact with them in the same order" mechanic generalizes beyond games — product tours and interactive tutorials can borrow the same playSequence()-style async highlighting pattern to walk new users through a series of UI elements one at a time.
Portfolio piece demonstrating state machine and timing discipline
Because correctness here depends entirely on precise state management (playingBack, acceptingInput, playerIndex) rather than visual polish alone, this snippet is a strong portfolio piece to show a potential employer careful handling of asynchronous UI state, distinct from more decorative snippets.

Got questions?

Frequently Asked Questions

Two mechanisms work together: a playingBack boolean is checked at the very start of handlePadClick() and immediately exits if true, and every pad button also has its disabled attribute set to true via setPadsEnabled(false) for the full duration of playSequence(). Because the pads are genuinely disabled at the DOM level, even rapid or accidental clicks during playback produce no click event at all, not just an ignored one.

Chaining multiple setTimeout calls to flash colors in order quickly becomes deeply nested and hard to follow, especially once you want to insert consistent pauses between each flash. By wrapping a single flash in a function that returns a Promise (lightPad()), the playback loop can use a normal for loop with await, so the code reads sequentially top to bottom while still remaining fully non-blocking for the rest of the page.

The score equals sequence.length - 1 at the moment of a wrong click. This is because sequence.length reflects the round currently being attempted (including the new color just added for that round), and the player failed to complete it, so they successfully finished sequence.length - 1 full rounds before the mistake. This value is what appears in the "Game Over — Score: N" banner and is compared against the stored best score.

No. The best score is stored using localStorage, which is scoped to the specific browser and device it was set in — it does not sync across devices or browsers automatically. To share a best score across devices, you would need to sync the value to a backend database keyed to a logged-in user account instead of relying solely on localStorage.

Yes. Add a new entry to the COLORS array, a corresponding pad button in the HTML with a matching id and data-color attribute, an entry in the pads object in the JS panel, and CSS rules for its background color and quadrant border-radius shape. You will likely also want to adjust the CSS grid layout from a 2x2 arrangement to accommodate additional pads, for example a hexagonal or circular arrangement for six colors.