3-in-a-Row
The 3-in-a-Row puzzle is the sibling of Takuzu. The grid is a square with an even side length . The solver fills every cell with one of two symbols, conventionally a filled square and an open circle, subject to three rules: each row and each column contains exactly of each symbol, and no three consecutive cells in a row or column share the same symbol. Some cells are pre-filled as clues.
The balance and no-triples rules are identical to the chosen variant in Chapter . Distinct rows and columns are an extra condition in some binary-puzzle variants, but neither chapter imposes it. Here different symbols and examples illustrate the same linear-sum and sliding-window encoding.
Rules and a small instance
A 3-in-a-Row puzzle is an grid of cells, with even. Some cells carry pre-filled symbols drawn from (conventionally rendered as a filled square, as an open circle). A solution assigns a symbol to every cell such that:
Every pre-filled clue is preserved.
Each row contains exactly zeros and ones.
Each column contains exactly zeros and ones.
No three consecutive cells in any row have the same symbol.
No three consecutive cells in any column have the same symbol.
Figure 25.1 is a 3-in-a-Row with eight clues; the solution is unique.
The programming model
For every cell introduce a Boolean .
Row and column balance
For each row , Symmetrically for each column ,
No three consecutive same
For each row and each starting column , the three cells cannot all be and cannot all be . Since each variable is Boolean, the sum is in , and the rule is exactly Symmetrically for every column and starting row.
Clue preservation
For every pre-filled cell with clue ,
A CP-SAT solver
from ortools.sat.python import cp_model
def solve_threeinarow(N, clues):
"""N x N grid (N even); clues = list of (r, c, v)."""
m = cp_model.CpModel()
x = [[m.NewBoolVar("") for _ in range(N)]
for _ in range(N)]
for r, c, v in clues:
m.Add(x[r][c] == v)
for r in range(N):
m.Add(sum(x[r]) == N // 2)
for c in range(N):
m.Add(sum(x[r][c] for r in range(N))
== N // 2)
for r in range(N):
for c in range(N - 2):
s = x[r][c] + x[r][c+1] + x[r][c+2]
m.Add(1 <= s); m.Add(s <= 2)
for c in range(N):
for r in range(N - 2):
s = x[r][c] + x[r+1][c] + x[r+2][c]
m.Add(1 <= s); m.Add(s <= 2)
s = cp_model.CpSolver()
status = s.Solve(m)
if status not in (cp_model.OPTIMAL, cp_model.FEASIBLE):
raise RuntimeError(
"no solution: " + s.StatusName(status))
return [[s.Value(x[r][c]) for c in range(N)]
for r in range(N)]
The toy of Figure 25.1 solves uniquely in about milliseconds.
A hard instance
Figure 25.3 is a 3-in-a-Row with thirty-one clues spread across the grid. CP-SAT returns the unique solution in about milliseconds.
Sources. 3-in-a-Row appears under various names in the English-language puzzle press. Binary-puzzle variants differ in whether they require distinct rows and columns; the version here omits that condition. No complexity conclusion for this variant follows merely by dropping constraints from a harder one. For this chapter, the toy of Figure 25.1 is the small benchmark distributed with the Solving Logic Puzzles Python tooling; the hard instance of Figure 25.3 was generated to test CP-SAT’s propagation on a scale-up of the same rule system.