{
  "id": 321188,
  "title": "Designing a Solvability Gate for 15 Puzzle Implementations",
  "url": "https://urgent.news/2026/08/08/designing-a-solvability-gate-for-15-puzzle-implementations",
  "topic": "finance",
  "section": "Finance & Markets",
  "published": "2026-08-08T20:02:54.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/lizely/designing-a-solvability-gate-for-15-puzzle-implementations-2adb"
  },
  "original_language": "en",
  "account": "The 15-puzzle implementation always asks the same fundamental question: given a starting arrangement, can the player ever reach the goal state? At first glance, this may seem like a random guessing game, but there is a clear mathematical rule that determines this. This rule splits the puzzle's state space into two connected components based on two key factors: the parity of the permutation (the number of inversions) and the row of the blank tile, counted from the bottom of the 4x4 grid. The board is solvable if and only if either of the following conditions are met:\n\n1. The blank is on an even row from the bottom and the inversion count is odd.\n2. The blank is on an odd row from the bottom and the inversion count is even.\n\nImplementations often get this rule wrong by including the blank in inversion counting, counting the blank's row from the top instead of the bottom, or by incorrectly alternating the checks. These implementation flaws can cause the gate to accept unsolvable boards or reject solvable ones. To avoid these issues, treat the two checks as a single boolean value and thoroughly unit test each branch.\n\nIn real-world production systems, this simple parity check needs to be expanded to cover additional scenarios like resume from corrupt saves, drag-and-drop moves that bypass the grid model, and hint features that propose illegal moves. The solution to all these problems is to have a single function `isSolvable(state)` that checks every transition that changes the board state and never exposes the underlying state object to other modules without that wrapper.\n\nThe provided TypeScript code implements this logic in a single, testable function `isSolvable()`. It first flattens the 2D board representation into a 1D array, then counts the number of inversions and determines the blank tile's row from the bottom. Finally, it returns true if the combination of inversion count and row parity meets the solvability criteria.\n\nTo further ensure the shuffle primitive never returns an unsolvable board, a rejection sampling approach is recommended. This generates random boards until it encounters one that passes the `isSolvable()` check. While rejection sampling may not be the most efficient method for all use cases, it guarantees legality for small to medium-sized puzzles.",
  "summary": "Every 15-puzzle implementation eventually hits the same quiet question: given a starting arrangement, can the player ever reach the goal? The answer is not obvious. Random shuffles fail roughly half the time, and a permissive build that lets the player play an unsolvable board wastes the user's evening. A strict gate that blocks too many starts kills engagement. Engineers building or integrating…",
  "key_points": [],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}