webdevpuneet.com

Nonogram Puzzle Game — Free HTML CSS JS Snippet

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

CategoryGames

Copy & paste

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>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

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.

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.