Ejemplo n.º 1
0
def constrain_sea(sym, sg, rc):
    """Add constraints to the sea cells."""

    # There must be only one sea, containing all black cells.
    sea_id = Int("sea-id")
    sg.solver.add(sea_id >= 0)
    sg.solver.add(sea_id < HEIGHT * WIDTH)
    for p in GIVENS:
        sg.solver.add(sea_id != sg.lattice.point_to_index(p))
    for y in range(HEIGHT):
        for x in range(WIDTH):
            p = Point(y, x)
            sg.solver.add(
                Implies(sg.cell_is(p, sym.B), rc.region_id_grid[p] == sea_id))
            sg.solver.add(
                Implies(sg.cell_is(p, sym.W), rc.region_id_grid[p] != sea_id))

    # The sea is not allowed to contain 2x2 areas of black cells.
    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])))
Ejemplo n.º 2
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")
Ejemplo n.º 3
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")
Ejemplo n.º 4
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")
Ejemplo n.º 5
0
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")
Ejemplo n.º 6
0
    def test_covers_point(self):
        """Unittest for ExpressionQuadTree.covers_point."""
        with self.assertRaises(ValueError):
            ExpressionQuadTree([])

        points = [Point(y, x) for y in range(4) for x in range(4)]
        t = ExpressionQuadTree(points)
        for p in points:
            self.assertTrue(t.covers_point(p))
        self.assertFalse(t.covers_point(Point(4, 0)))
        self.assertFalse(t.covers_point(Point(0, 4)))
        self.assertFalse(t.covers_point(Point(-1, 0)))
        self.assertFalse(t.covers_point(Point(0, -1)))
Ejemplo n.º 7
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")
Ejemplo n.º 8
0
    def test_get_point_expr(self):
        """Unittest for ExpressionQuadTree.get_point_expr."""
        points = [Point(y, x) for y in range(2) for x in range(2)]
        t = ExpressionQuadTree(points)

        y, x = Int("y"), Int("x")
        t.add_expr("test", lambda p: And(p.y == y, p.x == x))

        for p in points:
            expr = t.get_point_expr("test", p)
            self.assertEqual(expr, And(p.y == y, p.x == x))

        with self.assertRaises(ValueError):
            t.get_point_expr("test", Point(3, 3))
Ejemplo n.º 9
0
def add_sudoku_constraints(sg):
    """Add constraints for the normal Sudoku rules."""
    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))
Ejemplo n.º 10
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()
Ejemplo n.º 11
0
def add_nurikabe_constraints(sym, sg, rc):
    """Add the nurikabe constraints (one connected sea with no 2x2 regions)."""
    # There must be only one sea, containing all black cells.
    sea_id = Int("sea-id")
    for p in sg.lattice.points:
        sg.solver.add(sg.cell_is(p, sym.W) == (rc.region_id_grid[p] != sea_id))

    # Constrain sea_id to be the index of one of the points in
    # the smallest area, among those areas of size greater than 4.
    area_to_points = defaultdict(list)
    for p in sg.lattice.points:
        area_to_points[AREAS[p.y][p.x]].append(p)
    area_points = min((ps for ps in area_to_points.values() if len(ps) > 4),
                      key=len)
    sg.solver.add(
        Or(*[sea_id == sg.lattice.point_to_index(p) for p in area_points]))

    # The sea is not allowed to contain 2x2 areas of black cells.
    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(*[Not(cell == sym.W) for cell in pool_cells])))
Ejemplo n.º 12
0
def constrain_islands(sym, sg, rc):
    """Add constraints to the island cells."""
    # Each numbered cell is an island cell. The number in it is the number of
    # cells in that island. Each island must contain exactly one numbered cell.
    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.W))
                # Might as well force the given cell to be the root of the region's tree,
                # to reduce the number of possibilities.
                sg.solver.add(rc.parent_grid[p] == grilops.regions.R)
                sg.solver.add(rc.region_size_grid[p] == GIVENS[(y, x)])
            else:
                # Ensure that cells that are part of island regions are colored white.
                for gp in GIVENS:
                    island_id = sg.lattice.point_to_index(gp)
                    sg.solver.add(
                        Implies(rc.region_id_grid[p] == island_id,
                                sg.cell_is(p, sym.W)))
                # Force a non-given white cell to not be the root of the region's tree,
                # to reduce the number of possibilities.
                sg.solver.add(
                    Implies(sg.cell_is(p, sym.W),
                            rc.parent_grid[p] != grilops.regions.R))
Ejemplo n.º 13
0
def parse_url(url: str) -> PuzzleGivens:
    params = url.split('/')
    height = int(params[-2])
    width = int(params[-3])
    payload = params[-1]

    genre = params[-4].split('?')[-1]
    if genre not in GENRE_ALIASES:
        raise ValueError('Given URL is not a masyu')

    circles: PuzzleGivens = defaultdict(int)
    cell_num = 0
    payload_len = min(int((height * width + 2) / 3), len(payload))

    for c in payload[:payload_len]:
        n = int(c, 27)
        for t in [3**p for p in range(2, -1, -1)]:
            v = int(n / t) % 3
            if v > 0:
                y = int(cell_num / width)
                x = cell_num % width
                circles[Point(y, x)] = v
            cell_num += 1

    return circles
Ejemplo n.º 14
0
    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)))
Ejemplo n.º 15
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()
Ejemplo n.º 16
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")
Ejemplo n.º 17
0
 def test_get_exprs(self):
     """Unittest for ExpressionQuadTree.get_exprs."""
     t = ExpressionQuadTree(
         [Point(y, x) for y in range(2) for x in range(2)])
     t.add_expr("test", lambda p: p.y == 0)
     exprs = list(t.get_exprs("test"))
     self.assertEqual(len(exprs), 4)
     self.assertEqual(exprs.count(False), 2)
     self.assertEqual(exprs.count(True), 2)
Ejemplo n.º 18
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")
Ejemplo n.º 19
0
def is_outside(grid, model, y, x):
    """Returns whether the given point is effectively outside the loop.

  Returns 1 if the given point is not in the grid or outside the loop.
  Returns 0 if the given point is in the grid and inside the loop.
  """
    p = Point(y, x)
    if p in grid:
        return model.eval(grid[p]).as_long()
    return 1
Ejemplo n.º 20
0
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")
Ejemplo n.º 21
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")
Ejemplo n.º 22
0
def constrain_adjacent_cells(sg, rc):
    """Different regions of the same color may not be orthogonally adjacent."""
    for y in range(HEIGHT):
        for x in range(WIDTH):
            p = Point(y, x)
            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(
                    Implies(sg.grid[p] == cell,
                            rc.region_id_grid[p] == region_id))
Ejemplo n.º 23
0
def parse_url(url: str) -> PuzzleGivens:
    params = url.split('/')
    height = int(params[-2])
    width = int(params[-3])
    payload = params[-1]

    genre = params[-4].split('?')[-1]
    if genre not in GENRE_ALIASES:
        raise ValueError('Given URL is not a nurikabe')

    givens: PuzzleGivens = {}
    cell_num = 0

    def parse_char(payload: str, i: int) -> Tuple[int, int]:
        c = payload[i]
        if "0" <= c <= "9" or "a" <= c <= "f":
            return (int(c, 16), 1)
        elif c == "-":
            return (int(payload[i + 1:i + 3], 16), 3)
        elif c == "+":
            return (int(payload[i + 1:i + 4], 16), 4)
        elif c == "=":
            return (int(payload[i + 1:i + 4], 16) + 4096, 4)
        elif c == "%":
            return (int(payload[i + 1:i + 4], 16) + 8192, 4)
        elif c == ".":
            return (-2, 1)
        return (-1, 0)

    idx = 0
    while idx < len(payload) and cell_num < height * width:
        c = payload[idx]
        v, step = parse_char(payload, idx)
        if v != -1:
            y = int(cell_num / width)
            x = cell_num % width
            givens[Point(y, x)] = v
            idx += step
            cell_num += 1
        elif "g" <= c <= "z":
            cell_num += int(c, 36) - 15
            idx += 1
        else:
            idx += 1

    return givens
Ejemplo n.º 24
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")
Ejemplo n.º 25
0
def extract_instruction(solved_path):
  """Extract the instruction phrase from the solved path and letter grid."""
  offset_to_directions = {
    Vector(0, 0): "ES",
    Vector(0, 1): "SW",
    Vector(1, 0): "NE",
    Vector(1, 1): "NW",
  }
  extract = ""
  for y in range(len(letter_grid)):
    for x in range(len(letter_grid[0])):
      ok = True
      for v, ds in offset_to_directions.items():
        symbol_name = sym.symbols[solved_path[Point(y, x).translate(v)]].name
        if not all(d in symbol_name for d in ds):
          ok = False
          break
      if ok:
        extract += letter_grid[y][x]
  print(extract)
  print()
Ejemplo n.º 26
0
def parse_url(url: str) -> PuzzleGivens:
    params = url.split('/')
    width = int(params[-3])
    payload = params[-1]

    genre = params[-4].split('?')[-1]
    if genre not in GENRE_ALIASES:
        raise ValueError('Given URL is not a simpleloop')

    shaded: PuzzleGivens = defaultdict(bool)
    cell_num = 0

    for c in payload:
        n = int(c, 32)
        for b in [2**p for p in range(4, -1, -1)]:
            if n & b:
                y = int(cell_num / width)
                x = cell_num % width
                shaded[Point(y, x)] = True
            cell_num += 1

    return shaded
Ejemplo n.º 27
0
    def test_get_other_points_expr(self):
        """Unittest for ExpressionQuadTree.get_other_points_expr."""
        t = ExpressionQuadTree(
            [Point(y, x) for y in range(2) for x in range(2)])
        y = Int("y")
        t.add_expr("test", lambda p: p.y == y)

        expr = t.get_other_points_expr(
            "test",
            [Point(0, 1), Point(1, 0), Point(1, 1)])
        self.assertEqual(simplify(expr), y == 0)

        expr = t.get_other_points_expr("test", [Point(1, 0), Point(1, 1)])
        self.assertEqual(simplify(expr), y == 0)

        expr = t.get_other_points_expr("test", [])
        self.assertEqual(simplify(expr), And(y == 0, y == 1))
Ejemplo n.º 28
0
def add_neighbor_constraints(sg, y, x):
    """Add constraints for the given clue at (y, x)."""
    neighbor_locations = make_neighbor_locations(y, x)

    # Find all possible ways that neighboring cells could be shaded to match the
    # given clue. If we have 8 neighboring cells, we need to treat them as a
    # continuous ring; if we do not have 8 neighboring cells, we can assume that
    # the beginning and end cells of the neighbor location sequence are not
    # adjacent. Use set() to dedupe these possibilities.
    given_run_lengths = GIVENS[(y, x)]
    neighbor_patterns = set(
        place_runs([SYM.W] * len(neighbor_locations), given_run_lengths,
                   len(neighbor_locations) == 8))

    # Align the neighboring cell locations with the possible patterns with which
    # the neighboring cells may be filled in, and add constraints for each
    # possible pattern.
    or_terms = []
    for pattern in neighbor_patterns:
        and_terms = []
        for (ny, nx), symbol in zip(neighbor_locations, pattern):
            and_terms.append(sg.cell_is(Point(ny, nx), symbol))
        or_terms.append(And(*and_terms))
    sg.solver.add(Or(*or_terms))
Ejemplo n.º 29
0
def areas_row_col_to_point(r, c):
    """Converts a row and column in AREAS to a point."""
    y = r + 1
    num_givens = len(AREAS[r])
    x = 2 * c + 1 - num_givens
    return Point(y, x)
Ejemplo n.º 30
0
"""Araf solver example.

Example puzzle can be found at
https://www.gmpuzzles.com/blog/araf-rules-and-info/.
"""

from z3 import And, Implies, Not, PbEq

import grilops
import grilops.regions
from grilops.geometry import Point

HEIGHT, WIDTH = 7, 7
GIVENS = {
    Point(0, 0): 1,
    Point(0, 1): 9,
    Point(0, 4): 8,
    Point(1, 6): 8,
    Point(2, 1): 7,
    Point(2, 4): 9,
    Point(2, 6): 7,
    Point(3, 0): 10,
    Point(3, 3): 1,
    Point(4, 0): 3,
    Point(4, 2): 8,
    Point(4, 6): 6,
    Point(5, 0): 2,
    Point(6, 1): 1,
    Point(6, 5): 1,
    Point(6, 6): 3,
}