Source Code

<div class="ng-app">
  <div class="ng-header">
    <h2>Nonogram</h2>
    <div class="stats">
      <div class="stat"><span class="stat-label">Filled</span><span class="stat-val" id="ng-filled">0</span></div>
      <div class="stat"><span class="stat-label">Mistakes</span><span class="stat-val" id="ng-mistakes">0</span></div>
    </div>
  </div>

  <p class="ng-goal" id="ng-goal">Left-click to fill a cell, right-click (or long-press) to mark it empty. Match the row and column clue numbers.</p>

  <div class="ng-board" id="ng-board"></div>

  <div class="ng-actions">
    <button class="ghost-btn" id="ng-new">New Puzzle</button>
    <button class="ghost-btn" id="ng-clear">Clear Marks</button>
  </div>
</div>

Nonogram Puzzle Game — Free HTML CSS JS Snippet

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

What's included

Features

Row and column clues computed live from the solution grid via a run-length function — never hand-typed or able to drift out of sync
Three-state cells: untouched, filled, and marked-empty, each with distinct styling
Left-click fill gives immediate right/wrong feedback and increments a live mistake counter on an incorrect fill
Right-click (or long-press) toggles a mistake-free empty mark for logically eliminated cells
A small pool of hand-authored 6x6 solution grids selected at random for real replay variety
3x3 heavier grid-line dividers matching standard nonogram readability conventions
Win detection compares the full grid against the true solution, not just the filled-cell count
Context menu suppressed on right-click so marking works cleanly without an unwanted browser menu

About this UI Snippet

Nonogram Puzzle Game — Picross Picture Logic Puzzle with Auto-Generated Clues

Screenshot of the Nonogram Puzzle Game snippet rendered live

A nonogram (also called picross or griddler) is a picture logic puzzle: every row and column is labeled with a sequence of numbers representing the lengths of consecutive filled-cell runs in that line, and the solver must deduce which cells to fill purely from those numbers until a hidden picture emerges. This snippet implements a real, playable 6x6 nonogram — clues are computed programmatically from an actual solution grid, not hand-typed, and the win condition checks the full grid against that solution.

Clues are derived from the solution, not authored separately

Rather than storing clue numbers as separate data (which risks the clues and the picture disagreeing), runsFor(line) computes them directly from a boolean row or column: it walks the line counting consecutive 1s, pushing each run's length to an array whenever a 0 (or the line's end) breaks a run. rowClue(r) calls this on solution[r] directly; colClue(c) first transposes a column out of the row-based solution array with solution.map(row => row[c]) and runs the identical function. This guarantees the displayed clues are always exactly correct for whichever solution grid is loaded — there is no possibility of the clues and the actual picture drifting out of sync.

Two-state cell interaction: fill and mark-empty

A cell has three possible states — 0 (untouched), 1 (filled), 2 (marked empty, shown as an X). Left-click (onCellClick) fills a cell if the solution says it should be filled, or marks it empty (and increments the mistake counter) if it shouldn't — giving immediate right/wrong feedback rather than letting an error sit undetected until the final check. Right-click (onCellRightClick, with preventDefault() to suppress the browser context menu) toggles a manual empty-mark independently of correctness, which is how experienced nonogram solvers cross off cells they've logically eliminated without committing to filling them.

A small pool of hand-authored solutions

PUZZLES holds several complete 6x6 boolean solution grids, each hand-drawn to form a recognizable simple shape. newPuzzle() picks one at random, so replaying the game produces genuinely different puzzles rather than the same picture every time, while every puzzle in the pool is guaranteed solvable and correctly clued since the clues are always derived live from whichever grid was picked.

3x3 block dividers for readability

Standard nonograms mark every third row and column with a slightly heavier grid line so the eye can count cells in groups instead of one at a time — this snippet reproduces that with .block-r/.block-b classes applied every third cell, purely a readability aid with no effect on game logic.

Win detection against the true solution

checkWin() compares every cell's state against solution directly — a cell counts as solved only if state[r][c] === 1 exactly matches solution[r][c] === 1; marked-empty cells (state === 2) are correct as long as the solution agrees they should be empty. The moment every cell matches, the puzzle is marked won and a message reports the player's total mistake count.

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 how runsFor() turns a boolean row or column into the clue numbers shown in the header, and why deriving clues from the solution at render time instead of storing them separately eliminates an entire class of "clues don't match the picture" bugs. It's also a good candidate for extension — ask it to add a solvability checker that verifies each new puzzle has a logically deducible (not just guessable) solution, add a hint button that reveals one correct cell, or generate a much larger pool of solution grids algorithmically instead of hand-authoring each one.

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 playable nonogram (picross) picture logic puzzle in plain HTML, CSS, and JavaScript — no libraries.

Requirements:
- Store one or more complete boolean solution grids (e.g. 6x6 arrays of 0/1) representing hidden pictures, and pick one at random each new game.
- Compute every row's and every column's clue numbers programmatically from the solution grid itself using run-length logic (consecutive filled-cell run lengths, in order) — do not hand-author or separately store the clue numbers, since they must always exactly match whichever solution is loaded.
- Render the row clues to the left of the grid and the column clues above it, in a layout where the clue labels stay aligned with their corresponding grid row or column.
- Support three cell states: untouched, filled, and marked-empty. Left-clicking an untouched cell fills it if the solution says it should be filled, or marks it empty (with a visible X) and increments a mistake counter if the solution says it should not be filled. Right-clicking (with the browser's default context menu suppressed) toggles a manual empty-mark on any untouched or marked cell without ever counting as a mistake.
- Detect the win condition by comparing every cell's current state against the true solution grid, and the instant every cell agrees, show a clear "solved" message including the total mistake count for that puzzle.
- Add a heavier grid line every third row and every third column purely for visual grouping, matching standard nonogram readability conventions.
- Include a "New Puzzle" control that picks a new random solution and fully resets the board, and a separate "Clear Marks" control that resets only the current puzzle's cell states without changing which solution is loaded.

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
    Read the row and column clue numbersEach number sequence tells you the length of consecutive filled-cell runs in that row or column, in order.
  2. 2
    Left-click to fill a cellIf the cell should be filled, it turns indigo. If not, it is marked with an X and counts as a mistake.
  3. 3
    Right-click to mark a cell emptyCross off cells you have logically ruled out without risking a mistake — this toggle does not affect your mistake count.
  4. 4
    Watch the filled and mistake countersThe header tracks how many cells are currently filled and how many incorrect fills you have made this puzzle.
  5. 5
    Solve to reveal the pictureThe puzzle is automatically detected as solved the instant every cell matches the hidden solution grid.
  6. 6
    Load a new puzzleClick "New Puzzle" to draw a different random solution from the built-in pool, or "Clear Marks" to reset the current puzzle's cells without changing the picture.

Real-world uses

Common Use Cases

Logic puzzle game collections
A picture-deduction puzzle structurally different from stacking or matching games, pairing well with the Mini Sudoku Game or Lights Out Puzzle Game in a puzzle games section.
Teaching run-length encoding
runsFor() is a compact, readable real-world example of run-length encoding applied to a boolean sequence — a useful concept well beyond nonograms alone.
Daily puzzle or brain-training apps
The random-pick-from-a-pool pattern in newPuzzle() is a direct starting point for a "puzzle of the day" feature once expanded with a larger authored puzzle set.
Reference for solution-derived UI
Deriving displayed clues from the underlying data (rather than storing them redundantly) is a broadly useful pattern for any UI that must never let its labels drift out of sync with its source data.
Grid-with-margin-labels layout reference
The corner/column-clues/row-clues/grid four-quadrant CSS grid layout is reusable for any spreadsheet-like UI needing synchronized row and column headers.

Got questions?

Frequently Asked Questions

runsFor(line) walks a boolean array counting consecutive filled (1) cells, pushing each run's length to a results array whenever a 0 or the end of the line breaks the run. rowClue() calls this directly on a solution row; colClue() first builds a column array with solution.map(row => row[c]) and runs the identical function, so clues are always computed live from the true solution rather than authored by hand.

Left-click attempts to fill a cell: if the solution agrees it should be filled, it fills correctly; if not, it is marked with an X and counts as a mistake. Right-click toggles a manual empty-mark (also shown as an X) that never counts as a mistake, regardless of whether the cell should actually be filled — this is the standard nonogram technique for crossing off cells you have deduced must be empty.

checkWin() compares every cell in state against the true solution grid: a cell counts as correctly solved if being filled (state === 1) exactly matches whether the solution says that cell should be filled. The instant every cell in the grid agrees, the puzzle is marked won.

Add another 6x6 array of 0s and 1s to the PUZZLES array in the JS panel — no separate clue data is needed, since rowClue() and colClue() compute clues automatically from whatever solution grid is loaded.

Yes — change the SIZE constant and add correspondingly larger (SIZE x SIZE) solution grids to PUZZLES. The clue-generation, rendering, and win-check logic are all written generically against SIZE rather than hardcoded to 6.

The filled counter (from state.flat().filter(v => v === 1).length) shows raw progress, while mistakes only increments on an incorrect left-click fill — separating "how much have I done" from "how accurately am I solving it" gives more useful feedback than either number alone.