A simple solver for The Genius Star puzzle from happypuzzle.co.uk implemented in Haskell.
The solver tries to fill the grid by placing each of the eleven shapes in order (starting from the larger and more irregular ones) in all possible ways and backtracking when it is not possible to place the next shape.
The interesting part was choosing a representation of the grid and a
way to navigate around it. I've settled on the following idea. There
are three directions on the grid, which I arbitrarily called X
, Y
,
Z
, and each triangular cell has sides parallel to these directions.
Each cell has up to three neighbour cells obtained by reflecting it
with respect to its sides. The neighbours are described by the
function
neighbour :: Direction -> Int -> Maybe Int
where neighbour d n = Just m
if by reflecting the triangle
containing the number n
with respect to its side parallel to the
direction d
we obtain a triangle containing the number m
; and
neighbour d n = Nothing
if the side parallel to the direction d
lies on the border of the grid.
A shape is a set of triangular cells. We describe each shape by choosing a reference cell within it and defining all other cells in terms of sequences of reflections connecting each cell to the reference cell. For example, the shape
____
/\* /\
/__\/__\
\ /\ /
\/ \/
(with the reference cell marked with *
) is described by the
following sequences of reflections:
[[], [X], [Y], [X, Z], [Y, Z]]
Each reflection is identified by the direction with respect to which it is made. To try to place such a shape on a grid, we choose an empty cell as a reference cell and from it walk the reflection paths to find the numbers of the triangles covered by the shape. Of course, we can place a shape in different ways, rotating and flipping it. To accommodate for this, we compute for each shape the set of its possible orientations by applying all possible symmetries of the grid (rotations and reflections) to the sequences describing the elements of the shape.
With these choices, the rest of the implementation becomes an exercise
in list comprehensions. The function tryPlaceShape
checks if a
particular orientation of a shape (a particular collection of
reflection paths describing the elements of the shape with respect to
a reference cell) can be placed on a given grid (represented as a set
of numbers in the empty cells) using a given number as a reference
cell. The function placeShape
returns the set of all possible
placements of a given shape on a given grid, using all possible
orientations and all possible reference cells. The function
placeShapes
returns the list of all possible placements of a given
list of shapes together with the set of cells that are not yet
covered. To solve a particular puzzle, we apply this function to the
grid obtained from the full grid by removing seven cells occupied by
the "blocker" pieces, whose positions are determined by rolling the
provided dice, and to the list of the eleven shapes.