예제 #1
0
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")
예제 #2
0
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
예제 #3
0
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")
예제 #4
0
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")
예제 #5
0
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")
예제 #6
0
파일: lits.py 프로젝트: space-egret/grilops
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")
예제 #7
0
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")
예제 #8
0
파일: tapa.py 프로젝트: space-egret/grilops
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")
예제 #9
0
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")
예제 #10
0
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")
예제 #11
0
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")
예제 #12
0
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")
예제 #13
0
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")
예제 #14
0
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")
예제 #15
0
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")
예제 #16
0
    ("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)],
예제 #17
0
파일: heyawake.py 프로젝트: obijywk/grilops
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")
예제 #18
0
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")
예제 #19
0
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")
예제 #20
0
파일: nurikabe.py 프로젝트: Skynet0/griddy
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
예제 #21
0
파일: masyu.py 프로젝트: Skynet0/griddy
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
예제 #22
0
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")