Eternity II states cleanly as an exact-cover problem, and Knuth's Algorithm X with dancing links is the classic machine for those. Where it genuinely shines (small boards, exhaustive counting) and the two reasons it does not crack the 16×16: an unshrunk search tree, and no partial credit.
Some puzzles have to be bent to fit a formalism; Eternity II drops into exact
cover almost by itself. An exact-cover problem asks: given a set of items and
a collection of options, each option covering some items, pick options so that
every item is covered exactly once. Pentomino tilings, sudoku and the
n-queens problem are the textbook customers, and edge matching is next door.
Take 512 items: one per cell ("cell c is filled") and one per piece
("piece p is used"). Each option is a concrete placement, piece p at
cell c in rotation r, covering exactly two items, its cell and its
piece. A selection of options covering every item exactly once is precisely a
board with every cell filled and every piece used once.
What pure exact cover cannot say is that touching edges must agree. Knuth's
own extension handles it: XCC, exact covering with colours, where secondary
items (here, one per interior edge of the grid) carry a colour, and two
options may share a secondary item only if they assign it the same colour. A
full XCC solution is then exactly a perfect Eternity II board. The encoding
is faithful: nothing about the puzzle is approximated away.
Knuth's Algorithm X solves exact cover by disciplined trial and error: pick
the item with the fewest remaining options (the fail-first rule), try each
option that covers it, remove everything that option makes impossible, and
recurse; on failure, restore and try the next.
Dancing links (DLX) is the data structure that makes the restore step
beautiful. The matrix of options lives in circular doubly linked lists, and
removing an element is two pointer writes, left.right = right; right.left = left, which leaves the removed node's own pointers
intact. Undoing the removal is the same two writes reversed. Backtracking
becomes pointer surgery with zero copying, and the memory touched is exactly
proportional to work done. It is one of the most elegant algorithms in the
combinatorial toolbox, and Knuth's paper is a genuine pleasure to read.
Here is the complete search on the exact instance Knuth uses in the paper:
seven items A–G, six options R1–R6, exactly one solution. Step through it and
watch the three moves that make up the whole algorithm: choose the emptiest
column, cover it (columns and rows detach from the lattice), and, on failure,
uncover in reverse (the same links splice back). The dead end at column E is
the moment worth stepping slowly: everything the wrong guess removed comes
back in exactly the opposite order, for two pointer writes per link.
▶Interactive: dancing links cover and uncoverExplore →
Algorithm X with dancing links — cover, recurse, uncover
Knuth's own example: 7 items A–G, 6 options R1–R6, exactly one exact cover. Step through the search: the column with fewest 1s is chosen (fail-first), covering it detaches columns and rows from the lattice of links, a dead end triggers the dance — the same pointer writes, undone in reverse — and the search resumes where it left off.
Columns = items to cover, rows = options. Faded = detached from the link lattice. The count under each letter is the number of live options covering it.
depth 0○○○0 solutions
Partial solution
(empty)
The full matrix: every option linked into every item it covers. No choice made yet.
Note what never happens: no copying, no rebuilding, no scanning for what to
restore. The removed nodes keep their own pointers while detached, and that
is the entire trick, so undoing a cover is as cheap as doing it.
The same run, in words. The matrix is R1 {C,E,F}, R2 {A,D,G}, R3 {B,C,F},
R4 {A,D}, R5 {B,G}, R6 {D,E,G}:
Choose column A. Two live options, tied for fewest; fail-first says
branch on the most constrained item. Try its first option, R2 {A,D,G}:
cover columns A, D and G. Every row touching them (R2, R4, R5, R6)
detaches.
Recurse. Only R1 and R3 survive. Column B now has a single live
option, so choose it, try R3 {B,C,F}: cover B, C, F, which detaches R1.
Dead end. Column E is still uncovered and no live option covers it.
This branch can never complete, so no deeper search is even attempted.
The dance. Uncover F, C, B, then G, D, A, exact reverse order, each
restoration the mirror image of the removal. The matrix is bit-for-bit
back to its initial state, without having been saved anywhere.
Try A's other option, R4 {A,D}. Cover A, D. Column E now has exactly
one live option, R1 {C,E,F}: cover C, E, F. Column B has one option left,
R5 {B,G}: cover B, G.
No columns remain, every item covered exactly once. Solution:
R1 + R4 + R5, found at depth 3 with a single dead end. The search then
unwinds fully, verifies no other branch exists, and reports exactly one
solution, and that certainty of exhaustion is the whole product.
The problem is NP-complete. Exact cover is on Karp's original 1972
list, so no polynomial algorithm is known for it, and Algorithm X is
exponential in the worst case: it is a complete backtracking search, and
its tree can grow like the product of the branching at each level.
The data structure is what's cheap. DLX makes each link removal and
each restoration O(1): two pointer writes, with an exact undo, so the
total time is O(1) per link touched, proportional to the tree the search
actually explores. Dancing links buys a superb constant factor and zero
restore cost; it does not shrink the tree by a single node.
On Eternity II's scale: the XCC matrix itself is perfectly manageable, with 512
primary items (256 cells + 256 pieces), 480 secondary items for the interior
edges and their 22 colours, and at most 256×256×4=262,144
options before symmetry and border pruning. Building it is minutes of work.
The tree above it is the wall: with no forced moves
the branching stays wide all the way down, over a space usually estimated
near 10100, and O(1) per node times an astronomical node count is
still astronomical. That is the precise sense in which DLX is the right tool
for small boards and the wrong weapon for the 16×16.
DLX is the right tool when you want all solutions, or a count, or a proof
of uniqueness, on instances small enough to exhaust. On small edge-matching
boards it does exactly that: complete enumeration with excellent constant
factors, no solution missed, no state repeated. Counting complete tilings of
clue-sized regions, verifying that a generated mini-puzzle has a unique
solution, cross-checking another solver's exhaustive counts: this is its
home turf, and nothing heuristic competes there.
One project note from the trenches, offered as a warning rather than a
result: a from-scratch XCC implementation here validated cleanly on n-queens
and trivial boards, then spent days broken on anything larger, because the
interaction between colour-driven purification and cover/uncover restoration
is genuinely subtle. If you build it, follow Knuth's published algorithm to
the letter; the days lost above were this project's own to lose.
Two independent walls, either of which would suffice.
The tree is the same tree. Exact cover re-describes the search; it does
not shrink it. The fail-first item choice is a good variable ordering, but
no move is ever forced on this puzzle: the
least-covered item still offers dozens of live options deep into the search,
so the branching stays enormous all the way down, over a space usually
estimated near 10100 boards. A DLX engine walks that tree completely or
not usefully at all; and its pointer-chasing inner loop is memory-bound,
an order of magnitude behind the cache-tuned array engines the
record backtrackers use.
No partial credit. DLX answers one question: covered exactly, or not.
The entire Eternity II economy runs on partial scores (467, 469, 470)
and on searches that deliberately tolerate a few mismatched edges to get
there. Exact cover has no native way to leave an edge unmatched and pay a
penalty; relaxing it that far means rebuilding it into a branch-and-bound,
at which point the elegance that justified it is gone.
Keep DLX on the shelf for what it is: the exhaustive instrument. Counting,
uniqueness proofs, ground truth on small boards: unbeatable, and worth the
implementation care it demands. As an attack on the full puzzle it is a
dead end, for structural reasons no constant
factor will fix: the tree it must exhaust is astronomical, and the
partial-score game every record method plays is one it cannot enter.