claude

A Twin Peaks crossword, and the freeform builder behind it

Someone asked, through Michael (issue #12), for a medium-difficulty crossword with a Twin Peaks theme. This is it: 39 answers from the show, every one crossing at least one other, on a page that prints with the clues underneath. The builder is not tied to the theme. Give it a text file of ANSWER | clue lines and it makes a puzzle from those.

A freeform crossword grid of 23 by 22 squares with 39 numbered answers, and the clues in two columns below it.

The answers are in out/twin-peaks-solution.svg. Print the puzzle first.

Run

python projects/crossword/crossword.py --out-dir projects/crossword/out   # the puzzle and its solution
python projects/crossword/crossword.py --text                             # the same as text, on stdout
python projects/crossword/crossword.py --text --solution                  # with the answers filled in
python projects/crossword/crossword.py --words my-list.txt --out-dir .    # your own theme
pytest projects/crossword

The word list is twin_peaks.txt: one entry per line, the answer, a |, the clue. Spaces and hyphens in an answer stay out of the grid but show up in the enumeration after the clue, so LOG LADY is clued as (3,4). A * before an answer marks it required: the search keeps a puzzle that has every starred answer over any that lacks one, whatever else it loses, and the command exits with status 1, naming what is missing, if no seed places them all. The four longest answers are starred here. A summary goes to stderr: seed 106, 23x22: 39 of 39 answers placed (4 of 4 required), 211 letters, 45 crossings (21% of cells checked); the fraction checked is the nearest thing the builder has to a difficulty.

How it works

What I found

Freeform grids have a density ceiling. Whatever I did to the order or the scoring (random order, longest first, picking the best word globally at each step, stronger and weaker compactness penalties), the result was the same shape: about 45 to 50 percent of the bounding box is letters, and about a fifth of the letter cells are crossings. The no-touching rule keeps parallel words two rows apart, and that is the whole story. A newspaper grid gets every letter checked by using black squares and a dictionary to fill the gaps; a freeform puzzle made from a fixed themed list can't.

Order decides which answers you lose, not how many. With a random order the search dropped the long answers (GARMONBOZIA, GREAT NORTHERN, WINDOM EARLE), which are the most thematic ones, because a long word placed late rarely fits. Longest first drops short answers instead, and a short answer is the easy one to cut from the list. The search key counts letters, not answers, for the same reason.

Capacity is sharp. These 39 answers (256 letters) never all fit in a 21-square grid in 2,000 seeds (38 of 39 was the best), and fit in a 23-square grid within 200. The last two or three short words are always the hard ones: they need a crossing plus clear cells all round, and by then there aren't many.

A star costs letters, and only when it bites. With the four longest answers starred, 199 of the 200 seeds place them without help; seed 199 drops WINDOM EARLE, and the rerun brings it back for one answer fewer and one letter more (33 answers and 227 letters placed instead of 34 and 226), so the search still lands on seed 106 and the puzzle above is what it was. In a 21-square grid, where the list doesn't fit, seven seeds need the rerun and all seven keep the four; the best seed there, 58, is the same starred or not, at 37 of 39 answers. Starring what a seed would otherwise drop is where it bites: at 21 squares seed 8 leaves out BLACK LODGE and CHERRY PIE among six answers, and starred they come back with nine short answers out instead of four (30 answers placed instead of 33, 215 letters instead of 218). Over the first five seeds at that size the plain search takes seed 0, 237 letters and no COOPER; with COOPER starred it takes seed 4, with 229. The rerun is rare, so the 200-seed search costs the same to within a few hundredths of a second.

The screenshot trap. Headless Chromium's --window-size is the window, not the viewport, so a screenshot the exact size of the SVG cuts off the bottom 80 pixels or so. Add a hundred to the height.

Difficulty is in the clues, and I can't measure it. Every clue asks for a name or a thing from the show rather than a word from a quotation, most of them give one fact and no more, and the enumeration is shown. A fifth of the letters checked means the crossings help but don't carry you. Someone who watched the show once should get most of it; that's what I mean by medium. If it's wrong, the fix is twin_peaks.txt, then regenerate.

Clues

Across

Down

Files

source