def region_solve(): """Hex slitherlink solver example using regions.""" lattice = get_lattice() sym = grilops.SymbolSet(["I", "O"]) sg = grilops.SymbolGrid(lattice, sym) rc = grilops.regions.RegionConstrainer(lattice, solver=sg.solver, complete=True) # There must be exactly two connected regions: the inside and the outside. # The outside region ID will be 0 because the top-left-most element will # always be outside. inside_region_id = Int("inside_region_id") sg.solver.add(inside_region_id != 0) for p in lattice.points: # A cell should have symbol I if and only if it's in the inside region. sg.solver.add((sg.grid[p] == sym.I) == ( rc.region_id_grid[p] == inside_region_id)) # If this cell isn't in the givens array, it must be outside the loop. givens_addr = point_to_givens_row_col(p) if givens_addr is None: sg.solver.add(sg.grid[p] == sym.O) continue # Find the given corresponding to this cell. If it's None, we don't # know anything about it. r, c = givens_addr given = GIVENS[r][c] if given is None: continue # The given number must equal the number of adjacent cells on the # opposite side of the loop line. num_different_neighbors_terms = [(n.symbol != sg.grid[p], 1) for n in sg.edge_sharing_neighbors(p)] sg.solver.add(PbEq(num_different_neighbors_terms, given)) def hook_function(p, _): addr = point_to_givens_row_col(p) return " " if addr is None else None if sg.solve(): sg.print(hook_function) print_loop(sg.grid, sg.solver.model()) print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print(hook_function) print_loop(sg.grid, sg.solver.model()) else: print("No solution")
def main(): """Nurikabe solver example.""" sym = grilops.SymbolSet([("B", chr(0x2588)), ("W", " ")]) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) rc = grilops.regions.RegionConstrainer(lattice, solver=sg.solver) constrain_sea(sym, sg, rc) constrain_islands(sym, sg, rc) constrain_adjacent_cells(sg, rc) def print_grid(): sg.print(lambda p, _: str(GIVENS[(p.y, p.x)]) if (p.y, p.x) in GIVENS else None) if sg.solve(): print_grid() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") print_grid() else: print("No solution")
def main(): """Gokigen Naname solver example.""" sym = grilops.SymbolSet([("F", chr(0x2571)), ("B", chr(0x2572))]) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) # Ensure the given number of line segment constraints are met. for (y, x), v in GIVENS.items(): terms = [] if y > 0: if x > 0: terms.append(sg.cell_is(Point(y - 1, x - 1), sym.B)) if x < WIDTH: terms.append(sg.cell_is(Point(y - 1, x), sym.F)) if y < HEIGHT: if x > 0: terms.append(sg.cell_is(Point(y, x - 1), sym.F)) if x < WIDTH: terms.append(sg.cell_is(Point(y, x), sym.B)) sg.solver.add(PbEq([(term, 1) for term in terms], v)) add_loop_constraints(sym, sg) 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(): """SLICY solver example.""" sym = grilops.SymbolSet(["S", "L", "I", "C", "Y", ("W", " ")]) lattice = PointyToppedHexagonalLattice([ areas_row_col_to_point(r, c) for r in range(len(AREAS)) for c in range(len(AREAS[r])) ]) sg = grilops.SymbolGrid(lattice, sym) rc = grilops.regions.RegionConstrainer(lattice, solver=sg.solver, complete=True) sc = ShapeConstrainer( lattice, [ # Note that the example shapes are shown as flat-topped hexagons, so # you need to turn the page sideways to see them as pointy-topped ones. Shape([Vector(0, 0), Vector(1, 1), Vector(1, 3), Vector(2, 4)]), # S Shape([Vector(0, 0), Vector(0, 2), Vector(0, 4), Vector(-1, 5)]), # L Shape([Vector(0, 0), Vector(0, 2), Vector(0, 4), Vector(0, 6)]), # I Shape([Vector(0, 0), Vector(1, 1), Vector(1, 3), Vector(0, 4)]), # C Shape([Vector(0, 0), Vector(2, 0), Vector(1, 1), Vector(1, 3)]), # Y ], solver=sg.solver, allow_rotations=True, allow_reflections=True, allow_copies=True) link_symbols_to_shapes(sym, sg, sc) add_area_constraints(lattice, sc) add_sea_constraints(sym, sg, rc) add_adjacent_tetrahex_constraints(lattice, sc) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() print() else: print("No solution")
def main(): """Kuromasu solver example.""" sym = grilops.SymbolSet([("B", chr(0x2588) * 2), ("W", " ")]) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) rc = grilops.regions.RegionConstrainer(lattice, solver=sg.solver, complete=False) for p, c in GIVENS.items(): # Numbered cells may not be black. sg.solver.add(sg.cell_is(p, sym.W)) # Each number on the board represents the number of white cells that can be # seen from that cell, including itself. A cell can be seen from another # cell if they are in the same row or column, and there are no black cells # between them in that row or column. visible_cell_count = 1 + sum( grilops.sightlines.count_cells( sg, n.location, n.direction, stop=lambda c: c == sym.B) for n in sg.edge_sharing_neighbors(p)) sg.solver.add(visible_cell_count == c) # All the white cells must be connected horizontally or vertically. Enforce # this by requiring all white cells to have the same region ID. Force the # root of this region to be the first given, to reduce the space of # possibilities. white_root = min(GIVENS.keys()) white_region_id = lattice.point_to_index(white_root) for p in lattice.points: # No two black cells may be horizontally or vertically adjacent. sg.solver.add( Implies( sg.cell_is(p, sym.B), And(*[n.symbol == sym.W for n in sg.edge_sharing_neighbors(p)]))) # All white cells must have the same region ID. All black cells must not # be part of a region. sg.solver.add( If(sg.cell_is(p, sym.W), rc.region_id_grid[p] == white_region_id, rc.region_id_grid[p] == -1)) def print_grid(): sg.print(lambda p, _: f"{GIVENS[(p.y, p.x)]:02}" if (p.y, p.x) in GIVENS else None) if sg.solve(): print_grid() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") print_grid() else: print("No solution")
def main(): """LITS solver example.""" sym = grilops.SymbolSet(["L", "I", "T", "S", ("W", " ")]) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) rc = grilops.regions.RegionConstrainer(lattice, solver=sg.solver) sc = grilops.shapes.ShapeConstrainer( lattice, [ [Vector(0, 0), Vector(1, 0), Vector(2, 0), Vector(2, 1)], # L [Vector(0, 0), Vector(1, 0), Vector(2, 0), Vector(3, 0)], # I [Vector(0, 0), Vector(0, 1), Vector(0, 2), Vector(1, 1)], # T [Vector(0, 0), Vector(1, 0), Vector(1, 1), Vector(2, 1)], # S ], solver=sg.solver, allow_rotations=True, allow_reflections=True, allow_copies=True) link_symbols_to_shapes(sym, sg, sc) add_area_constraints(lattice, sc) add_nurikabe_constraints(sym, sg, rc) add_adjacent_tetronimo_constraints(lattice, sc) if sg.solve(): sg.print() print() sc.print_shape_types() print() sc.print_shape_instances() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() print() sc.print_shape_types() print() sc.print_shape_instances() print() else: print("No solution")
def main(): """Shape solver example.""" points = [] for y, row in enumerate(GRID): for x, c in enumerate(row): if c == "O": points.append(Point(y, x)) lattice = RectangularLattice(points) shapes = [ Shape([Vector(0, 0), Vector(1, 0), Vector(2, 0), Vector(3, 0)]), # I Shape([Vector(0, 0), Vector(1, 0), Vector(2, 0), Vector(2, 1)]), # L Shape([Vector(0, 1), Vector(0, 2), Vector(1, 0), Vector(1, 1)]), # S ] sym = grilops.SymbolSet([("B", chr(0x2588) * 2), ("W", " ")]) sg = grilops.SymbolGrid(lattice, sym) sc = ShapeConstrainer(lattice, shapes, sg.solver, complete=False, allow_rotations=True, allow_reflections=True, allow_copies=False) for p in points: sg.solver.add(sg.cell_is(p, sym.W) == (sc.shape_type_grid[p] == -1)) for n in sg.vertex_sharing_neighbors(p): np = n.location sg.solver.add( Implies( And(sc.shape_type_grid[p] != -1, sc.shape_type_grid[np] != -1), sc.shape_type_grid[p] == sc.shape_type_grid[np])) if sg.solve(): sg.print() print() sc.print_shape_types() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
def main(): """Star Battle solver example.""" sym = grilops.SymbolSet([("EMPTY", " "), ("STAR", "*")]) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) # There must be exactly two stars per column. for y in range(HEIGHT): sg.solver.add( Sum(*[ If(sg.cell_is(Point(y, x), sym.STAR), 1, 0) for x in range(WIDTH) ]) == 2) # There must be exactly two stars per row. for x in range(WIDTH): sg.solver.add( Sum(*[ If(sg.cell_is(Point(y, x), sym.STAR), 1, 0) for y in range(HEIGHT) ]) == 2) # There must be exactly two stars per area. area_cells = defaultdict(list) for y in range(HEIGHT): for x in range(WIDTH): area_cells[AREAS[y][x]].append(sg.grid[Point(y, x)]) for cells in area_cells.values(): sg.solver.add(Sum(*[If(c == sym.STAR, 1, 0) for c in cells]) == 2) # Stars may not touch each other, not even diagonally. for y in range(HEIGHT): for x in range(WIDTH): p = Point(y, x) sg.solver.add( Implies( sg.cell_is(p, sym.STAR), And(*[ n.symbol == sym.EMPTY for n in sg.vertex_sharing_neighbors(p) ]))) if sg.solve(): sg.print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
def main(): """Aquarium solver example.""" sym = grilops.SymbolSet([("B", chr(0x2588)), ("W", " ")]) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) # Number of shaded cells per row / column must match clues. for y in range(HEIGHT): sg.solver.add( PbEq([(sg.grid[(y, x)] == sym.B, 1) for x in range(WIDTH)], ROW_CLUES[y])) for x in range(WIDTH): sg.solver.add( PbEq([(sg.grid[(y, x)] == sym.B, 1) for y in range(HEIGHT)], COL_CLUES[x])) # The water level in each aquarium is the same across its full width. for y in range(HEIGHT): for al in set(REGIONS[y]): # If any aquarium cell is filled within a row, then all cells of that # aquarium within that row must be filled. cells = [ sg.grid[(y, x)] for x in range(WIDTH) if REGIONS[y][x] == al ] for cell in cells[1:]: sg.solver.add(cell == cells[0]) # If an aquarium is filled within a row, and that aquarium also has # cells in the row below that row, then that same aquarium's cells below # must be filled as well. if y < HEIGHT - 1: cells_below = [ sg.grid[(y + 1, x)] for x in range(WIDTH) if REGIONS[y + 1][x] == al ] if cells_below: sg.solver.add( Implies(cells[0] == sym.B, cells_below[0] == sym.B)) 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(): """Heyawake solver example.""" sym = grilops.SymbolSet([("B", chr(0x2588)), ("W", " ")]) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) # Rule 1: Painted cells may never be orthogonally connected (they may not # share a side, although they can touch diagonally). for p in lattice.points: sg.solver.add( Implies( sg.cell_is(p, sym.B), And(*[n.symbol != sym.B for n in sg.edge_sharing_neighbors(p)]) ) ) # Rule 2: All white cells must be interconnected (form a single polyomino). rc = grilops.regions.RegionConstrainer( lattice, sg.solver, complete=False) white_region_id = Int("white_region_id") sg.solver.add(white_region_id >= 0) sg.solver.add(white_region_id < HEIGHT * WIDTH) for p in lattice.points: sg.solver.add( If( sg.cell_is(p, sym.W), rc.region_id_grid[p] == white_region_id, rc.region_id_grid[p] == -1 ) ) # Rule 3: A number indicates exactly how many painted cells there must be in # that particular room. region_cells = defaultdict(list) for p in lattice.points: region_cells[REGIONS[p.y][p.x]].append(sg.grid[p]) for region, count in REGION_COUNTS.items(): sg.solver.add(PbEq([(c == sym.B, 1) for c in region_cells[region]], count)) # Rule 4: A room which has no number may contain any number of painted cells, # or none. # Rule 5: Where a straight (orthogonal) line of connected white cells is # formed, it must not contain cells from more than two rooms—in other words, # any such line of white cells which connects three or more rooms is # forbidden. region_names = sorted(list(set(c for row in REGIONS for c in row))) bits = len(region_names) def set_region_bit(bv, p): i = region_names.index(REGIONS[p.y][p.x]) chunks = [] if i < bits - 1: chunks.append(Extract(bits - 1, i + 1, bv)) chunks.append(BitVecVal(1, 1)) if i > 0: chunks.append(Extract(i - 1, 0, bv)) return Concat(*chunks) for p in lattice.points: for n in sg.edge_sharing_neighbors(p): bv = reduce_cells( sg, p, n.direction, set_region_bit(BitVecVal(0, bits), p), lambda acc, c, ap: set_region_bit(acc, ap), lambda acc, c, sp: c == sym.B ) popcnt = Sum(*[BV2Int(Extract(i, i, bv)) for i in range(bits)]) sg.solver.add(Implies(sg.cell_is(p, sym.W), popcnt <= 2)) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
"""Battleship solver example.""" from z3 import And, Not, Implies, Or, PbEq import grilops import grilops.shapes from grilops.geometry import Point, Vector SYM = grilops.SymbolSet([ ("X", " "), ("N", chr(0x25B4)), ("E", chr(0x25B8)), ("S", chr(0x25BE)), ("W", chr(0x25C2)), ("B", chr(0x25AA)), ("O", chr(0x2022)), ]) DIR_TO_OPPOSITE_SYM = { Vector(-1, 0): SYM.S, Vector(0, 1): SYM.W, Vector(1, 0): SYM.N, Vector(0, -1): SYM.E, } HEIGHT, WIDTH = 8, 8 LATTICE = grilops.get_rectangle_lattice(HEIGHT, WIDTH) GIVENS_Y = [1, 5, 1, 5, 0, 3, 2, 2] GIVENS_X = [2, 4, 2, 3, 0, 4, 1, 3] GIVENS = { Point(2, 5): SYM.S, Point(6, 1): SYM.S,
(1, 6): [2, 2], (1, 9): [2], (3, 3): [1], (4, 2): [1, 1], (4, 5): [2], (4, 7): [2, 2], (5, 2): [4], (5, 4): [4], (5, 7): [3], (6, 6): [3], (8, 0): [4], (8, 3): [4], (8, 9): [3], (9, 6): [3], } SYM = grilops.SymbolSet([("B", chr(0x2588)), ("W", " ")]) def make_neighbor_locations(y, x): """Returns a list of neighboring locations, without gaps between them.""" ds = [(-1, 0), (-1, 1), (0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1)] ns = [] start = 0 for dy, dx in ds: ny, nx = y + dy, x + dx if 0 <= ny < HEIGHT and 0 <= nx < WIDTH: ns.append((ny, nx)) else: start = len(ns) return ns[start:] + ns[:start]
def region_solve(): """Slitherlink solver example using regions.""" sym = grilops.SymbolSet([("I", chr(0x2588)), ("O", " ")]) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) rc = grilops.regions.RegionConstrainer(lattice, solver=sg.solver, complete=False) def constrain_no_inside_diagonal(y, x): """Add constraints for diagonally touching cells. "Inside" cells may not diagonally touch each other unless they also share an adjacent cell. """ nw = sg.grid[Point(y, x)] ne = sg.grid[Point(y, x + 1)] sw = sg.grid[Point(y + 1, x)] se = sg.grid[Point(y + 1, x + 1)] sg.solver.add( Implies(And(nw == sym.I, se == sym.I), Or(ne == sym.I, sw == sym.I))) sg.solver.add( Implies(And(ne == sym.I, sw == sym.I), Or(nw == sym.I, se == sym.I))) region_id = Int("region_id") for p in lattice.points: # Each cell must be either "inside" (part of a single region) or # "outside" (not part of any region). sg.solver.add( Or(rc.region_id_grid[p] == region_id, rc.region_id_grid[p] == -1)) sg.solver.add( (sg.grid[p] == sym.I) == (rc.region_id_grid[p] == region_id)) if p not in GIVENS: continue given = GIVENS[p] neighbors = sg.edge_sharing_neighbors(p) # The number of grid edge border segments adjacent to this cell. num_grid_borders = 4 - len(neighbors) # The number of adjacent cells on the opposite side of the loop line. num_different_neighbors_terms = [(n.symbol != sg.grid[p], 1) for n in neighbors] # If this is an "inside" cell, we should count grid edge borders as loop # segments, but if this is an "outside" cell, we should not. sg.solver.add( If(sg.grid[p] == sym.I, PbEq(num_different_neighbors_terms, given - num_grid_borders), PbEq(num_different_neighbors_terms, given))) # "Inside" cells may not diagonally touch each other unless they also share # an adjacent cell. for y in range(HEIGHT - 1): for x in range(WIDTH - 1): constrain_no_inside_diagonal(y, x) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
import grilops import grilops.regions from grilops.geometry import Point SIZE = 4 BLACK_CELLS = set([ Point(0, 0), Point(1, 0), Point(3, 1), Point(3, 2), ]) SYM = grilops.SymbolSet([ ("BL", chr(0x2588)), ("NS", chr(0x25AF)), ("EW", chr(0x25AD)), ("NE", chr(0x25F9)), ("SE", chr(0x25FF)), ("SW", chr(0x25FA)), ("NW", chr(0x25F8)), ]) def main(): """Heteromino solver example.""" lattice = grilops.get_square_lattice(SIZE) sg = grilops.SymbolGrid(lattice, SYM) rc = grilops.regions.RegionConstrainer(lattice, solver=sg.solver, complete=False) def constrain_neighbor(p, np, is_root, shape, has_neighbor):
def slitherlink(givens): """Solver for Slitherlink minipuzzles.""" sym = grilops.SymbolSet([("I", chr(0x2588)), ("O", " ")]) sg = grilops.SymbolGrid(LATTICE, sym) rc = grilops.regions.RegionConstrainer(LATTICE, solver=sg.solver, complete=False) shifter = Shifter(sg.solver) region_id = Int("region_id") for p in LATTICE.points: # Each cell must be either "inside" (part of a single region) or # "outside" (not part of any region). sg.solver.add( Or(rc.region_id_grid[p] == region_id, rc.region_id_grid[p] == -1)) sg.solver.add( (sg.grid[p] == sym.I) == (rc.region_id_grid[p] == region_id)) given = givens[p.y][p.x] if given == 9: continue given = shifter.given(p, given) neighbors = sg.edge_sharing_neighbors(p) # The number of grid edge border segments adjacent to this cell. num_grid_borders = 4 - len(neighbors) # The number of adjacent cells on the opposite side of the loop line. num_different_neighbors = Sum( [If(n.symbol != sg.grid[p], 1, 0) for n in neighbors]) # If this is an "inside" cell, we should count grid edge borders as loop # segments, but if this is an "outside" cell, we should not. sg.solver.add( If(sg.grid[p] == sym.I, num_different_neighbors == given - num_grid_borders, num_different_neighbors == given)) def constrain_no_inside_diagonal(y, x): """Add constraints for diagonally touching cells. "Inside" cells may not diagonally touch each other unless they also share an adjacent cell. """ nw = sg.grid[Point(y, x)] ne = sg.grid[Point(y, x + 1)] sw = sg.grid[Point(y + 1, x)] se = sg.grid[Point(y + 1, x + 1)] sg.solver.add( Implies(And(nw == sym.I, se == sym.I), Or(ne == sym.I, sw == sym.I))) sg.solver.add( Implies(And(ne == sym.I, sw == sym.I), Or(nw == sym.I, se == sym.I))) for y in range(SIZE - 1): for x in range(SIZE - 1): constrain_no_inside_diagonal(y, x) assert sg.solve() sg.print() print() shifter.print_shifts() print() return shifter.eval_binary()
def main(): """Akari solver example.""" size = 10 lattice = grilops.get_square_lattice(size) black_cells = { (0, 0): None, (0, 3): None, (0, 9): None, (1, 7): None, (2, 1): 3, (2, 6): 0, (3, 2): 2, (3, 5): None, (3, 9): 1, (4, 3): 1, (4, 4): 0, (4, 5): None, (5, 4): 1, (5, 5): None, (5, 6): None, (6, 0): None, (6, 4): 2, (6, 7): 2, (7, 3): None, (7, 8): None, (8, 2): 1, (9, 0): 0, (9, 6): 1, (9, 9): 0, } def print_given(point): if point in black_cells: v = black_cells.get(point) if v is None: return chr(0x2588) return str(v) return None lattice.print(print_given) print() sym = grilops.SymbolSet([ ("BLACK", chr(0x2588)), ("EMPTY", " "), ("LIGHT", "*"), ]) sg = grilops.SymbolGrid(lattice, sym) for point in lattice.points: if point in black_cells: sg.solver.add(sg.cell_is(point, sym.BLACK)) light_count = black_cells[point] if light_count is not None: sg.solver.add( PbEq([(n.symbol == sym.LIGHT, 1) for n in sg.edge_sharing_neighbors(point)], light_count)) else: # All black cells are given; don't allow this cell to be black. sg.solver.add(sg.cell_is_one_of(point, [sym.EMPTY, sym.LIGHT])) def is_black(c): return c == sym.BLACK def count_light(c): return If(c == sym.LIGHT, 1, 0) for point in lattice.points: if point in black_cells: continue visible_light_count = sum( grilops.sightlines.count_cells( sg, n.location, n.direction, count=count_light, stop=is_black) for n in sg.edge_sharing_neighbors(point)) # Ensure that each light cannot see any other lights, and that each cell # is lit by at least one light. sg.solver.add( If(sg.cell_is(point, sym.LIGHT), visible_light_count == 0, visible_light_count > 0)) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")