A unique solution is not a solvable puzzle

I spent a while generating puzzles that had exactly one answer and were, in practice, unplayable. Getting from one to the other turned out to be the whole problem — and the fix made the generator 500 times faster, which was not what I expected from a change made for a completely different reason.

The rules

Hexhive is Queens on a hexagonal board. On a hexagon of side n you place 2n−1 crowns so that:

Three axes instead of two is what makes it a different puzzle rather than the same puzzle with new art. On a square board the two axes are independent in a way that gives you a lot of free deduction. On a hexagon the third axis cuts across both, and the constraint propagates differently.

The obvious generator, and why it does not work

The obvious approach: find a random solution, grow colour regions around the crowns, then keep adjusting regions until the board has exactly one solution. That last step is genuinely necessary — purely random regions land on a unique solution often enough at side 4 or 5, but at side 6 I tried three thousand times without a single hit.

So you repair until unique. And then you have a puzzle nobody can solve.

Measuring the old generator — how many puzzles required guessing at some point:

side 4    60% of puzzles required trial and error
side 5   100%
side 6   100%, and 45% of all solving steps were trial and error

This is not a bug in the repair loop. It is what the repair loop is for. "Adjust regions until a second solution no longer exists" walks the board to the boundary where uniqueness is barely true — and barely-unique is exactly the shape that has no human-legible chain of reasoning through it. The answer is pinned down, but only by exhaustion. A person sits there, finds no forced move, and guesses.

Uniqueness is a property of the answer. Solvability is a property of the path to the answer. Optimising for the first tells you nothing about the second.

Repairing toward solvability instead

The fix is to add a second repair phase with a different target. After the board is unique, run a solver across it from an empty board using only the rules a person can follow:

If it solves the board, keep it. If it stalls, look at where it stalled: take the region closest to being forced — fewest candidates left — and hand one of its non-answer candidate cells to a neighbour. Then run the solver again. Each pass nudges the board toward being deducible.

The thing I did first, and should not have, was generate-then-filter: make a puzzle, test whether the simple rules crack it, throw it away if not. At side 6 about 0.04% of puzzles pass. That is roughly 30 seconds per puzzle.

Repairing toward the target instead:

ApproachTime per puzzle at side 6
Generate, test, discard~30 s
Repair toward deducibility56 ms

About 500× faster, and I did not make the change for speed. I made it because generate-and-filter was producing bad puzzles slowly and I wanted it to produce good ones. The speed was a side effect of no longer throwing away almost everything I made.

Uniqueness comes free

Here is the part I did not see coming.

If the simple rules can solve the board from empty, then every crown was forced — each one is the only cell that could have gone there, given the constraints and the deductions before it. A forced chain cannot have branched. So the solution is unique, necessarily, without checking.

The code still verifies it. But that check is now insurance rather than a requirement, and the expensive uniqueness-repair phase mostly stopped mattering: by the time a board is deducible it is almost always already unique.

I had the dependency backwards for weeks. I thought uniqueness was the hard constraint and solvability was a nice-to-have on top. It is the other way round.

The hint engine is the same program, pointed the other way

The solver that decides whether a puzzle is deducible is the same one that gives hints during play. That is not code reuse for its own sake — it is what makes the guarantee real. The generator only ships a board the hint engine can talk its way through, so the hint engine can always say something useful.

Two decisions in it were less obvious than they look.

The rules are ordered by how hard they are to understand

Not by how powerful they are, and not by what kind of answer they produce. There are seven rules, and they are tried from most legible to least:

#Rule
1Two placed crowns clash — same line or same region
2A crown is somewhere the answer cannot have it
3A region is confined to one line
4A line is confined to one region
5A crown here would starve some region or line of every cell
6A crown here leads to a contradiction further down
7Only one cell left in a region or line — that is a crown

"This region has only one cell left" is easier to follow than any lookahead, so it must be reachable before the lookahead rules get a turn. Order them by result type instead and you get hints that explain a forced move through trial-and-error reasoning when a one-line explanation was sitting right there. The hint would be correct and useless.

Rule 6 is trial and error wearing a suit. The generator now guarantees puzzles that do not need it — across the test set it never fires — but it stays in as a backstop.

The engine does not trust the player's own marks

Players cross out cells as notes. Those crosses can be wrong. If the hint engine treated them as facts it would cheerfully derive a hint from a player's mistake and be confidently, unhelpfully wrong.

So it maintains its own exclusion set, derived only from the rules. The player's crosses are folded in as extra knowledge and the engine keeps going from there — which is what makes a hint the next step you have not worked out yet, rather than a step you already took.

What it does when it finds nothing

It says so, and it does not charge you a hint. A board with no forced move left usually means a crown is in the wrong place; telling you "nothing more can be worked out right now" is more honest, and more useful, than inventing something.

Checking that it actually holds

Forty-five puzzles — fifteen each at sides 4, 5 and 6 — solved from an empty board by doing nothing but pressing the hint button:

all 45 solved
no unsound suggestions
never fell back to revealing an answer
rule 6 (trial and error) never fired

average hints to solve:  12.5 / 15.4 / 17.7

The interesting column is the last one. A puzzle that takes 15 forced steps to solve has 15 things in it a person can find. That is a measure of how much game is in the game, and it comes out of the same solver.

A smaller lesson, about the hint panel

Worth recording because I got it wrong twice in the same way.

On a phone the hint panel has to appear somewhere, and the board already fills the screen. First attempt: add 300px of bottom padding to the game area and scroll the target cell into view. Second attempt: measure the space left and shrink the board to fit the panel beside it.

AttemptWhy it failed
Pad and scrollEvery button jumps away from the player's thumb the moment the hint opens, and the board stays scrolled after it closes
Shrink the boardPage stops moving, but the board changes size mid-game, which is just as distracting

Both attempts were trying to make room. Both moved the thing the player was looking at. The version that works moves nothing at all: the panel floats over the board, translucent, with a very light blur behind the text — deliberately light, because blurring hard enough to make the text comfortable defeats the point of being able to see through it.

It covers the button row for a few seconds. That is the cost, and it is cheaper than the alternatives: it closes itself after five seconds, or on Esc, or the moment you touch the board.

Where this leaves it

The generator and the hint engine are one solver used twice — once to decide whether a board deserves to exist, once to explain it while you play. Every puzzle Hexhive ships can be reasoned out with rules a person can hold in their head, because that is the condition it had to pass to be generated at all.

Hexhive is a free iOS puzzle: a new board every day in three sizes, unlimited random ones, and a hint that explains itself.

More about Hexhive