Skip to content
Vamshi Jandhyala

Books · Solving Puzzles through Mathematical Programming

Chapter 6

Drive Ya Nuts

Seven hexagonal nuts sit on a plastic base of seven pegs. Each nut carries six numbered edges; the numbers 11 through 66 appear in some order around each nut’s perimeter. The base is a flower: one peg at the centre, six around it, with each outer peg sharing one edge with the centre and one edge with each of its two outer neighbours. The solver must seat each nut on some peg, in some rotation, so that every pair of abutting edges carries the same number. There are exactly twelve abutting edges: six centre-to-outer pairs and six outer-to-outer pairs around the flower’s perimeter.

The puzzle was manufactured by Milton Bradley from 19701970 under the trade name Drive Ya Nuts. The mechanical design is charmingly direct: the plastic nuts unscrew from the pegs, the problem is unmistakable, and a solver without computational aid is entirely alone with seven rotatable hexagons. Brute force works at 7!66=2351462407! \cdot 6^6 = 235\,146\,240 configurations, but the constraint-programming encoding below reduces the search to a few dozen milliseconds.

The classical Milton Bradley piece set is shown in Figure 6.1. Edges are listed anti-clockwise from a canonical starting edge (labelled aa); the canonical edge is always the one carrying the number 11 unless two faces of the nut carry a 11, which does not occur in the 19701970 instance.

The seven nuts of the Milton Bradley set. Each hexagon’s six edges are numbered 11 through 66; the letter in the centre names the piece. Edges are listed anti-clockwise from the aa-edge (top).

The programming model

The tightest formulation treats each peg as a slot holding one (piece, rotation) pair, and derives six edge-colour variables per slot as the rotated copy of the chosen piece.

Variables

Let Ringi{0,1,,6}\mathrm{Ring}_i \in \{0, 1, \ldots, 6\} be the integer variable encoding which piece sits at slot ii, for i{0,1,,6}i \in \{0, 1, \ldots, 6\} (slot 00 is the centre; slots 11 through 66 are the outer positions in anti-clockwise order starting from the top). Let Roti{0,1,,5}\mathrm{Rot}_i \in \{0, 1, \ldots, 5\} encode the rotation of that piece, measured in 6060^\circ steps. For each slot ii and each edge position e{0,1,,5}e \in \{0, 1, \ldots, 5\} (with e=0e = 0 the edge pointing towards the centre of the flower for outer slots, and e=0e = 0 the top edge for the centre slot), introduce an integer variable Edgei,e{1,2,,6}\mathrm{Edge}_{i, e} \in \{1, 2, \ldots, 6\}.

Piece-rotation-edge table

For each slot ii, enumerate the 76=427 \cdot 6 = 42 tuples (Ringi,Roti,Edgei,0,,Edgei,5)\bigl(\mathrm{Ring}_i, \mathrm{Rot}_i, \mathrm{Edge}_{i, 0}, \ldots, \mathrm{Edge}_{i, 5}\bigr) that are consistent with the piece inventory (the ee-th edge of piece pp at rotation rr is Data[p][(e+r)mod6]\mathrm{Data}[p][(e + r) \bmod 6]) and impose the table constraint.

Piece uniqueness

The seven pieces are distinct: AllDifferent(Ring0,Ring1,,Ring6).\operatorname{AllDifferent}(\mathrm{Ring}_0, \mathrm{Ring}_1, \ldots, \mathrm{Ring}_6).

Edge-matching constraints

Six centre-to-outer matches: Edge0,e  =  Edgee+1,0,e{0,1,,5}.\mathrm{Edge}_{0, e} \;=\; \mathrm{Edge}_{e + 1, 0}, \quad e \in \{0, 1, \ldots, 5\}. Six outer cyclic matches. Writing next(i)\mathrm{next}(i) for the anti-clockwise neighbour of outer slot ii (with next(6)=1\mathrm{next}(6) = 1): Edgei,5  =  Edgenext(i),1,i{1,2,,6}.\mathrm{Edge}_{i, 5} \;=\; \mathrm{Edge}_{\mathrm{next}(i), 1}, \quad i \in \{1, 2, \ldots, 6\}.

A solver in fifty lines

from ortools.sat.python import cp_model

RINGS = [
    [1, 6, 5, 4, 3, 2],   # A
    [1, 4, 3, 6, 5, 2],   # B
    [1, 6, 4, 2, 5, 3],   # C
    [1, 6, 2, 4, 5, 3],   # D
    [1, 6, 5, 3, 2, 4],   # E
    [1, 4, 6, 2, 3, 5],   # F
    [1, 2, 3, 4, 5, 6],   # G
]

def solve():
    m = cp_model.CpModel()
    piece = [m.NewIntVar(0, 6, "") for _ in range(7)]
    rot   = [m.NewIntVar(0, 5, "") for _ in range(7)]
    edge  = [[m.NewIntVar(1, 6, "") for _ in range(6)]
             for _ in range(7)]
    tuples = []
    for p in range(7):
        for r in range(6):
            row = [p, r]
            for e in range(6):
                row.append(RINGS[p][(e + r) % 6])
            tuples.append(row)
    for i in range(7):
        m.AddAllowedAssignments(
            [piece[i], rot[i]] + edge[i], tuples)
    m.AddAllDifferent(piece)
    # Centre to outer
    for e in range(6):
        m.Add(edge[0][e] == edge[e + 1][0])
    # Outer cyclic
    for i in range(1, 7):
        j = i + 1 if i < 6 else 1
        m.Add(edge[i][5] == edge[j][1])
    s = cp_model.CpSolver()
    s.Solve(m)
    return [(s.Value(piece[i]),
             s.Value(rot[i]),
             [s.Value(edge[i][e]) for e in range(6)])
            for i in range(7)]

The solver returns a seating assignment in roughly 3030 milliseconds. Enumerating all solutions returns exactly six, all rotations of one another: the centre piece is fixed and the outer ring is cyclically shifted. The solution is therefore unique up to the six-fold rotation (cyclic) symmetry of the flower, and is displayed in Figure 6.2: piece DD takes the centre, and pieces FF, EE, BB, GG, CC, AA occupy the six outer slots in anti-clockwise order starting from the top.

The solved flower. Every pair of abutting edges carries the same number; piece DD sits at the centre, and the outer six read FF, EE, BB, GG, CC, AA in anti-clockwise order from the top.

Sources. Drive Ya Nuts was introduced by Milton Bradley in 19701970 and reissued intermittently thereafter; its seven-hexagon format is the best-known instance of the broader Japanese edge-matching puzzle family, which also includes Thompson’s MacMahon Hexagons and the 4×44 \times 4 Eternity II variant. The puzzle’s solution is unique up to the six-fold rotation (cyclic) symmetry of the solved flower; the solver finds exactly these six rotations and no others. The six reflections of the dihedral group are not physically realisable here, since flipping the flower over would require mirror-image nuts, which the inventory does not contain. The constraint-programming treatment given here is a straightforward edge-matching encoding; identical formulations scale unchanged to 3737-hexagon or 6161-hexagon flowers, at which point the search space grows past brute force but remains easy for modern CP solvers.