DEV Community

SEN LLC
SEN LLC

Posted on

Canal View: a number only ever describes the near end of its rays, and one dry cell hides the rest

Canal View in the browser with a four-rung solver. The rule reads
"the number counts the black cells it can see in the four directions", and
the word doing all the work is see: a run stops at the first white cell,
and whatever is behind that cell is not counted. So a number is never a
statement about its row and column — only about the near end of them.
Measured over the 72 boards I ship: an 8×8 number says 3.84 on average while
5.28 flooded cells actually sit in its four rays, so it is looking at
72.8% of them, and at 12×12 that falls to 64.7%. On one 12×12 board a
single dry cell hides a run of 10 flooded cells from the number staring
straight at it. Count the sightlines twice and
4 × canal = seen + no-number-that-way + blocked-by-a-dry-cell comes out
exactly: 4,616 = 1,576 + 2,451 + 589 at 8×8.

Live demo: https://sen.ltd/portfolio/canal-view/
Source: https://github.com/sen-ltd/canal-view

Canal View

The rule

Flood some cells (shade them black, if you prefer the usual phrasing).

  • The flooded cells form one edge-connected group — the canal
  • No 2×2 block is entirely flooded
  • Numbered cells stay dry, and the number is the total of the flooded cells it can see: walk out in the four directions and add up the runs that start next to it

Three lines. What surprised me is that the thing carrying the difficulty is not
the numbers.

The word "see"

Walk a ray out of a number and, whatever the answer turns out to be, it has the
same shape every time: a flooded cells, then a dry cell, then anything at
all.

"Then anything at all" is the whole puzzle. Put eight flooded cells behind one
dry cell and the number still says zero. A number does not report the contents
of its row and column; it reports the near end of them.

board a number says flooded in its rays share it sees rays with canal behind the wall hidden cells, mean most hidden by one dry cell
8×8 3.84 5.28 72.8% 9.5 1.73 6
12×12 5.06 7.83 64.7% 25.5 2.34 10

The bigger the board, the more of its own row and column each number is blind
to. A 12×12 board has 25.5 rays with canal sitting behind the wall, and the
worst single dry cell in the bank hides a run of ten.

That is also what separates Canal View from the "see along the line" puzzles it
looks like on paper. The same 3 could mean "three in a row next to me" or "one
next to me, and I have no idea what is happening two cells further out".

Count the sightlines twice

Fix a flooded cell. The numbers that can see it are at most one per direction —
whichever number the run reaches first. So the pairs (number, flooded cell it
sees) can be counted two ways:

  • from the numbers, the total is the clue sum
  • from the cells, it is at most four per flooded cell

That is a floor on the size of the canal for free, and the 2×2 rule gives the
ceiling: the largest 2×2-free subset of an h×w grid is
hw − ⌊h/2⌋⌊w/2⌋ — take every other row whole and every other cell of the rows
in between.

⌈Σ clues / 4⌉ ≤ canal ≤ hw − ⌊h/2⌋⌊w/2⌋
Enter fullscreen mode Exit fullscreen mode
board floor from the clues actual canal ceiling from 2×2 clue sum ÷ canal
8×8 11 32.1 48 1.37
12×12 28 72.4 108 1.51

The sandwich is loose, and the reason it is loose is the previous section. It
would be tight if every flooded cell were seen from all four sides; measured,
a flooded cell is seen 1.37 times at 8×8 and 1.51 at 12×12. Split the missing
sightlines and every (flooded cell, direction) pair is exactly one of three
things:

board (cell, direction) pairs seen no number that way a dry cell in between blocked
8×8 4,616 1,576 2,451 589 12.8%
12×12 10,432 3,933 4,351 2,148 20.6%

Column two is exactly four times the canal and column three is exactly the clue
sum. Two completely different pieces of code produce those numbers, so the
identity is a test: npm test checks it board by board. Occlusion alone is
20.6% of all sightlines at 12×12, and it grows with the board.

Sorting flooded cells by how many numbers can see them, 96 of the 1,154 flooded
cells across the 8×8 bank are seen by nobody at all.

A ray is not 2^m cells, it is m + 1 states

"a flooded, then a dry cell, then anything" is also very convenient to
implement. A direction of length m has m + 1 states — the length of the arm
— rather than 2^m, and a number's whole cross is one bounded composition
of its value into four arms. That is the only thing the solver ever branches
on.

board numbers cells in a number's rays 2^cells compositions that fit
8×8 11.4 8.9 478 8.4
12×12 21.6 13 8,192 17.9

Which is why the complete solver is cheap: the median shipped 12×12 board
finishes in 7 search nodes, the worst in 319, at 22.2 ms a board.

The ladder is four rungs over those compositions:

  • arm — arc consistency on the compositions: throw away every arm assignment the marks already contradict, then keep what all the survivors agree on
  • block — and three corners of a 2×2 flooded makes the fourth dry
  • flow — and connectivity: two pieces of canal that can no longer meet is a contradiction, a cell the canal can never reach is dry, and a cell whose loss would cut the canal in two is flooded. Plus the sandwich above, as a bound on what is left
  • search — the complete solver, branching on the most constrained number
rung cells, 8×8 cells, 12×12 boards, 8×8 boards, 12×12
arm 54.6% 41.9% 0/36 0/36
block 63.7% 53.9% 0/36 0/36
flow 81.5% 67.5% 18/36 6/36
search 100% 100% 36/36 36/36

The canal is allowed to trap a lake

Canal View has no rule saying the dry cells have to join up. If you come from
Nurikabe you will add one without noticing. Real boards use the freedom
constantly:

board canal of the grid lakes boards with a lake independent loops boards with a loop
8×8 32.1 50.1% 3.5 36/36 2.1 35/36
12×12 72.4 50.3% 12.1 36/36 6.8 36/36

A canal with a lake in it is a canal with a cycle, and this is where "no 2×2"
earns its place: it is what lets a canal have a cycle without being thick. The
smallest shape that is 2×2-free without being a tree is the eight-cell ring
— the border of a 3×3 — and at eight cells it is the only one. Count fixed
polyominoes both ways and you can watch the two ideas separate:

cells fixed polyominoes no 2×2 block tree-like 2×2-free with a cycle
7 760 570 570 0
8 2,725 1,909 1,908 1
9 9,910 6,485 6,473 12
10 36,446 22,282 22,202 80

Columns two and four are published sequences —
A001168 and A066158
and npm run ledger refuses to write its output if the enumerator disagrees
with either. Column three, the one a canal actually has to satisfy, is neither,
and I could not find it in OEIS.

The canals themselves are counted too, in two independent ways — testing all
2^hw subsets, and growing sets outwards from an anchor — and the two counts
have to match. In a 4×5 grid, 116,166 connected sets, of which 25,822 (22.2%)
are 2×2-free.

What actually holds a finished board still

Take a shipped answer and flip one cell. All 6,301 single-cell flips across the
bank are illegal. The question is which rule catches them:

rule flips it kills flips only it kills
a number 5,298 2,974
the 2×2 rule 932 197
the canal joining up 2,395 806
nothing at all 0

15.9% of the flips are caught by the 2×2 rule or by connectivity and by
nothing else.
Drop either rule and that many single-cell edits of the answer
become legal immediately — which is exactly what the next table measures.

Four ways to misread it

misreading board boards intended answer still legal answers, median unique none
the 2×2 rule dropped 8×8 24 24/24 32 2 0
12×12 24 24/24 ≥2,000 0 0
the canal allowed to break up 8×8 24 24/24 1,248 0 0
12×12 24 24/24 ≥2,000 0 0
dry cells required to join up too 8×8 24 0/24 0 0 24
12×12 24 0/24 0 0 24
the number read as its whole row and column 8×8 12 0/12 0 0 11
12×12 12 0/12 0 0 12

The two halves fail in opposite directions, and both are nasty in their own
way. Dropping a rule leaves the intended answer legal and buries it under
hundreds of others: the board still solves, and nothing looks wrong until you
notice a second answer. Adding a rule that is not there — or reading the
number as its whole row and column — makes the intended answer illegal, and
the board becomes empty. That is the one where you spend an hour looking for an
answer that is not there.

The dial is how many numbers are left

The generator draws the answer first: grow a random connected, 2×2-free set,
write a number in every dry cell, then take numbers away one at a time for
as long as the answer stays alone.

A board with a number in every dry cell is saying its answer out loud. Rubbing
numbers out from there:

numbers kept, 8×8 drawn unique rate
every dry cell 108 108 100.0%
26 108 83 76.9%
22 108 54 50.0%
18 108 21 19.4%
15 108 6 5.6%
12 108 0 0.0%
10 108 0 0.0%

The shipped boards carry 11.4 numbers at 8×8 and 21.6 at 12×12. Cutting
down to 12 numbers at random on an 8×8 gave 0 unique boards in 108 draws; 22
numbers on a 12×12, also 0 in 108. Every number left on a shipped board is one
whose removal was tried and refused.

And the top row of that table has a trapdoor in it. Numbering every dry cell
is not quite a guarantee.
Of 200 freshly drawn 8×8 canals, 3 still had a
second answer, and in all 3 the cells that moved were exactly the ones no
number was looking at — a flooded cell nobody can see, which the canal can
afford to lose. You can write the whole answer down and still not have said it.

For completeness: numbers scattered at random, with values drawn either from
the small range real boards use or from the whole range their rays allow,
produced 0 unique boards in 1,600 draws. Most of them have no answer at
all — a board that was not built around an answer usually cannot be solved,
because the counts it demands and the canal that would have to carry them do
not fit together.

Two engines

The composition trick is the load-bearing idea, so it is checked against an
engine that has never heard of it: walk the cells in reading order, try flooded
then dry, prune only on a 2×2 that has already closed and on a number whose
count can no longer land, and check the finished grid against the rules
directly.

On the 30 6×6 boards small enough for brute force to finish — 1,939,359 nodes —
the two agree 30 times with 0 disagreements, and they agree on the answer
set, not only the count. The same check runs with the 2×2 rule dropped, where
the answers multiply into the hundreds: 30 agreements, 1,126 answers.

Wrap-up

Of the three lines of rules, the numbers are not the part that makes the board
hard. They see 65–73% of the canal in their own rays and the rest hides behind
a single dry cell. The board is still unique because connectivity and the 2×2
rule hold down what the numbers cannot see: 15.9% of all single-cell flips are
stopped by those two rules alone.

  • TypeScript and Vite, no runtime dependencies, 20 tests
  • The generator ships locally maximal boards: every further number removal was tried and refused
  • Every figure above comes out of npm run stats / npm run ledger, and the README and the page are written from that JSON rather than typed in

MIT licensed, and the demo has all 72 boards in it.

Top comments (0)