9 Copy-Paste Mini-Game Snippets That Actually Play (Wordle, Simon, a 15-Puzzle & More)

Explore 9 fully playable HTML, CSS and JavaScript mini-games, including Wordle, Simon Says, 15-Puzzle, Tic-Tac-Toe, Morse, and a virtual piano.
9 Copy-Paste Mini-Game Snippets

Most "game" snippets on the web are screenshots — a static mockup of a Wordle board or a piano that doesn't make a sound. These nine are different: every one is a fully playable, self-contained game with real logic underneath, not a decorative shell. The 15-puzzle only ever shuffles into solvable states. The Tic-Tac-Toe opponent actually tries to win and block instead of clicking randomly. The Wordle clone correctly handles repeated letters, which is the single most common bug in every "build a Wordle clone" tutorial. The piano makes real musical notes through the Web Audio API, not a beep.

Below: a WPM typing test with live character-diffing, a reaction-time tester built on performance.now(), a Simon Says memory game with async-driven sequence playback, Whack-a-Mole with self-rescheduling randomised timers, a provably-solvable 15-puzzle, a Tic-Tac-Toe opponent with real heuristics, a Wordle-style word guesser with correct duplicate-letter scoring, a Morse code translator that plays real audio at the exact international timing ratios, and a fully playable virtual piano. Every one is a live, interactive preview — play them right here in the article — and each exports to React, Vue, Angular, or Tailwind in one click from the snippet page.

Why these aren't just decorative

  • The logic is actually correct, not just plausible-looking. A naive Wordle clone double-credits repeated letters; a naive 15-puzzle shuffle can produce an unsolvable board about half the time; a naive Tic-Tac-Toe "AI" just picks a random empty cell. Every one of those failure modes is a genuinely common bug, and every snippet here specifically avoids it.
  • Timing is measured with the right clock. The reaction tester uses performance.now(), not Date.now(), because it's high-resolution and immune to system clock adjustments — the same API browser profiling tools use internally. The Morse player derives every dot, dash, and silence from one base unit so the whole rhythm stays proportionally correct at any speed.
  • Async state is modelled, not faked. Simon's sequence playback is a real async function that awaits each flash in order, which is what lets the code read top-to-bottom like synchronous logic while staying fully non-blocking — no nested setTimeout callback pyramids.

1. Typing Speed Test (WPM Counter)

Type a random sample sentence and watch a live WPM and accuracy counter update as you go, with every character coloured correct or incorrect in real time.

How it works: WPM uses the standard typing-test convention that one "word" equals five characters including spaces — words = typed.length / 5, then wpm = Math.round((typed.length / 5) / (elapsedSeconds / 60)) — which normalises scoring across sentences with different average word lengths instead of counting actual word boundaries. The timer deliberately does not start on page load: it starts on the very first input event via a null-check on startTime, so time spent reading the prompt before you start typing never counts against you. A setInterval running every 100ms recalculates WPM, accuracy, and elapsed time continuously so the numbers update smoothly rather than jumping only on keystrokes.

Best for: keyboarding-practice sites, recruiting pages for typing-heavy roles, and portfolio pieces that want to demonstrate real-time DOM diffing. Tip: the character-diffing and timing logic are cleanly separated from rendering, which makes this a good base to extend into a fixed-duration 60-second mode.

Grab the code: Typing Speed Test (WPM Counter)

2. Reaction Time Tester

Wait for the panel to turn green, then click as fast as you can — click too early and it's a "Too soon!" fail, not a valid time.

How it works: if the wait before "go" were a fixed duration, users would learn to time their click by rhythm instead of genuinely reacting. randomDelay() returns 1500 + Math.random() * 2500 — a random 1.5 to 4 second wait — passed into a cancellable setTimeout. The instant the panel flips green, goTime = performance.now() captures a high-resolution timestamp, and a click during the ready state computes Math.round(performance.now() - goTime) for the result in whole milliseconds. performance.now() is sub-millisecond precise and immune to system clock changes, which is exactly why browser profiling tools use it instead of Date.now().

Best for: neuroscience/cognitive-testing demo sites, "test your reflexes" landing pages, and as a teaching example for correct browser timing APIs. Tip: the cancellable timeout is the important detail — without it, a stray "go" state can fire after the round already ended in a too-soon failure.

Grab the code: Reaction Time Tester Game

3. Simon Says Colour Sequence Game

The classic four-pad memory game — the sequence plays back, you repeat it, a correct repeat adds one more colour, and a wrong tap ends the run with your score saved.

How it works: the whole game state is one growing array, sequence, which gains a new random colour from COLORS = ['red', 'green', 'blue', 'yellow'] every round. Rather than chaining setTimeout callbacks — unreadable once you're flashing ten pads in order — each flash is wrapped in a Promise-returning lightPad(color, duration) helper, awaited one at a time inside an async function playSequence(): for (let i = 0; i < sequence.length; i++) { await lightPad(sequence[i], 480); }. That reads top-to-bottom like synchronous code while staying fully non-blocking. The best score persists across sessions via localStorage.

Best for: memory-training apps, waiting-room/kiosk entertainment, and as a clean reference for the async/await sequencing pattern itself, independent of the game. Tip: the promise-per-flash pattern here generalises to any UI that needs to play a scripted sequence of animations in order without a callback pyramid.

Grab the code: Simon Says Colour Sequence Game

4. Whack-a-Mole Game

A 3x3 grid of holes, a mole that pops up at unpredictable intervals, a satisfying squash-hit animation, and a 30-second countdown against your best score.

How it works: the tricky part of Whack-a-Mole isn't the animation, it's coordinating a randomised, self-rescheduling timer with real user input while keeping one source of truth for "what's clickable right now." A single activeHole reference tracks the currently-up mole; each pop schedules its own retract via setTimeout and then reschedules the next pop, with both the pop interval and up-time randomised per round. A separate one-second setInterval drives the 30-second countdown independently. The best score reads and writes localStorage under the key whack-a-mole-best, only flashing "New best score!" when the record actually breaks.

Best for: arcade-style landing page easter eggs, kids'-app portfolio pieces, and 404 pages that want to give visitors something to do. Tip: the single-activeHole-reference pattern is the right way to prevent double-scoring in any "only one thing can be active" game — resist the temptation to track state per-cell instead.

Grab the code: Whack-a-Mole Game

5. Sliding Number Puzzle (15-Puzzle)

The classic 4x4 sliding tile puzzle — shuffled a way that guarantees it's actually solvable, which is the detail most from-scratch implementations get wrong.

How it works: a purely random permutation of 15 tiles is unsolvable roughly half the time — permutation parity makes solvability a real mathematical constraint, not a formality. shuffleFromSolved() sidesteps the problem entirely by starting from the solved board and replaying 250 random legal slides, which can never leave a solvable state. A subtler bug it also avoids: naively picking any random legal neighbor lets the blank tile bounce back and forth between the same two cells, producing a shuffle that looks busy but barely mixes the board. The snippet tracks lastEmpty, the blank's position before the previous move, and filters it out of the candidate list each iteration, forcing every step to make genuine progress.

Best for: waiting-screen distractions, brain-teaser content sections, and as a teaching example of permutation parity in a genuinely visual, hands-on way. Tip: tiles animate with transform: translate() rather than layout properties, so the slide is a pure GPU-composited move with no reflow cost.

Grab the code: Sliding Number Puzzle (15-Puzzle)

6. Tic-Tac-Toe vs Computer

A real opponent, not a random-move bot: it takes a winning move when one exists, blocks yours when you have one, and otherwise prefers the centre, then corners, then edges.

How it works: computerMove() runs a short, ordered heuristic chain instead of a full minimax search: first findWinningMove('O') checks whether the computer can win this turn and takes it immediately; if not, the same function is reused with the human's mark to check findWinningMove('X') and block it; if neither applies, it takes the centre cell if open, then a random open corner, then a random open edge. Reusing one findWinningMove(mark) function for both the "can I win" and "must I block" checks — just by swapping which mark it's called with — is what keeps the whole AI to a handful of lines instead of a full game-tree search, while still playing a genuinely competent, not-obviously-beatable game.

Best for: any site that wants a quick, low-stakes interactive moment — pricing page easter eggs, loading-screen distractions, kids' educational sites. Tip: the win/block/centre/corner/edge heuristic ordering is a reusable pattern worth knowing even outside Tic-Tac-Toe — it's the classic "good enough without search" approach to simple perfect-information games.

Grab the code: Tic-Tac-Toe vs Computer

7. Word Guess Game (Wordle-Style)

A 6x5 Wordle clone with correctly-implemented duplicate-letter scoring — the single detail that breaks nearly every from-scratch Wordle tutorial.

How it works: a naive scorer checks whether each guessed letter exists anywhere in the target and colours it yellow if so — which breaks the moment a letter repeats. Guess ERASE against target CRANE and a naive check marks both E's yellow, even though CRANE only has one E. scoreGuess() fixes this with two passes sharing one consumed tracking array: pass one marks every exact-position match green and immediately consumes that target index; pass two then searches for an unconsumed occurrence of each remaining letter via targetLetters.findIndex((t, idx) => t === letter && !consumed[idx]), marking it yellow and consuming it if found. Because consumption is tracked per target-letter-index and shared across both passes, a target with exactly one E can only ever credit one guessed E — matching real Wordle behaviour exactly.

Best for: word-game portfolio pieces, ESL/vocabulary-learning sites, and as a genuinely useful interview-prep or tutorial artifact — the CRANE/ERASE edge case is a well-known trap worth walking through concretely. Tip: if you're extending this, test any change against a target and guess that share exactly two of the same letter — that's where naive scoring silently breaks.

Grab the code: Word Guess Game (Wordle-Style)

8. Morse Code Translator & Player

Type text, watch it convert to dots and dashes live, then hear it played back as real audio at exactly the international Morse timing ratios.

How it works: Morse code is defined entirely by timing ratios, not any visual shape — a dot is 1 unit, a dash is 3 units, the gap within one character is 1 unit, between letters 3 units, between words 7 units. This snippet derives every duration from one UNIT_MS constant (DOT = UNIT_MS, DASH = UNIT_MS * 3, LETTER_GAP = UNIT_MS * 3, WORD_GAP = UNIT_MS * 7), so changing playback speed is a single-line edit that scales the entire rhythm proportionally. Audio comes from a real OscillatorNode/GainNode pair created fresh per beep (an oscillator is single-use — once stopped it can't restart), set to a 600Hz sine tone, with oscillator.stop() scheduled against the audio context's own high-resolution clock rather than a JavaScript timer, so playback timing never drifts.

Best for: ham-radio and signals-history content, scouting/survival-skills educational sites, and as a genuinely interesting small scheduling problem to study if you've never scheduled audio against an AudioContext clock before. Tip: the AudioContext is created lazily on first user interaction specifically to respect browser autoplay policies, which require an audio context to originate from a real user gesture.

Grab the code: Morse Code Translator & Player

9. Virtual Piano Keyboard

A real playable piano — click a key or press the mapped computer key, and hear the actual musically-correct note, not a generic beep.

How it works: every key carries a real equal-tempered frequency (C4 = 261.63 Hz, up through B4 = 493.88 Hz), computed from the standard 12-tone equal temperament formula freq = 440 * 2^((n-49)/12) where A4 = 440 Hz. Pressing a key builds an OscillatorNode set to type: 'triangle' — a softer, more piano-like timbre than the harsh default sine wave — routed through a GainNode implementing a real attack/decay envelope: linearRampToValueAtTime ramps volume up over 20ms to avoid the audible "click" a hard volume jump causes, then an exponential ramp eases into sustain. Releasing the key ramps gain back down over 400ms before calling osc.stop(), producing a natural decay instead of an abrupt cutoff. The black keys are absolutely positioned overlapping the white keys, exactly like a real keyboard layout.

Best for: music-education sites, a reference implementation for anyone learning AudioContext/OscillatorNode/GainNode envelope shaping for the first time, and as a base for interval-recognition or chord-building quiz tools. Tip: every key's data-freq attribute is copy-pasteable — it's a ready reference for converting note names to playback frequency in any other audio project.

Grab the code: Virtual Piano Keyboard

How to drop these into your project

  1. Open the snippet and hit View / Edit Code to see the HTML, CSS, and JS in separate tabs — every game here is pure vanilla JavaScript with zero dependencies and no CDN scripts required.
  2. Paste the HTML into your markup, the CSS into your stylesheet, and the JS before your closing </body> tag, or use the one-click export to React, Vue, Angular, or Tailwind right from the snippet page.
  3. Games with audio (the Morse player and the piano) create their AudioContext lazily, only on the first click or keypress — never on mount — so they respect every browser's autoplay policy automatically. Keep that pattern if you extend them.
  4. Games with persisted state (Whack-a-Mole's best score, Simon's high score) use a single, clearly-named localStorage key. If you embed more than one of these on the same page, double check the keys don't collide.
  5. Every game manages its own timers (setInterval/setTimeout) internally and cleans them up on reset — if you wrap one in a React/Vue component, mirror that cleanup inside your unmount lifecycle so a game left running in an unmounted component doesn't keep firing.

Final thought

What separates a "fun" snippet from a genuinely useful one is whether the logic underneath is actually correct — a Wordle clone that mis-scores repeated letters, a puzzle that shuffles into an unsolvable state, or an AI opponent that just clicks randomly all fail in ways a careful player notices immediately. Every game here specifically gets the non-obvious part right, which is exactly what makes them worth studying even if you never ship a game: the duplicate-letter scoring pattern, the legal-move shuffle, the win/block/centre heuristic, and the audio-context timing all generalize far beyond the game they're wrapped in.

Preview any of them live, tweak the code, and export to your framework of choice in one click. Browse the full collection at FWD Tools UI Snippets — it's free and runs entirely in your browser.

About the author

Puneet Sharma
Puneet Sharma is a freelance web developer and the creator of FWD Tools and WebDevPuneet.

Post a Comment