예제 #1
0
def shikaku(givens):
    """Solver for Shikaku minipuzzles."""
    sym = grilops.make_number_range_symbol_set(0, SIZE * SIZE - 1)
    sg = grilops.SymbolGrid(LATTICE, sym)
    rc = grilops.regions.RegionConstrainer(LATTICE,
                                           solver=sg.solver,
                                           rectangular=True)
    shifter = Shifter(sg.solver)

    for p in LATTICE.points:
        sg.solver.add(sg.cell_is(p, rc.region_id_grid[p]))
        given = givens[p.y][p.x]
        if given > 0:
            given = shifter.given(p, given)
            sg.solver.add(rc.parent_grid[p] == grilops.regions.R)
            sg.solver.add(rc.region_size_grid[p] == given)
        else:
            sg.solver.add(rc.parent_grid[p] != grilops.regions.R)

    assert sg.solve()
    sg.print()
    print()
    shifter.print_shifts()
    print()
    return shifter.eval_binary()
예제 #2
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")
예제 #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 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")
예제 #5
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")
예제 #6
0
def main():
    """Status Park (Loop) solver example."""
    for row in GIVENS:
        for cell in row:
            sys.stdout.write(cell)
        print()

    points = []
    for y, row in enumerate(GIVENS):
        for x, c in enumerate(row):
            if c != X:
                points.append(Point(y, x))
    lattice = RectangularLattice(points)

    sym = grilops.loops.LoopSymbolSet(lattice)
    sym.append("EMPTY", " ")

    sg = grilops.SymbolGrid(lattice, sym)
    grilops.loops.LoopConstrainer(sg, single_loop=True)
    sc = ShapeConstrainer(
        lattice, [Shape([Vector(y, x) for y, x in shape]) for shape in SHAPES],
        solver=sg.solver,
        allow_rotations=True,
        allow_reflections=True,
        allow_copies=False)

    for p in points:
        if GIVENS[p.y][p.x] == W:
            # White circles must be part of the loop.
            sg.solver.add(sym.is_loop(sg.grid[p]))
        elif GIVENS[p.y][p.x] == B:
            # Black circles must be part of a shape.
            sg.solver.add(sc.shape_type_grid[p] != -1)

        # A cell is part of the loop if and only if it is not part of
        # any shape.
        sg.solver.add(sym.is_loop(sg.grid[p]) == (sc.shape_type_grid[p] == -1))

        # Orthogonally-adjacent cells must be part of the same shape.
        for n in sg.edge_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")
예제 #7
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")
예제 #8
0
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")
예제 #9
0
def main():
    """Halloween Town / Valentine's Day Town solver."""
    sg = grilops.SymbolGrid(LATTICE, SYM)
    lc = grilops.loops.LoopConstrainer(sg, single_loop=True)

    # Cheat a little bit and force the loop order to start such that the answer
    # extraction starts at the correct place and proceeds in the correct order.
    sg.solver.add(lc.loop_order_grid[Point(5, 6)] == 0)
    sg.solver.add(lc.loop_order_grid[Point(5, 5)] == 1)

    # Count the number of Os in the puzzle answers.
    o_count = sum(c == "O" for row in ANSWERS for c in row)

    # There will be exactly twice as many turns as Os. This constraint is not
    # strictly necessary to add, but the solver runs faster when it is added.
    sg.solver.add(
        PbEq([(sg.cell_is_one_of(p, TURN_SYMBOLS), 1) for p in LATTICE.points],
             o_count * 2))

    # Find the loop order values for all of the turns.
    turn_loop_orders = [Int(f"tlo-{i}") for i in range(o_count * 2)]
    for tlo in turn_loop_orders:
        sg.solver.add(tlo >= 0)
        sg.solver.add(tlo < 8 * 8)
    for i in range(len(turn_loop_orders) - 1):
        sg.solver.add(turn_loop_orders[i] < turn_loop_orders[i + 1])
    for p in LATTICE.points:
        # Figure out each turn's loop order value.
        sg.solver.add(
            Implies(
                sg.cell_is_one_of(p, TURN_SYMBOLS),
                Or(*[tlo == lc.loop_order_grid[p]
                     for tlo in turn_loop_orders])))

        if ANSWERS[p.y][p.x] == "O":
            # An O must be a turn.
            sg.solver.add(sg.cell_is_one_of(p, TURN_SYMBOLS))

            # An O must be in an odd position in the list of turn loop order values.
            or_terms = []
            for i in range(1, len(turn_loop_orders), 2):
                or_terms.append(lc.loop_order_grid[p] == turn_loop_orders[i])
            sg.solver.add(Or(*or_terms))

    if sg.solve():
        sg.print()
        print(extract_answer(sg, lc.loop_order_grid))
        print()
        if sg.is_unique():
            print("Unique solution")
        else:
            print("Alternate solution")
            sg.print()
    else:
        print("No solution")
예제 #10
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")
예제 #11
0
파일: shape.py 프로젝트: obijywk/grilops
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")
예제 #12
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")
예제 #13
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")
예제 #14
0
def main():
    """Skyscraper solver example."""
    lattice = grilops.get_square_lattice(SIZE)
    directions = {d.name: d for d in lattice.edge_sharing_directions()}
    sg = grilops.SymbolGrid(lattice, SYM)

    # Each row and each column contains each building height exactly once.
    for y in range(SIZE):
        sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for x in range(SIZE)]))
    for x in range(SIZE):
        sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for y in range(SIZE)]))

    # We'll use the sightlines accumulator to keep track of a tuple storing:
    #   the tallest building we've seen so far
    #   the number of visible buildings we've encountered
    Acc = Datatype("Acc")  # pylint: disable=C0103
    Acc.declare("acc", ("tallest", IntSort()), ("num_visible", IntSort()))
    Acc = Acc.create()  # pylint: disable=C0103

    def accumulate(a, height):
        return Acc.acc(
            If(height > Acc.tallest(a), height, Acc.tallest(a)),
            If(height > Acc.tallest(a),
               Acc.num_visible(a) + 1, Acc.num_visible(a)))

    for x, c in enumerate(GIVEN_TOP):
        sg.solver.add(c == Acc.num_visible(
            grilops.sightlines.reduce_cells(sg, Point(0, x), directions["S"],
                                            Acc.acc(0, 0), accumulate)))
    for y, c in enumerate(GIVEN_LEFT):
        sg.solver.add(c == Acc.num_visible(
            grilops.sightlines.reduce_cells(sg, Point(y, 0), directions["E"],
                                            Acc.acc(0, 0), accumulate)))
    for y, c in enumerate(GIVEN_RIGHT):
        sg.solver.add(c == Acc.num_visible(
            grilops.sightlines.reduce_cells(sg, Point(
                y, SIZE - 1), directions["W"], Acc.acc(0, 0), accumulate)))
    for x, c in enumerate(GIVEN_BOTTOM):
        sg.solver.add(c == Acc.num_visible(
            grilops.sightlines.reduce_cells(sg, Point(
                SIZE - 1, x), directions["N"], Acc.acc(0, 0), accumulate)))

    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():
    """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")
예제 #16
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")
예제 #17
0
def main():
    """Hex kakuro solver example."""
    points = []
    points.extend([Point(1, i) for i in range(-3, 4, 2)])
    points.extend([Point(2, i) for i in range(-4, 5, 2)])
    points.extend([Point(3, -5), Point(3, -3), Point(3, 3), Point(3, 5)])
    points.extend([
        Point(4, -6),
        Point(4, -4),
        Point(4, -2),
        Point(4, 2),
        Point(4, 4),
        Point(4, 6)
    ])
    points.extend([Point(5, -5), Point(5, -3), Point(5, 3), Point(5, 5)])
    points.extend([Point(6, i) for i in range(-4, 5, 2)])
    points.extend([Point(7, i) for i in range(-3, 4, 2)])
    lattice = PointyToppedHexagonalLattice(points)
    sym = grilops.make_number_range_symbol_set(1, 9)
    sg = grilops.SymbolGrid(lattice, sym)

    dirs_by_name = dict(sg.lattice.edge_sharing_directions())
    for entry in SUMS:
        (y, x), dirname, given = entry
        d = dirs_by_name[dirname]
        s = grilops.sightlines.count_cells(sg,
                                           Point(y, x),
                                           d,
                                           count=lambda c: c)
        sg.solver.add(given == s)

    for p in lattice.points:
        for d in [Vector(0, 2), Vector(1, 1), Vector(1, -1)]:
            if p.translate(d.negate()) not in sg.grid:
                q = p
                ps = []
                while q in sg.grid:
                    ps.append(sg.grid[q])
                    q = q.translate(d)
                sg.solver.add(Distinct(*ps))

    if sg.solve():
        sg.print()
        print()
        if sg.is_unique():
            print("Unique solution")
        else:
            print("Alternate solution")
            sg.print()
    else:
        print("No solution")
예제 #18
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")
예제 #19
0
def killer_sudoku(cages: List[str], cage_sum_grid: List[List[int]]) -> str:
    """Solver for Killer Sudoku minipuzzles."""
    sym = grilops.make_number_range_symbol_set(1, SIZE)
    sg = grilops.SymbolGrid(LATTICE, sym)
    shifter = Shifter(sg.solver)

    # Add normal sudoku constraints.
    for y in range(SIZE):
        sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for x in range(SIZE)]))
    for x in range(SIZE):
        sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for y in range(SIZE)]))
    for z in range(9):
        top = (z // 3) * 3
        left = (z % 3) * 3
        cells = [
            sg.grid[Point(y, x)] for y in range(top, top + 3)
            for x in range(left, left + 3)
        ]
        sg.solver.add(Distinct(*cells))

    # Build a map from each cage label to the cells within that cage.
    cage_cells = defaultdict(list)
    for p in LATTICE.points:
        cage_cells[cages[p.y][p.x]].append(sg.grid[p])

    # The digits used in each cage must be unique.
    for cells_in_cage in cage_cells.values():
        sg.solver.add(Distinct(*cells_in_cage))

    cage_sums = {}
    for p in LATTICE.points:
        cage_sum = cage_sum_grid[p.y][p.x]
        if cage_sum > 0:
            shifted_cage_sum = shifter.given(p, cage_sum)
            cage_label = cages[p.y][p.x]
            assert cage_label not in cage_sums
            cage_sums[cage_label] = shifted_cage_sum

    # Add constraints for cages with given sums.
    for cage_label, shifted_cage_sum in cage_sums.items():
        sg.solver.add(Sum(*cage_cells[cage_label]) == shifted_cage_sum)

    assert sg.solve()
    sg.print()
    print()
    shifter.print_shifts()
    print()
    return shifter.eval_binary()
예제 #20
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")
예제 #21
0
def skyscraper(givens: Dict[Direction, List[int]]) -> str:
    """Solver for Skyscraper minipuzzles."""
    sym = grilops.make_number_range_symbol_set(1, SIZE)
    sg = grilops.SymbolGrid(LATTICE, sym)
    shifter = Shifter(sg.solver)

    # Each row and each column contains each building height exactly once.
    for y in range(SIZE):
        sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for x in range(SIZE)]))
    for x in range(SIZE):
        sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for y in range(SIZE)]))

    # We'll use the sightlines accumulator to keep track of a tuple storing:
    #   the tallest building we've seen so far
    #   the number of visible buildings we've encountered
    Acc = Datatype("Acc")  # pylint: disable=C0103
    Acc.declare("acc", ("tallest", IntSort()), ("num_visible", IntSort()))
    Acc = Acc.create()  # pylint: disable=C0103

    def accumulate(a, height):
        return Acc.acc(
            If(height > Acc.tallest(a), height, Acc.tallest(a)),
            If(height > Acc.tallest(a),
               Acc.num_visible(a) + 1, Acc.num_visible(a)))

    for d, gs in givens.items():
        for i, g in enumerate(gs):
            if d.vector.dy != 0:
                g = g - shifter.col_shifts.get(i, 0)
                p = Point(0 if d.vector.dy < 0 else SIZE - 1, i)
            elif d.vector.dx != 0:
                g = g - shifter.row_shifts.get(i, 0)
                p = Point(i, 0 if d.vector.dx < 0 else SIZE - 1)
            sg.solver.add(g == Acc.num_visible(  # type: ignore[attr-defined]
                grilops.sightlines.reduce_cells(
                    sg,
                    p,
                    LATTICE.opposite_direction(d),
                    Acc.acc(0, 0),  # type: ignore[attr-defined]
                    accumulate)))

    assert sg.solve()
    sg.print()
    print()
    shifter.print_shifts()
    print()
    return shifter.eval_binary()
예제 #22
0
def main():
  """Sudoku solver example."""
  givens = [
      [5, 3, 0, 0, 7, 0, 0, 0, 0],
      [6, 0, 0, 1, 9, 5, 0, 0, 0],
      [0, 9, 8, 0, 0, 0, 0, 6, 0],
      [8, 0, 0, 0, 6, 0, 0, 0, 3],
      [4, 0, 0, 8, 0, 3, 0, 0, 1],
      [7, 0, 0, 0, 2, 0, 0, 0, 6],
      [0, 6, 0, 0, 0, 0, 2, 8, 0],
      [0, 0, 0, 4, 1, 9, 0, 0, 5],
      [0, 0, 0, 0, 8, 0, 0, 7, 9],
  ]

  sym = grilops.make_number_range_symbol_set(1, 9)
  sg = grilops.SymbolGrid(grilops.get_square_lattice(9), sym)

  for y, given_row in enumerate(givens):
    for x, given in enumerate(given_row):
      if given != 0:
        sg.solver.add(sg.cell_is(Point(y, x), sym[given]))

  for y in range(9):
    sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for x in range(9)]))

  for x in range(9):
    sg.solver.add(Distinct(*[sg.grid[Point(y, x)] for y in range(9)]))

  for z in range(9):
    top = (z // 3) * 3
    left = (z % 3) * 3
    cells = [sg.grid[Point(y, x)] for y in range(top, top + 3) for x in range(left, left + 3)]
    sg.solver.add(Distinct(*cells))

  if sg.solve():
    sg.print()
    print()
    if sg.is_unique():
      print("Unique solution")
    else:
      print("Alternate solution")
      sg.print()
  else:
    print("No solution")
예제 #23
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")
예제 #24
0
def main():
  """Castle Wall solver example."""
  sg = grilops.SymbolGrid(LATTICE, SYM)
  lc = LoopConstrainer(sg, single_loop=True)

  for p, (io, expected_count, direction) in GIVENS.items():
    # Constrain whether the given cell is inside or outside of the loop. This
    # also prevents these cells from containing loop symbols themselves.
    sg.solver.add(lc.inside_outside_grid[p] == io)

    if expected_count is not None and direction is not None:
      # Count and constrain the number of loop segments in the given direction.
      seg_syms = DIRECTION_SEGMENT_SYMBOLS[direction]
      actual_count = grilops.sightlines.count_cells(
          sg, p, direction,
          lambda c, ss=seg_syms: If(Or(*[c == s for s in ss]), 1, 0)
      )
      sg.solver.add(actual_count == expected_count)

  def show_cell(p, _):
    if p in GIVENS:
      if GIVENS[p][0] == I:
        return chr(0x25AB)
      if GIVENS[p][0] == O:
        return chr(0x25AA)
    return None

  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")
예제 #25
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")
예제 #26
0
def main():
  """Masyu solver example."""
  lattice = PointyToppedHexagonalLattice([
      givens_row_col_to_point(r, c)
      for r in range(len(GIVENS))
      for c in range(len(GIVENS[r]))
  ])
  sym = grilops.loops.LoopSymbolSet(lattice)
  sym.append("EMPTY", ". \n  ")
  sg = grilops.SymbolGrid(lattice, sym)
  grilops.loops.LoopConstrainer(sg, single_loop=True)

  turns = [sym.NESE, sym.ESW, sym.WSE, sym.NWSW, sym.WNE, sym.ENW]

  for p in lattice.points:
    # 60-degree turns are disallowed.
    sg.solver.add(sg.grid[p] != sym.ESE)
    sg.solver.add(sg.grid[p] != sym.SESW)
    sg.solver.add(sg.grid[p] != sym.WSW)
    sg.solver.add(sg.grid[p] != sym.WNW)
    sg.solver.add(sg.grid[p] != sym.NENW)
    sg.solver.add(sg.grid[p] != sym.ENE)

    r, c = point_to_givens_row_col(p)

    if GIVENS[r][c] == 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 _, d in lattice.edge_sharing_directions():
        np = p.translate(d)
        if np in sg.grid:
          sg.solver.add(Implies(
              sg.cell_is_one_of(p, sym.symbols_for_direction(d)),
              sg.cell_is(np, sym.symbol_for_direction_pair(d, d.negate()))
          ))

    elif GIVENS[r][c] == W:
      # The loop must go straight through a white circle.
      sg.solver.add(sg.cell_is_one_of(p, [sym.NESW, sym.EW, sym.NWSE]))

      # At least one connected adjacent cell must turn.
      for d in [Vector(-1, 1), Vector(0, 2), Vector(1, 1)]:
        np1 = p.translate(d)
        np2 = p.translate(d.negate())
        if np1 in sg.grid and np2 in sg.grid:
          sg.solver.add(Implies(
              sg.cell_is(p, sym.symbol_for_direction_pair(d, d.negate())),
              Or(
                  sg.cell_is_one_of(np1, turns),
                  sg.cell_is_one_of(np2, turns)
              )
          ))

  if sg.solve():
    solved_grid = sg.solved_grid()

    def print_function(p):
      return sym.symbols[solved_grid[p]].label

    lattice.print(print_function, "  \n  ")
    print()
    if sg.is_unique():
      print("Unique solution")
    else:
      print("Alternate solution")
      solved_grid = sg.solved_grid()
      lattice.print(print_function, "  \n  ")
      print()
  else:
    print("No solution")
예제 #27
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")
예제 #28
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")
예제 #29
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")
예제 #30
0
def main():
  """Battleship solver example."""
  sg = grilops.SymbolGrid(LATTICE, SYM)
  sc = grilops.shapes.ShapeConstrainer(
      LATTICE,
      [
          [Vector(0, i) for i in range(4)],
          [Vector(0, i) for i in range(3)],
          [Vector(0, i) for i in range(3)],
          [Vector(0, i) for i in range(2)],
          [Vector(0, i) for i in range(2)],
          [Vector(0, i) for i in range(2)],
          [Vector(0, i) for i in range(1)],
          [Vector(0, i) for i in range(1)],
          [Vector(0, i) for i in range(1)],
      ],
      solver=sg.solver,
      allow_rotations=True
  )

  # Constrain the given ship segment counts and ship segments.
  for y, count in enumerate(GIVENS_Y):
    sg.solver.add(
        PbEq([(Not(sg.cell_is(Point(y, x), SYM.X)), 1) for x in range(WIDTH)], count)
    )
  for x, count in enumerate(GIVENS_X):
    sg.solver.add(
        PbEq([(Not(sg.cell_is(Point(y, x), SYM.X)), 1) for y in range(HEIGHT)], count)
    )
  for p, s in GIVENS.items():
    sg.solver.add(sg.cell_is(p, s))

  for p in LATTICE.points:
    shape_type = sc.shape_type_grid[p]
    shape_id = sc.shape_instance_grid[p]
    touching_types = [
        n.symbol for n in LATTICE.vertex_sharing_neighbors(sc.shape_type_grid, p)
    ]
    touching_ids = [
        n.symbol for n in LATTICE.vertex_sharing_neighbors(sc.shape_instance_grid, p)
    ]

    # Link the X symbol to the absence of a ship segment.
    sg.solver.add(
        (sc.shape_type_grid[p] == -1) == sg.cell_is(p, SYM.X))

    # Ship segments of different ships may not touch.
    and_terms = []
    for touching_id in touching_ids:
      and_terms.append(
          Implies(
              shape_id >= 0,
              Or(touching_id == shape_id, touching_id == -1)
          )
      )
    sg.solver.add(And(*and_terms))

    # Choose the correct symbol for each ship segment.
    touching_count_terms = [(c == shape_type, 1) for c in touching_types]
    sg.solver.add(
        Implies(
            And(shape_type >= 0, PbEq(touching_count_terms, 2)),
            sg.cell_is(p, SYM.B)
        )
    )
    sg.solver.add(
        Implies(
            And(shape_type >= 0, PbEq(touching_count_terms, 0)),
            sg.cell_is(p, SYM.O)
        )
    )
    for n in sg.edge_sharing_neighbors(p):
      sg.solver.add(
          Implies(
              And(
                  shape_type >= 0,
                  PbEq(touching_count_terms, 1),
                  sc.shape_type_grid[n.location] == shape_type
              ),
              sg.cell_is(p, DIR_TO_OPPOSITE_SYM[n.direction])
          )
      )

  if sg.solve():
    sg.print()
    print()
    sc.print_shape_instances()
    print()
    if sg.is_unique():
      print("Unique solution")
    else:
      print("Alternate solution")
      sg.print()
      print()
      sc.print_shape_instances()
      print()
  else:
    print("No solution")