def main(): """Shikaku solver example.""" sym = grilops.make_number_range_symbol_set(0, HEIGHT * WIDTH - 1) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) rc = grilops.regions.RegionConstrainer( lattice, solver=sg.solver, rectangular=True, min_region_size=min(GIVENS.values()), max_region_size=max(GIVENS.values()) ) for p in lattice.points: sg.solver.add(sg.cell_is(p, rc.region_id_grid[p])) region_size = GIVENS.get(p) if region_size: sg.solver.add(rc.parent_grid[p] == grilops.regions.R) sg.solver.add(rc.region_size_grid[p] == region_size) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
def shikaku(givens): """Solver for Shikaku minipuzzles.""" sym = grilops.make_number_range_symbol_set(0, SIZE * SIZE - 1) sg = grilops.SymbolGrid(LATTICE, sym) rc = grilops.regions.RegionConstrainer(LATTICE, solver=sg.solver, rectangular=True) shifter = Shifter(sg.solver) for p in LATTICE.points: sg.solver.add(sg.cell_is(p, rc.region_id_grid[p])) given = givens[p.y][p.x] if given > 0: given = shifter.given(p, given) sg.solver.add(rc.parent_grid[p] == grilops.regions.R) sg.solver.add(rc.region_size_grid[p] == given) else: sg.solver.add(rc.parent_grid[p] != grilops.regions.R) assert sg.solve() sg.print() print() shifter.print_shifts() print() return shifter.eval_binary()
def main(): """Spiral Galaxies solver example.""" # The grid symbols will be the region IDs from the region constrainer. sym = grilops.make_number_range_symbol_set(0, HEIGHT * WIDTH - 1) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) rc = grilops.regions.RegionConstrainer(lattice, sg.solver) for p in sg.grid: sg.solver.add(sg.cell_is(p, rc.region_id_grid[p])) # Make the upper-left-most cell covered by a circle the root of its region. roots = {(int(math.floor(y)), int(math.floor(x))) for (y, x) in GIVENS} r = grilops.regions.R for y in range(HEIGHT): for x in range(WIDTH): sg.solver.add((rc.parent_grid[Point(y, x)] == r) == ((y, x) in roots)) # Ensure that each cell has a "partner" within the same region that is # rotationally symmetric with respect to that region's circle. for p in lattice.points: or_terms = [] for (gy, gx) in GIVENS: region_id = lattice.point_to_index( Point(int(math.floor(gy)), int(math.floor(gx)))) partner = Point(int(2 * gy - p.y), int(2 * gx - p.x)) if lattice.point_to_index(partner) is None: continue or_terms.append( And( rc.region_id_grid[p] == region_id, rc.region_id_grid[partner] == region_id, )) sg.solver.add(Or(*or_terms)) def show_cell(unused_p, region_id): rp = lattice.points[region_id] for i, (gy, gx) in enumerate(GIVENS): if int(math.floor(gy)) == rp.y and int(math.floor(gx)) == rp.x: return chr(65 + i) raise Exception("unexpected region id") if sg.solve(): sg.print(show_cell) print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print(show_cell) else: print("No solution")
def main(): """Fillomino solver example.""" givens = [ [0, 0, 0, 3, 0, 0, 0, 0, 5], [0, 0, 8, 3, 10, 0, 0, 5, 0], [0, 3, 0, 0, 0, 4, 4, 0, 0], [1, 3, 0, 3, 0, 0, 2, 0, 0], [0, 2, 0, 0, 3, 0, 0, 2, 0], [0, 0, 2, 0, 0, 3, 0, 1, 3], [0, 0, 4, 4, 0, 0, 0, 3, 0], [0, 4, 0, 0, 4, 3, 3, 0, 0], [6, 0, 0, 0, 0, 1, 0, 0, 0], ] sym = grilops.make_number_range_symbol_set(1, 10) lattice = grilops.get_rectangle_lattice(len(givens), len(givens[0])) sg = grilops.SymbolGrid(lattice, sym) rc = grilops.regions.RegionConstrainer(lattice, solver=sg.solver) for p in lattice.points: # The filled number must match the size of the region. sg.solver.add(sg.grid[p] == rc.region_size_grid[p]) # The size of the region must match the clue. given = givens[p.y][p.x] if given != 0: sg.solver.add(rc.region_size_grid[p] == given) # Different regions of the same size may not be orthogonally adjacent. region_sizes = [ n.symbol for n in lattice.edge_sharing_neighbors(rc.region_size_grid, p) ] region_ids = [ n.symbol for n in lattice.edge_sharing_neighbors(rc.region_id_grid, p) ] for region_size, region_id in zip(region_sizes, region_ids): sg.solver.add( Implies(rc.region_size_grid[p] == region_size, rc.region_id_grid[p] == region_id)) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
def main(): """Hex kakuro solver example.""" points = [] points.extend([Point(1, i) for i in range(-3, 4, 2)]) points.extend([Point(2, i) for i in range(-4, 5, 2)]) points.extend([Point(3, -5), Point(3, -3), Point(3, 3), Point(3, 5)]) points.extend([ Point(4, -6), Point(4, -4), Point(4, -2), Point(4, 2), Point(4, 4), Point(4, 6) ]) points.extend([Point(5, -5), Point(5, -3), Point(5, 3), Point(5, 5)]) points.extend([Point(6, i) for i in range(-4, 5, 2)]) points.extend([Point(7, i) for i in range(-3, 4, 2)]) lattice = PointyToppedHexagonalLattice(points) sym = grilops.make_number_range_symbol_set(1, 9) sg = grilops.SymbolGrid(lattice, sym) dirs_by_name = dict(sg.lattice.edge_sharing_directions()) for entry in SUMS: (y, x), dirname, given = entry d = dirs_by_name[dirname] s = grilops.sightlines.count_cells(sg, Point(y, x), d, count=lambda c: c) sg.solver.add(given == s) for p in lattice.points: for d in [Vector(0, 2), Vector(1, 1), Vector(1, -1)]: if p.translate(d.negate()) not in sg.grid: q = p ps = [] while q in sg.grid: ps.append(sg.grid[q]) q = q.translate(d) sg.solver.add(Distinct(*ps)) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
def killer_sudoku(cages: List[str], cage_sum_grid: List[List[int]]) -> str: """Solver for Killer Sudoku minipuzzles.""" sym = grilops.make_number_range_symbol_set(1, SIZE) sg = grilops.SymbolGrid(LATTICE, sym) shifter = Shifter(sg.solver) # Add normal sudoku constraints. for y in range(SIZE): sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for x in range(SIZE)])) for x in range(SIZE): sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for y in range(SIZE)])) for z in range(9): top = (z // 3) * 3 left = (z % 3) * 3 cells = [ sg.grid[Point(y, x)] for y in range(top, top + 3) for x in range(left, left + 3) ] sg.solver.add(Distinct(*cells)) # Build a map from each cage label to the cells within that cage. cage_cells = defaultdict(list) for p in LATTICE.points: cage_cells[cages[p.y][p.x]].append(sg.grid[p]) # The digits used in each cage must be unique. for cells_in_cage in cage_cells.values(): sg.solver.add(Distinct(*cells_in_cage)) cage_sums = {} for p in LATTICE.points: cage_sum = cage_sum_grid[p.y][p.x] if cage_sum > 0: shifted_cage_sum = shifter.given(p, cage_sum) cage_label = cages[p.y][p.x] assert cage_label not in cage_sums cage_sums[cage_label] = shifted_cage_sum # Add constraints for cages with given sums. for cage_label, shifted_cage_sum in cage_sums.items(): sg.solver.add(Sum(*cage_cells[cage_label]) == shifted_cage_sum) assert sg.solve() sg.print() print() shifter.print_shifts() print() return shifter.eval_binary()
def skyscraper(givens: Dict[Direction, List[int]]) -> str: """Solver for Skyscraper minipuzzles.""" sym = grilops.make_number_range_symbol_set(1, SIZE) sg = grilops.SymbolGrid(LATTICE, sym) shifter = Shifter(sg.solver) # Each row and each column contains each building height exactly once. for y in range(SIZE): sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for x in range(SIZE)])) for x in range(SIZE): sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for y in range(SIZE)])) # We'll use the sightlines accumulator to keep track of a tuple storing: # the tallest building we've seen so far # the number of visible buildings we've encountered Acc = Datatype("Acc") # pylint: disable=C0103 Acc.declare("acc", ("tallest", IntSort()), ("num_visible", IntSort())) Acc = Acc.create() # pylint: disable=C0103 def accumulate(a, height): return Acc.acc( If(height > Acc.tallest(a), height, Acc.tallest(a)), If(height > Acc.tallest(a), Acc.num_visible(a) + 1, Acc.num_visible(a))) for d, gs in givens.items(): for i, g in enumerate(gs): if d.vector.dy != 0: g = g - shifter.col_shifts.get(i, 0) p = Point(0 if d.vector.dy < 0 else SIZE - 1, i) elif d.vector.dx != 0: g = g - shifter.row_shifts.get(i, 0) p = Point(i, 0 if d.vector.dx < 0 else SIZE - 1) sg.solver.add(g == Acc.num_visible( # type: ignore[attr-defined] grilops.sightlines.reduce_cells( sg, p, LATTICE.opposite_direction(d), Acc.acc(0, 0), # type: ignore[attr-defined] accumulate))) assert sg.solve() sg.print() print() shifter.print_shifts() print() return shifter.eval_binary()
def main(): """Sudoku solver example.""" givens = [ [5, 3, 0, 0, 7, 0, 0, 0, 0], [6, 0, 0, 1, 9, 5, 0, 0, 0], [0, 9, 8, 0, 0, 0, 0, 6, 0], [8, 0, 0, 0, 6, 0, 0, 0, 3], [4, 0, 0, 8, 0, 3, 0, 0, 1], [7, 0, 0, 0, 2, 0, 0, 0, 6], [0, 6, 0, 0, 0, 0, 2, 8, 0], [0, 0, 0, 4, 1, 9, 0, 0, 5], [0, 0, 0, 0, 8, 0, 0, 7, 9], ] sym = grilops.make_number_range_symbol_set(1, 9) sg = grilops.SymbolGrid(grilops.get_square_lattice(9), sym) for y, given_row in enumerate(givens): for x, given in enumerate(given_row): if given != 0: sg.solver.add(sg.cell_is(Point(y, x), sym[given])) for y in range(9): sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for x in range(9)])) for x in range(9): sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for y in range(9)])) for z in range(9): top = (z // 3) * 3 left = (z % 3) * 3 cells = [sg.grid[Point(y, x)] for y in range(top, top + 3) for x in range(left, left + 3)] sg.solver.add(Distinct(*cells)) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
def main(): """Araf solver example.""" min_given_value = min(GIVENS.values()) max_given_value = max(GIVENS.values()) # The grid symbols will be the region IDs from the region constrainer. sym = grilops.make_number_range_symbol_set(0, HEIGHT * WIDTH - 1) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) rc = grilops.regions.RegionConstrainer( lattice, sg.solver, min_region_size=min_given_value + 1, max_region_size=max_given_value - 1 ) for point in lattice.points: sg.solver.add(sg.cell_is(point, rc.region_id_grid[point])) # Exactly half of the givens must be region roots. As an optimization, add # constraints that the smallest givens must not be region roots, and that the # largest givens must be region roots. undetermined_given_locations = [] num_undetermined_roots = len(GIVENS) // 2 for p, v in GIVENS.items(): if v == min_given_value: sg.solver.add(Not(rc.parent_grid[p] == grilops.regions.R)) elif v == max_given_value: sg.solver.add(rc.parent_grid[p] == grilops.regions.R) num_undetermined_roots -= 1 else: undetermined_given_locations.append(p) sg.solver.add( PbEq( [ (rc.parent_grid[p] == grilops.regions.R, 1) for p in undetermined_given_locations ], num_undetermined_roots ) ) # Non-givens must not be region roots for point in lattice.points: if point not in GIVENS: sg.solver.add(Not(rc.parent_grid[point] == grilops.regions.R)) add_given_pair_constraints(sg, rc) region_id_to_label = { lattice.point_to_index(point): chr(65 + i) for i, point in enumerate(GIVENS.keys()) } def show_cell(unused_loc, region_id): return region_id_to_label[region_id] if sg.solve(): sg.print(show_cell) print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print(show_cell) else: print("No solution")
"""Skyscraper solver example. Example puzzle can be found at https://www.puzzlemix.com/Skyscraper. """ from z3 import Datatype, Distinct, If, IntSort import grilops import grilops.sightlines from grilops.geometry import Point, Vector SIZE = 5 SYM = grilops.make_number_range_symbol_set(1, SIZE) GIVEN_TOP = [4, 2, 1, 2, 3] GIVEN_LEFT = [3, 2, 3, 2, 1] GIVEN_RIGHT = [3, 4, 1, 2, 2] GIVEN_BOTTOM = [1, 4, 3, 2, 2] def main(): """Skyscraper solver example.""" lattice = grilops.get_square_lattice(SIZE) sg = grilops.SymbolGrid(lattice, SYM) # Each row and each column contains each building height exactly once. for y in range(SIZE): sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for x in range(SIZE)])) for x in range(SIZE): sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for y in range(SIZE)])) # We'll use the sightlines accumulator to keep track of a tuple storing:
def main(): """Greater Than Killer Sudoku solver example.""" cages = [ "AABCCDDEE", "FFBGGHIJK", "FLMNHHIJK", "LLMNNOIPP", "QQRNSOTTU", "VWRSSXTUU", "VWYYSXZaa", "bccddXZee", "bbbdffZgg", ] cage_sums = { "B": 6, "D": 16, "F": 14, "H": 17, "I": 9, "J": 12, "K": 9, "L": 20, "M": 13, "N": 29, "O": 4, "R": 8, "S": 12, "V": 8, "W": 14, "Y": 17, "b": 11, "d": 11, "e": 8, } sym = grilops.make_number_range_symbol_set(1, 9) lattice = grilops.get_square_lattice(9) sg = grilops.SymbolGrid(lattice, sym) add_sudoku_constraints(sg) # Build a map from each cage label to the cells within that cage. cage_cells = defaultdict(list) for p in lattice.points: cage_cells[cages[p.y][p.x]].append(sg.grid[p]) # The digits used in each cage must be unique. for cells_in_cage in cage_cells.values(): sg.solver.add(Distinct(*cells_in_cage)) # Add constraints for cages with given sums. for cage_label, cage_sum in cage_sums.items(): sg.solver.add(Sum(*cage_cells[cage_label]) == cage_sum) # Add constraints between cage sums. def cage_sum_greater(a, b): sg.solver.add(Sum(*cage_cells[a]) > Sum(*cage_cells[b])) def cage_sum_equal(a, b): sg.solver.add(Sum(*cage_cells[a]) == Sum(*cage_cells[b])) cage_sum_equal("C", "G") cage_sum_greater("J", "E") cage_sum_greater("E", "K") cage_sum_greater("W", "c") cage_sum_greater("c", "b") cage_sum_greater("f", "d") cage_sum_greater("X", "f") if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
def main(): """Outflight Entertainment sudoku solver example.""" cages = [ "AABCCD", "EFBCGD", "EFFHGI", "EJJHGI", "EKJHGL", "KKJLLL", ] peaks = [ (0, 1), (0, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 3), (3, 0), (3, 5), (4, 2), (5, 1), (5, 4), ] extract = { "A": (5, 2), "B": (0, 3), "C": (3, 2), "D": (1, 4), "E": (0, 1), "F": (1, 5), "G": (4, 2), "H": (3, 5), "I": (1, 0), "J": (5, 5), "K": (3, 4), "L": (5, 1), "M": (0, 5), "N": (4, 4), } def answer(sg): solved_grid = sg.solved_grid() s = "" s += chr(64 + solved_grid[extract["A"]] + solved_grid[extract["B"]]) s += chr(64 + solved_grid[extract["C"]] + solved_grid[extract["D"]]) s += chr(64 + solved_grid[extract["E"]] + solved_grid[extract["F"]] + solved_grid[extract["G"]]) s += chr(64 + solved_grid[extract["H"]] + solved_grid[extract["I"]]) s += chr(64 + solved_grid[extract["J"]] + solved_grid[extract["K"]] + solved_grid[extract["L"]]) s += chr(64 + solved_grid[extract["M"]] + solved_grid[extract["N"]]) return s sym = grilops.make_number_range_symbol_set(1, 6) lattice = grilops.get_square_lattice(6) sg = grilops.SymbolGrid(lattice, sym) add_sudoku_constraints(sg) # Constrain regions to match the cages and be rooted at the peaks. cage_label_to_region_id = {} for py, px in peaks: cage_label_to_region_id[cages[py][px]] = lattice.point_to_index( (py, px)) rc = grilops.regions.RegionConstrainer(lattice, sg.solver) for y, x in lattice.points: sg.solver.add( rc.region_id_grid[(y, x)] == cage_label_to_region_id[cages[y][x]]) # Within each region, a parent cell must have a greater value than a child # cell, so that the values increase as you approach the root cell (the peak). for p in lattice.points: for n in sg.edge_sharing_neighbors(p): sg.solver.add( Implies( rc.edge_sharing_direction_to_index( n.direction) == rc.parent_grid[p], n.symbol > sg.grid[p])) if sg.solve(): sg.print() print() print(answer(sg)) while not sg.is_unique(): print() print("Alternate solution") sg.print() print() print(answer(sg)) else: print("No solution")