Source Code

<div class="lo-app">
  <div class="lo-header">
    <h2>Lights Out</h2>
    <div class="stats">
      <div class="stat"><span class="stat-label">Moves</span><span class="stat-val" id="lo-moves">0</span></div>
      <div class="stat"><span class="stat-label">Best</span><span class="stat-val" id="lo-best">--</span></div>
    </div>
  </div>

  <p class="lo-goal">Click a tile to toggle it and its neighbors. Turn every light off to win.</p>

  <div class="lo-grid" id="lo-grid"></div>

  <p class="lo-feedback" id="lo-feedback">Good luck.</p>

  <div class="lo-actions">
    <button class="ghost-btn" id="lo-reset">Reset puzzle</button>
    <button class="ghost-btn" id="lo-new">New puzzle</button>
  </div>
</div>

Lights Out Puzzle Game — Free HTML CSS JS Snippet

Lights Out Puzzle Game · Games · Plain HTML, CSS & JS · Live preview

What's included

Features

applyToggle() implements the classic plus-shaped neighbor toggle rule with edge bounds checking
Puzzles are generated by replaying random toggles from the solved state, guaranteeing solvability with no solver needed
Reset puzzle replays the exact same seed sequence; New puzzle generates a fresh random seed
Live move counter and a best-moves-to-solve record tracked for the session
Win detection via a single board.every() check — no partial-credit state
Grid is built once and updated by toggling CSS classes, not full DOM re-renders
Glowing lit-tile styling with inset box-shadow and CSS transitions for tactile feedback
Configurable SIZE constant scales the grid and all logic to any board dimension

About this UI Snippet

Lights Out Puzzle Game — 5x5 Toggle Grid with Guaranteed-Solvable Generation

Screenshot of the Lights Out Puzzle Game snippet rendered live

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.

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 exactly why applying random toggles from the solved board guarantees the resulting puzzle is solvable, and how that connects to Lights Out being a linear system over GF(2). It's also a great candidate for extension — ask the assistant to add a "shortest solution" solver using Gaussian elimination over GF(2) so you can display the true minimum move count, add a hint button that reveals one correct next move toward that optimal solution, or persist the best score per grid size in localStorage.

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 Lights Out puzzle game in plain HTML, CSS, and JavaScript — no libraries, no backend.

Requirements:
- Render a 5x5 grid of tile buttons, each either "on" (lit) or "off", built once and updated only by toggling a CSS class afterward.
- Clicking a tile must toggle that tile and its orthogonal (up, down, left, right — never diagonal) neighbors that exist within the grid bounds, using one shared toggle function.
- Generate a new puzzle by starting from the fully-off solved board and applying a random sequence of toggles to it, storing that exact sequence so it can be replayed later — explain in a comment why this guarantees the resulting puzzle is always solvable without needing a separate solver.
- Add a "Reset puzzle" button that restores the exact same scrambled starting position by replaying the stored random sequence from a cleared board, and a separate "New puzzle" button that generates a brand-new random sequence.
- Track and display a live move counter that increments on every accepted click, and detect a win the moment every tile in the grid is off, at which point further clicks should be ignored and the final move count shown.
- Track a best (lowest) moves-to-solve value across puzzles played in the current session and display it alongside the live counter.

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 any lit tileClicking a tile calls applyToggle(r, c), which flips that cell and its up/down/left/right neighbors — never diagonals.
  2. 2
    Watch the move counterEvery accepted click increments the moves stat shown in the header, live.
  3. 3
    Turn 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.
  4. 4
    Reset 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.
  5. 5
    Generate a new puzzleClick "New puzzle" to call generatePuzzle() again, which applies a fresh set of random toggles from the solved state.
  6. 6
    Tune 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

Brain-training and puzzle game collections
A self-contained logic puzzle that fits alongside other grid-based games like the sliding puzzle game or minesweeper in a games hub.
Teaching linear algebra over GF(2)
Lights Out is a textbook example of a system of equations over the field with two elements. Use this snippet as a concrete, interactive demo before introducing the underlying matrix theory.
Loading-screen or empty-state distraction
Drop the puzzle into an idle moment in a product — a waiting room, an empty dashboard state, or a loading screen — as a lightweight, dependency-free timekiller.
Reference implementation of state-toggle generation
The seed-and-replay generation technique (apply random moves from a known-solved state to guarantee solvability) generalizes to any puzzle built on invertible, commutative operations.
Neon-tile visual style reference
The glowing yellow lit-tile treatment with inset shadows is directly reusable for any toggle-grid or status-light dashboard visualization.

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.