For supporters
Source Code
Want the source code?
Play the game for free. Become a WebDevPuneet supporter to unlock the complete HTML, CSS and JavaScript source code, the exports (HTML, React, Vue, Angular, Tailwind) and Fork.
Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Lights Out Puzzle Game — 5x5 Toggle Grid with Guaranteed-Solvable Generation

Lights Out is a classic electronic puzzle from the 1990s: a grid of lights where clicking one tile toggles it and its orthogonal neighbors, and the goal is to turn every light off. It is a pure logic puzzle rooted in linear algebra over GF(2) — every toggle is its own inverse, which makes it possible to generate a puzzle that is provably solvable without running any solver at all. This snippet implements a complete playable 5x5 board in vanilla JavaScript: click handling, the plus-shaped toggle rule, guaranteed-solvable puzzle generation, a move counter, and a best-score tracker.
The plus-shaped toggle rule
applyToggle(r, c) flips the clicked cell and its four orthogonal neighbors (up, down, left, right — never diagonals), skipping any neighbor that falls outside the SIZE x SIZE grid. This five-cell "plus" pattern is the entire rule set of the game. Every other mechanic — solvability, the win condition, puzzle generation — follows directly from how this one function behaves.
Why random toggles from the solved state always produce a solvable puzzle
Because every toggle operation is its own inverse (applying the same toggle twice returns a cell to its original state) and toggles commute with each other, any sequence of toggles applied to the solved, all-off board can be undone by applying the exact same sequence of toggles again, in any order. generatePuzzle() exploits this directly: it starts from an all-off board, records a random sequence of cell coordinates in seed, and applies each one silently. The resulting scrambled board is guaranteed solvable — no backtracking solver or brute-force search is needed to prove it, because the seed sequence itself is a valid solution.
Reset versus New Puzzle
The two action buttons expose this property directly. "Reset puzzle" replays the same seed array from a cleared board, restoring the identical starting position without re-randomizing it — useful if you want to retry a specific puzzle after a failed attempt. "New puzzle" calls generatePuzzle() again, producing a fresh random seed and therefore a fresh solvable puzzle.
Move counting and win detection
Every click that isn't ignored (input is locked once won is true) increments moves and re-renders the grid. checkWin() runs after every move and simply checks whether every entry in the board array is false — Lights Out has no partial-credit state, only fully off or not yet solved. When the board clears, the game locks input, shows the final move count, and updates best if this run beat the previous record for the session.
Rendering as toggled CSS classes, not redrawn DOM
buildGrid() creates the 25 button elements once, and every subsequent update only toggles the .on class via render() — the DOM nodes themselves are never rebuilt during play. This keeps the click handler wiring stable and avoids the flicker or lost focus state that full re-renders can introduce in a rapid-click puzzle game.
Step by step
How to Use
- 1Click any lit tileClicking a tile calls applyToggle(r, c), which flips that cell and its up/down/left/right neighbors — never diagonals.
- 2Watch the move counterEvery accepted click increments the moves stat shown in the header, live.
- 3Turn every light off to wincheckWin() runs after each move and checks board.every(v => v === false). Clearing the board locks the grid and shows your final move count.
- 4Reset to retry the same puzzleClick "Reset puzzle" to replay the same random seed array from a cleared board — useful for re-attempting a puzzle you scrambled up.
- 5Generate a new puzzleClick "New puzzle" to call generatePuzzle() again, which applies a fresh set of random toggles from the solved state.
- 6Tune the grid sizeChange the SIZE constant in the JS panel to build a 3x3, 4x4, or larger board — the toggle and win logic scale automatically.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
applyToggle(r, c) flips the clicked cell and its up, down, left, and right neighbors (never diagonals), skipping any neighbor that falls outside the grid boundary. This five-cell plus pattern is the entire rule set of the game.
Every toggle in Lights Out is its own inverse and toggles commute, so any sequence of random toggles applied to the all-off solved board can always be undone by applying that exact same sequence again. generatePuzzle() records the random sequence it applies as seed, which doubles as a guaranteed valid solution.
Reset puzzle replays the stored seed array from a cleared board, restoring the exact same starting position. New Puzzle calls generatePuzzle() again to produce a brand-new random seed and a different scrambled board.
Yes. Change the SIZE constant in the JS panel. The board array, toggle logic, win check, and CSS grid-template-columns all reference SIZE, so a 3x3, 4x4, or 7x7 board works without further changes.
No, best is held in a plain JavaScript variable for the current session only. To persist it across visits, read and write the value to localStorage inside startPuzzle() and checkWin().
Yes, but this snippet does not compute it — it only verifies the seed sequence itself is a valid solution. Finding the minimum-move solution requires solving the underlying linear system over GF(2), which is a good extension exercise but outside the scope of the base game.