Long-term readers (given the radio silence, do I have any other type?) will remember that I wrote a Puzzle Solving Framework to help solve “newspaper” pencil-and-paper puzzles. I’ve been thinking of preparing a presentation about it for a local Python group, so I decided to implement another one.

## Introducing Light Up

Light Up is an example of such a puzzle. Simon Tatham’s Portable Puzzle Collection includes an online version (amongst others).

## Quick Rule Summary

The puzzle is presented as a grid of white and black cells. The black cells are pre-set – like in a cross-word. Some black cells are marked with a number from 0 to 4.

The task is to mark all the white cells as either empty (indicated with a small rectangular dot) or containing a light globe (indicated with a large circle).

Lights illuminate all white cells squares in their line-of-sight in four directions, until blocked by a black square.

The constraints are:

- Lights may not be in direct line-of-sight of other lights.
- All white cells must be illuminated.
- The numbered blocks must be directly adjacent (i.e. four directions) with that many lights.

Go try some now, before the spoilers.

## Tactics

Here’s an example screen-shot (from my own program) of an unsolved puzzle.

Where can we start to solve this puzzle?

### Tactic A

We can mark the cells around the 0 cell as empty. There can’t be any lights around it.

More generally, if the number on a cell equals the number of adjacent lights already known, any remaining adjacent cells must be empty.

If I teach the solver this rule, it can solve this much of this puzzle already.

### Tactic B

Now consider the block numbered with a 3. There are only three remaining places a light could go, so they all must be lights.

More generally, if the number on a cell subtract the number of adjacent lights already known equals the number of adjacent unknown cells, they must be lights.

These rules apply recursively. If I teach the solver this rule, it can solve this much of the puzzle already:

**Legend:** The yellow areas indicate a cell is illuminated.

### Tactic C

Now consider the unlit, empty cell halfway up the left side.

There is only one possible place a light could go to light this cell – the bottom left.

More generally, if an unlit cell has only one unknown cell in its line-of-site. Than unknown cell must be a light.

Once we implement that rule, the final three cells are all marked as lights.

Ta daaa! A solver! Exciting!

Wait… Does it solve every puzzle?

Lets try this one:

How far do we get with Tactics A, B and C?

Oh dear! Not very far at all. Time to add a new tactic.

### Tactic D

This one is a little more complicated. Consider the cell in the bottom right corner. If it was a light, the light would illuminate two of the direct neighbours of the 3 cell nearby, making it impossible for the 3 cell to be satisfied.

More generally, if all but one of the remaining direct neighbours must be lights, then a “diagonal” cell sitting between two unknown cells must be empty.

This gives us this extra information:

If we then recursively apply all these tactics…

Ta daa! We have a solver! Wait, we’ve been here before. Does it really solve every puzzle? What about this one?

All the tactics lead to…

Nope. Not solved.

## Drawing the Line

This is the point where I stop. I don’t have any more simple crank-turning rules to hand. I could brute-force it, but that is against everything I stand for. I could look for more simple rules, but eventually I am going to fail, because Light Up turns out to be NP-complete.

So, that’s the point where I declare this code isn’t a Light Up *solver*, it is a Light Up *helper*, taking away the mundane part of the puzzle and leaving the user to solve only the hard parts.

(Just so we are clear: I am declaring this a rousing success – especially as it only took an evening to knock out, with the framework.)

## Implementation Details

There was very little novel here compared to other puzzles I have discussed before.

It came in at about 400 lines, give or take, for the solver, and another 270 for the GUI.

Each cell was either a Blank Cell (to be solved), a Blank Block or a Numbered Block.

Numbered Blocks were configured with all 8 of their immediate neighbours. Each numbered block was responsible for implementing tactics A, B and D.

Blank Cells were configured with an (unordered) set of all of the other blank cells in their line-of-site. Each Blank Cell was responsible for implementing Tactic C. If it was marked as a light, it was also responsible for informing all of its line-of-site neighbours that they were empty.

Part of the elegance of the implementations of other puzzles is that each cell was responsible for figuring out its own constraints, by inspecting its neighbours. Unfortunately, this implementation wasn’t as elegant. Each of the tactics resulted in a conclusion being formed, not about the cell implementing it, but about one of its neighbours. e.g. when a Numbered Block applied Tactic A, it did not change its own state, but changed the state (via a method call) on the blank cells near it.

One minor novelty is that I added toggles to turn one or off particular tactics, so I could illustrate progress more clearly, and a screen-shot function.

## Conclusion

Another success for the Puzzle Solving Framework, but not a terribly exciting one.

You must be logged in to post a comment.