Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Murdle Solver

A solver for Murdle-style logic grid puzzles. Pure Python, no dependencies.

Usage

./murdle_solver.py example.txt
suspects         weapons       places
Miss Saffron     pool cue      lobby
Sgt Gunmetal     dumbbell      gym
Coach Raspberry  poisoned tea  pool

Run the tests with python3 -m unittest.

Puzzle format

A puzzle file declares its categories first, then one clue per line. # starts a comment.

suspects: Miss Saffron, Sgt Gunmetal, Coach Raspberry
weapons:  dumbbell, pool cue, poisoned tea
places:   gym, pool, lobby

Sgt Gunmetal + dumbbell                       # these go together
not Coach Raspberry + (gym | lobby)           # so Raspberry was at the pool
pool cue + (pool | lobby)                     # the cue was at one of the two
Miss Saffron + pool cue or Sgt Gunmetal + pool cue

Every category must list the same number of items, and every item name must be unique across the whole puzzle. A row of the answer grid is one item from each category — the suspect, the weapon they used, where they were, and so on.

Clues

clue   := option ("or" option)*
option := ["not"] term ("+" term)*
term   := item | "(" item ("|" item)* ")"
Syntax Means
A + B some row contains both A and B
A + B + C some row contains all three
not A + B no row contains both — not negates the whole line
A + (B | C) some row contains A and at least one of B or C
A + B or C + D at least one of the two holds

& is a synonym for +, | and either ... or are synonyms for or, and inside parentheses a comma works like |. So not A + (B, C) reads as "A was with neither B nor C".

Note that not distributes over the whole option: not A + B is "never A and B together", not "A with something other than B".

Item names

Names are matched loosely, so clues can read the way the puzzle does. Case and extra whitespace are ignored, a leading the is optional in either direction, and any unambiguous substring works:

places: the boat, the lighthouse

the lighthouse + revenge
lighthouse + revenge      # same clue

An unknown or ambiguous name is an error naming the line and the candidates, rather than a wrong answer — so a typo can't quietly change the puzzle.

How it works

Depth-first search over the answer grid, one cell at a time in row-major order.

The first category is pinned to the identity assignment: rows have no inherent order, so row r is defined as the row holding item r of the first category. That removes the row permutation symmetry, leaving (n!)^(k-1) candidate grids for k categories of n items.

After every single cell assignment, each clue is tested for whether it can still be satisfied — a positive clue fails once no row could possibly match it, and a negative one fails once some row definitely does. That test is monotone: a clue that has become unsatisfiable can never recover as more cells are filled in, so failing it is a sound reason to backtrack immediately, and once the grid is full the test is exact. In practice this settles a full 5×5×5×5×5 puzzle instantly.

An underconstrained puzzle can have an enormous number of solutions, so the solver stops after MAX_SOLUTIONS (1000) and says so.

About

Murdle puzzle solver

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages