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 load_puzzle( url: str) -> Tuple[SymbolGrid, Optional[Callable[[Point, int], str]]]: params = url.split('/') height = int(params[-2]) width = int(params[-3]) givens: PuzzleGivens = parse_url(url) lattice = get_rectangle_lattice(height, width) sym = LoopSymbolSet(lattice) sym.append('EMPTY', chr(0x25AE)) sg = SymbolGrid(lattice, sym) lc = LoopConstrainer(sg, single_loop=True) set_loop_order_zero = False # Construct puzzle from URL for p in sg.lattice.points: if givens[p]: sg.solver.add(sg.cell_is(p, sym.EMPTY)) else: sg.solver.add(Not(sg.cell_is(p, sym.EMPTY))) # Restrict loop order to an empty cell if not set_loop_order_zero: sg.solver.add(lc.loop_order_grid[p] == 0) set_loop_order_zero = True return sg, None
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 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(): """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(): """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(): """Tapa solver example.""" lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, SYM) rc = grilops.regions.RegionConstrainer(lattice, solver=sg.solver, complete=False) # Ensure the wall consists of a single region and is made up of shaded # symbols. wall_id = Int("wall_id") sg.solver.add(wall_id >= 0) sg.solver.add(wall_id < HEIGHT * WIDTH) for y in range(HEIGHT): for x in range(WIDTH): p = Point(y, x) sg.solver.add( sg.cell_is(p, SYM.B) == (rc.region_id_grid[p] == wall_id)) # Ensure that given clue cells are not part of the wall. if (y, x) in GIVENS: sg.solver.add(sg.cell_is(p, SYM.W)) # Shaded cells may not form a 2x2 square anywhere in the grid. for sy in range(HEIGHT - 1): for sx in range(WIDTH - 1): pool_cells = [ sg.grid[Point(y, x)] for y in range(sy, sy + 2) for x in range(sx, sx + 2) ] sg.solver.add(Not(And(*[cell == SYM.B for cell in pool_cells]))) # Add constraints for the given clues. for y, x in GIVENS.keys(): add_neighbor_constraints(sg, y, x) def show_cell(p, _): given = GIVENS.get((p.y, p.x)) if given is None: return None if len(given) > 1: return "*" return str(given[0]) 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(): """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(): """Cave solver example.""" lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, SYM) rc = grilops.regions.RegionConstrainer(lattice, solver=sg.solver) # The cave must be a single connected group. Force the root of this region to # be the top-most, left-most given. cave_root_point = next(p for p in lattice.points if GIVENS[p.y][p.x] != 0) cave_region_id = lattice.point_to_index(cave_root_point) sg.solver.add(rc.parent_grid[cave_root_point] == grilops.regions.R) for p in lattice.points: # Ensure that every cave cell has the same region ID. sg.solver.add( sg.cell_is(p, SYM.W) == (rc.region_id_grid[p] == cave_region_id)) # Every shaded region must connect to an edge of the grid. We'll enforce # this by requiring that the root of a shaded region is along the edge of # the grid. if 0 < p.y < HEIGHT - 1 and 0 < p.x < WIDTH - 1: sg.solver.add( Implies(sg.cell_is(p, SYM.B), rc.parent_grid[p] != grilops.regions.R)) if GIVENS[p.y][p.x] != 0: sg.solver.add(sg.cell_is(p, SYM.W)) # Count the cells visible along sightlines from the given cell. 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 == GIVENS[p.y][p.x]) def print_grid(): sg.print(lambda p, _: str(GIVENS[p.y][p.x]) if GIVENS[p.y][p.x] != 0 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 loop_solve(): """Slitherlink solver example using loops.""" # We'll place symbols at the intersections of the grid lines, rather than in # the spaces between the grid lines where the givens are written. This # requires increasing each dimension by one. lattice = grilops.get_rectangle_lattice(HEIGHT + 1, WIDTH + 1) sym = grilops.loops.LoopSymbolSet(lattice) sym.append("EMPTY", " ") sg = grilops.SymbolGrid(lattice, sym) grilops.loops.LoopConstrainer(sg, single_loop=True) for (y, x), c in GIVENS.items(): # For each side of this given location, add one if there's a loop edge # along that side. We'll determine this by checking the kinds of loop # symbols in the north-west and south-east corners of this given location. terms = [ # Check for east edge of north-west corner (given's north edge). sg.cell_is_one_of(Point(y, x), [sym.EW, sym.NE, sym.SE]), # Check for north edge of south-east corner (given's east edge). sg.cell_is_one_of(Point(y + 1, x + 1), [sym.NS, sym.NE, sym.NW]), # Check for west edge of south-east corner (given's south edge). sg.cell_is_one_of(Point(y + 1, x + 1), [sym.EW, sym.SW, sym.NW]), # Check for south edge of north-west corner (given's west edge). sg.cell_is_one_of(Point(y, x), [sym.NS, sym.SE, sym.SW]), ] sg.solver.add(PbEq([(term, 1) for term in terms], c)) 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(): """Nanro solver example.""" lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, SYM) rc = grilops.regions.RegionConstrainer(lattice, solver=sg.solver, complete=False) # Constrain the symbol grid to contain the given labels. for p, l in GIVEN_LABELS.items(): sg.solver.add(sg.cell_is(p, l)) # Use the RegionConstrainer to require a single connected group made up of # only labeled cells. label_region_id = lattice.point_to_index(min(GIVEN_LABELS.keys())) for p in lattice.points: sg.solver.add( If(sg.cell_is(p, SYM.EMPTY), rc.region_id_grid[p] == -1, rc.region_id_grid[p] == label_region_id)) # No 2x2 group of cells may be fully labeled. for sy in range(HEIGHT - 1): for sx in range(WIDTH - 1): pool_cells = [ sg.grid[Point(y, x)] for y in range(sy, sy + 2) for x in range(sx, sx + 2) ] sg.solver.add(Or(*[c == SYM.EMPTY for c in pool_cells])) region_cells = defaultdict(list) for p in lattice.points: region_cells[REGIONS[p.y][p.x]].append(sg.grid[p]) # Each bold region must contain at least one labeled cell. for cells in region_cells.values(): sg.solver.add(Or(*[c != SYM.EMPTY for c in cells])) # Each number must equal the total count of labeled cells in that region. for cells in region_cells.values(): num_labeled_cells = Sum(*[If(c == SYM.EMPTY, 0, 1) for c in cells]) sg.solver.add( And(*[ Implies(c != SYM.EMPTY, c == num_labeled_cells) for c in cells ])) # When two numbers are orthogonally adjacent across a region boundary, the # numbers must be different. for p in lattice.points: for n in sg.edge_sharing_neighbors(p): np = n.location if REGIONS[p.y][p.x] != REGIONS[np.y][np.x]: sg.solver.add( Implies( And(sg.grid[p] != SYM.EMPTY, sg.grid[np] != SYM.EMPTY), sg.grid[p] != sg.grid[np])) 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")
("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, Point(7, 5): SYM.O, } def main(): """Battleship solver example.""" sg = grilops.SymbolGrid(LATTICE, SYM) sc = grilops.shapes.ShapeConstrainer( LATTICE, [ [Vector(0, i) for i in range(4)],
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")
def main(): """Masyu solver example.""" e, w, b = " ", chr(0x25e6), chr(0x2022) givens = [ [e, e, w, e, w, e, e, e, e, e], [e, e, e, e, w, e, e, e, b, e], [e, e, b, e, b, e, w, e, e, e], [e, e, e, w, e, e, w, e, e, e], [b, e, e, e, e, w, e, e, e, w], [e, e, w, e, e, e, e, w, e, e], [e, e, b, e, e, e, w, e, e, e], [w, e, e, e, b, e, e, e, e, w], [e, e, e, e, e, e, w, w, e, e], [e, e, b, e, e, e, e, e, e, b], ] for row in givens: for cell in row: sys.stdout.write(cell) print() lattice = grilops.get_rectangle_lattice(len(givens), len(givens[0])) sym = grilops.loops.LoopSymbolSet(lattice) sym.append("EMPTY", " ") sg = grilops.SymbolGrid(lattice, sym) lc = grilops.loops.LoopConstrainer(sg, single_loop=True) # Choose a non-empty cell to have loop order zero, to speed up solving. p = min(p for p in lattice.points if givens[p.y][p.x] != e) sg.solver.add(lc.loop_order_grid[p] == 0) straights = [sym.NS, sym.EW] turns = [sym.NE, sym.SE, sym.SW, sym.NW] for p in lattice.points: given = givens[p.y][p.x] if given == b: # The loop must turn at a black circle. sg.solver.add(sg.cell_is_one_of(p, turns)) # All connected adjacent cells must contain straight loop segments. for n in sg.edge_sharing_neighbors(p): if n.location.y < p.y: sg.solver.add( Implies(sg.cell_is_one_of(p, [sym.NE, sym.NW]), sg.cell_is(n.location, sym.NS))) if n.location.y > p.y: sg.solver.add( Implies(sg.cell_is_one_of(p, [sym.SE, sym.SW]), sg.cell_is(n.location, sym.NS))) if n.location.x < p.x: sg.solver.add( Implies(sg.cell_is_one_of(p, [sym.SW, sym.NW]), sg.cell_is(n.location, sym.EW))) if n.location.x > p.x: sg.solver.add( Implies(sg.cell_is_one_of(p, [sym.NE, sym.SE]), sg.cell_is(n.location, sym.EW))) elif given == w: # The loop must go straight through a white circle. sg.solver.add(sg.cell_is_one_of(p, straights)) # At least one connected adjacent cell must turn. if 0 < p.y < len(givens) - 1: sg.solver.add( Implies( sg.cell_is(p, sym.NS), Or( sg.cell_is_one_of(p.translate(Vector(-1, 0)), turns), sg.cell_is_one_of(p.translate(Vector(1, 0)), turns)))) if 0 < p.x < len(givens[0]) - 1: sg.solver.add( Implies( sg.cell_is(p, sym.EW), Or( sg.cell_is_one_of(p.translate(Vector(0, -1)), turns), sg.cell_is_one_of(p.translate(Vector(0, 1)), turns)))) 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(): """Yajilin solver example.""" for y in range(HEIGHT): for x in range(WIDTH): if (y, x) in GIVENS: direction, count = GIVENS[(y, x)] sys.stdout.write(str(count)) sys.stdout.write(direction) sys.stdout.write(" ") else: sys.stdout.write(" ") print() print() lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sym = grilops.loops.LoopSymbolSet(lattice) sym.append("BLACK", chr(0x25AE)) sym.append("GRAY", chr(0x25AF)) sym.append("INDICATIVE", " ") sg = grilops.SymbolGrid(lattice, sym) grilops.loops.LoopConstrainer(sg, single_loop=True) for y in range(HEIGHT): for x in range(WIDTH): p = Point(y, x) if (y, x) in GIVENS: sg.solver.add(sg.cell_is(p, sym.INDICATIVE)) elif (y, x) in GRAYS: sg.solver.add(sg.cell_is(p, sym.GRAY)) else: sg.solver.add( Not(sg.cell_is_one_of(p, [sym.INDICATIVE, sym.GRAY]))) sg.solver.add( Implies( sg.cell_is(p, sym.BLACK), And(*[ n.symbol != sym.BLACK for n in sg.edge_sharing_neighbors(p) ]))) for (sy, sx), (direction, count) in GIVENS.items(): if direction == U: cells = [(y, sx) for y in range(sy)] elif direction == R: cells = [(sy, x) for x in range(sx + 1, WIDTH)] elif direction == D: cells = [(y, sx) for y in range(sy + 1, HEIGHT)] elif direction == L: cells = [(sy, x) for x in range(sx)] sg.solver.add( PbEq([(sg.cell_is(Point(y, x), sym.BLACK), 1) for (y, x) in cells], count)) def print_grid(): sg.print(lambda p, _: GIVENS[(p.y, p.x)][0] 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 load_puzzle( url: str) -> Tuple[SymbolGrid, Optional[Callable[[Point, int], str]]]: params = url.split('/') height = int(params[-2]) width = int(params[-3]) givens: PuzzleGivens = parse_url(url) lattice = get_rectangle_lattice(height, width) sym = SymbolSet([("B", chr(0x2588)), ("W", " ")]) sg = SymbolGrid(lattice, sym) sea_size = None min_region_size = None max_region_size = None # No question marks, so we can compute and constrain sizes if QMARK not in givens.values(): sea_size = height * width - sum(givens.values()) min_region_size = min(sea_size, *givens.values()) max_region_size = max(sea_size, *givens.values()) rc = RegionConstrainer(lattice, solver=sg.solver, min_region_size=min_region_size, max_region_size=max_region_size) # CODE BELOW IS LARGELY DERIVED FROM # https://github.com/obijywk/grilops/blob/master/examples/nurikabe.py # Make trivial deductions about shaded cells known_shaded = set() for p in givens: # 1's are surrounded by black squares if givens[p] == 1: for n in sg.lattice.edge_sharing_points(p): if n in sg.grid: sg.solver.add(sg.cell_is(n, sym.B)) known_shaded.add(n) else: # Check two-step lateral offsets for _, d in sg.lattice.edge_sharing_directions(): n = p.translate(d) if n.translate(d) in givens: sg.solver.add(sg.cell_is(n, sym.B)) known_shaded.add(n) # Check two-step diagonal offsets for (_, di), (_, dj) in itertools.combinations( sg.lattice.edge_sharing_directions(), 2): dn = p.translate(di).translate(dj) if dn != p and dn in givens: n1, n2 = p.translate(di), p.translate(dj) sg.solver.add(sg.cell_is(n1, sym.B)) sg.solver.add(sg.cell_is(n2, sym.B)) known_shaded.update((n1, n2)) # TODO: Conduct a few more trivial deductions, within reason. # Basic reachability (search) is a good one # If we found a cell that must be shaded, root the sea to reduce # the search space sea_root = None if len(known_shaded) >= 1: p = known_shaded.pop() sg.solver.add(rc.parent_grid[p] == grilops.regions.R) sg.solver.add(rc.region_size_grid[p] == height * width - sum(givens.values())) sea_root = p # # Constrain the sea sea_id = Int("sea-id") island_ids = [sg.lattice.point_to_index(p) for p in givens] if sea_root: sg.solver.add(sea_id == sg.lattice.point_to_index(sea_root)) else: sg.solver.add(sea_id >= 0) sg.solver.add(sea_id < height * width) for p in sg.lattice.points: if p in givens: # Given cells are unshaded by definition sg.solver.add(sg.cell_is(p, sym.W)) # Constrain given to be root of island tree to reduce possibilities sg.solver.add(rc.parent_grid[p] == grilops.regions.R) sg.solver.add(rc.region_id_grid[p] == sg.lattice.point_to_index(p)) if not sea_root: sg.solver.add(sea_id != sg.lattice.point_to_index(p)) if givens[p] != QMARK: sg.solver.add(rc.region_size_grid[p] == givens[p]) else: # If we placed a sea root, then all regions are accounted for, so # and cell that is not a given and not the sea root is not a root. if sea_root: if p != sea_root: sg.solver.add(rc.parent_grid[p] != grilops.regions.R) else: # No shaded cells known, so ust say that non given white cells # can't be region roots sg.solver.add( Implies(sg.cell_is(p, sym.W), rc.parent_grid[p] != grilops.regions.R)) # Iff a cell is white, it is an island sg.solver.add( sg.cell_is(p, sym.W) == Or( *[rc.region_id_grid[p] == iid for iid in island_ids])) # Iff a cell is shaded, it is part of the sea sg.solver.add( sg.cell_is(p, sym.B) == (rc.region_id_grid[p] == sea_id)) if sea_size is not None: sg.solver.add( Implies(sg.cell_is(p, sym.B), rc.region_size_grid[p] == sea_size)) # Iff two adjacent cells have the same color, they are part of the same # region adjacent_cells = [n.symbol for n in sg.edge_sharing_neighbors(p)] adjacent_region_ids = [ n.symbol for n in sg.lattice.edge_sharing_neighbors(rc.region_id_grid, p) ] for cell, region_id in zip(adjacent_cells, adjacent_region_ids): sg.solver.add( (sg.grid[p] == cell) == (rc.region_id_grid[p] == region_id)) # The sea cannot contain 2x2 shaded for sy in range(height - 1): for sx in range(width - 1): pool_cells = [ sg.grid[Point(y, x)] for y in range(sy, sy + 2) for x in range(sx, sx + 2) ] sg.solver.add(Not(And(*[cell == sym.B for cell in pool_cells]))) def print_fn(p: Point, i: int) -> str: if p in givens: if givens[p] == QMARK: return "?" return str(givens[p]) return None return sg, print_fn
def load_puzzle( url: str, ura_mashu=False ) -> Tuple[SymbolGrid, Optional[Callable[[Point, int], str]]]: params = url.split('/') height = int(params[-2]) width = int(params[-3]) givens: PuzzleGivens = parse_url(url) lattice = get_rectangle_lattice(height, width) sym = LoopSymbolSet(lattice) sym.append('EMPTY', ' ') sg = SymbolGrid(lattice, sym) lc = LoopConstrainer(sg, single_loop=True) straights = [sym.NS, sym.EW] turns = [sym.NE, sym.SE, sym.SW, sym.NW] # CODE BELOW IS FROM # https://github.com/obijywk/grilops/blob/master/examples/masyu.py # If we have givens, then restrict a pearl to reduce possibilities if not len(givens) == 0: sg.solver.add(lc.loop_order_grid[next(iter(givens.keys()))] == 0) for p in sg.lattice.points: if givens[p]: if (givens[p] == MASYU_BLACK_PEARL) == (not ura_mashu): # The loop must turn at a black circle. sg.solver.add(sg.cell_is_one_of(p, turns)) # All connected adjacent cells must contain straight segments. for n in sg.edge_sharing_neighbors(p): if n.location.y < p.y: sg.solver.add( Implies(sg.cell_is_one_of(p, [sym.NE, sym.NW]), sg.cell_is(n.location, sym.NS))) if n.location.y > p.y: sg.solver.add( Implies(sg.cell_is_one_of(p, [sym.SE, sym.SW]), sg.cell_is(n.location, sym.NS))) if n.location.x < p.x: sg.solver.add( Implies(sg.cell_is_one_of(p, [sym.SW, sym.NW]), sg.cell_is(n.location, sym.EW))) if n.location.x > p.x: sg.solver.add( Implies(sg.cell_is_one_of(p, [sym.NE, sym.SE]), sg.cell_is(n.location, sym.EW))) else: # The loop must go straight through a white circle. sg.solver.add(sg.cell_is_one_of(p, straights)) # At least one connected adjacent cell must turn. if 0 < p.y < height - 1: sg.solver.add( Implies( sg.cell_is(p, sym.NS), Or( sg.cell_is_one_of(p.translate(Vector(-1, 0)), turns), sg.cell_is_one_of(p.translate(Vector(1, 0)), turns)))) if 0 < p.x < width - 1: sg.solver.add( Implies( sg.cell_is(p, sym.EW), Or( sg.cell_is_one_of(p.translate(Vector(0, -1)), turns), sg.cell_is_one_of(p.translate(Vector(0, 1)), turns)))) def print_fn(p: Point, i: int) -> str: if givens[p] == MASYU_WHITE_PEARL: return chr(0x25cb) if givens[p] == MASYU_BLACK_PEARL: return chr(0x25cf) return None return sg, print_fn
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")