Example #1
0
def constrain_visited_counts(sg):
    """The number of visited squares constraints must be satisfied."""
    for y, count in enumerate(ROW_COUNTS):
        if count is None:
            continue
        row = [sg.grid[(y, x)] for x in range(SIZE)]
        terms = [(c != SYM.EMPTY, 1) for c in row]
        sg.solver.add(PbEq(terms, count))
    for x, count in enumerate(COL_COUNTS):
        if count is None:
            continue
        col = [sg.grid[(y, x)] for y in range(SIZE)]
        terms = [(c != SYM.EMPTY, 1) for c in col]
        sg.solver.add(PbEq(terms, count))
Example #2
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")
Example #3
0
def add_area_constraints(lattice, sc):
    """Ensure each area of the puzzle contains exactly one tetrahex."""
    for area_label in {c for line in AREAS for c in line}:
        area_points = []
        area_type_cells = []
        area_instance_cells = []
        for p in lattice.points:
            r, c = point_to_areas_row_col(p)
            if AREAS[r][c] == area_label:
                area_points.append(p)
                area_type_cells.append(sc.shape_type_grid[p])
                area_instance_cells.append(sc.shape_instance_grid[p])

        area_type = Int(f"at-{area_label}")
        sc.solver.add(area_type >= 0)
        sc.solver.add(area_type <= 4)
        sc.solver.add(
            And(*[Or(c == -1, c == area_type) for c in area_type_cells]))

        area_instance = Int(f"ai-{area_label}")
        sc.solver.add(
            Or(*[
                area_instance == lattice.point_to_index(p) for p in area_points
            ]))
        sc.solver.add(
            And(*
                [Or(c == -1, c == area_instance)
                 for c in area_instance_cells]))

        sc.solver.add(PbEq([(c != -1, 1) for c in area_type_cells], 4))
Example #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")
Example #5
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")
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")
Example #7
0
def n_sense_codons(
        T: CodeRef,
        n_codons: int,
        codons: Sequence[CodonRef] = z3_enum_codons,
        aminos: Sequence[AminoRef] = z3_enum_aminos,
        exclude: Optional[AminoRef] = None
) -> List[ConstraintRef]:
    if exclude is None:
        exclude = (get_null(aminos), get_stop(aminos))
    return [PbEq(
        [
            (And([decode(T, c) != aa for aa in exclude]), 1)
            for c in codons
        ], k=n_codons
    )]
Example #8
0
def add_given_pair_constraints(sg, rc):
  """Add constraints for the pairs of givens contained within each region."""
  # Each larger (root) given must be paired with a single smaller given in its
  # same region, and the size of the region must be between the givens' values.
  for lp, lv in GIVENS.items():
    partner_terms = []
    for sp, sv in GIVENS.items():
      if lp == sp or lv <= sv:
        continue

      # Rule out pairs that can't possibly work, due to the Manhattan distance
      # between givens being too large.
      manhattan_distance = abs(lp.y - sp.y) + abs(lp.x - sp.x)
      min_region_size = manhattan_distance + 1
      if lv < min_region_size:
        sg.solver.add(
            rc.region_id_grid[sp] != sg.lattice.point_to_index(lp))
        continue

      partner_terms.append(
          And(
              # The smaller given must not be a region root.
              Not(rc.parent_grid[sp] == grilops.regions.R),

              # The givens must share a region, rooted at the larger given.
              rc.region_id_grid[sp] == sg.lattice.point_to_index(lp),

              # The region must be larger than the smaller given's value.
              sv < rc.region_size_grid[lp]
          )
      )
    if not partner_terms:
      sg.solver.add(rc.parent_grid[lp] != grilops.regions.R)
    else:
      sg.solver.add(
          Implies(
              rc.parent_grid[lp] == grilops.regions.R,
              And(
                  rc.region_size_grid[lp] < lv,
                  PbEq([(term, 1) for term in partner_terms], 1)
              )
          )
      )
Example #9
0
def exactly_one_codon_per_amino(
        T: CodeRef,
        codons: Sequence[CodonRef] = z3_enum_codons,
        aminos: Sequence[AminoRef] = z3_enum_aminos,
        exclude: Optional[AminoRef] = None
) -> List[ConstraintRef]:
    """

    :param T: genetic code
    :param codons: list of CodonRef objects
    :param aminos: list of AminoRef objects
    :param exclude: range of T to ignore (list or list-like of aminos)
    :return: list of constraints
    """
    if exclude is None:
        exclude = (get_null(aminos), get_stop(aminos))

    return [
        PbEq([(decode(T, c) == aa, 1) for c in codons], k=1)
        for aa in aminos if aa not in exclude
    ]
Example #10
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")
Example #11
0
def Exactly(*args):
    assert len(args) >= 1, 'Non empty list of arguments expected'
    return PbEq([(arg, 1) for arg in args[:-1]], args[-1])
Example #12
0
def main():
    """Akari solver example."""
    size = 10
    lattice = grilops.get_square_lattice(size)

    black_cells = {
        (0, 0): None,
        (0, 3): None,
        (0, 9): None,
        (1, 7): None,
        (2, 1): 3,
        (2, 6): 0,
        (3, 2): 2,
        (3, 5): None,
        (3, 9): 1,
        (4, 3): 1,
        (4, 4): 0,
        (4, 5): None,
        (5, 4): 1,
        (5, 5): None,
        (5, 6): None,
        (6, 0): None,
        (6, 4): 2,
        (6, 7): 2,
        (7, 3): None,
        (7, 8): None,
        (8, 2): 1,
        (9, 0): 0,
        (9, 6): 1,
        (9, 9): 0,
    }

    def print_given(point):
        if point in black_cells:
            v = black_cells.get(point)
            if v is None:
                return chr(0x2588)
            return str(v)
        return None

    lattice.print(print_given)
    print()

    sym = grilops.SymbolSet([
        ("BLACK", chr(0x2588)),
        ("EMPTY", " "),
        ("LIGHT", "*"),
    ])
    sg = grilops.SymbolGrid(lattice, sym)

    for point in lattice.points:
        if point in black_cells:
            sg.solver.add(sg.cell_is(point, sym.BLACK))
            light_count = black_cells[point]
            if light_count is not None:
                sg.solver.add(
                    PbEq([(n.symbol == sym.LIGHT, 1)
                          for n in sg.edge_sharing_neighbors(point)],
                         light_count))
        else:
            # All black cells are given; don't allow this cell to be black.
            sg.solver.add(sg.cell_is_one_of(point, [sym.EMPTY, sym.LIGHT]))

    def is_black(c):
        return c == sym.BLACK

    def count_light(c):
        return If(c == sym.LIGHT, 1, 0)

    for point in lattice.points:
        if point in black_cells:
            continue
        visible_light_count = sum(
            grilops.sightlines.count_cells(
                sg, n.location, n.direction, count=count_light, stop=is_black)
            for n in sg.edge_sharing_neighbors(point))
        # Ensure that each light cannot see any other lights, and that each cell
        # is lit by at least one light.
        sg.solver.add(
            If(sg.cell_is(point, sym.LIGHT), visible_light_count == 0,
               visible_light_count > 0))

    if sg.solve():
        sg.print()
        print()
        if sg.is_unique():
            print("Unique solution")
        else:
            print("Alternate solution")
            sg.print()
    else:
        print("No solution")
Example #13
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")
Example #14
0
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")
Example #15
0
is_scout = np.array([ Bool("is_scout_%s%s" % (r, c)) for (r, c) in board() ]).reshape(H, W).tolist()

# Piece placement
no_scouts_in_lakes = [ Not(is_scout[r][c]) for (r, c) in lakes() ]

# Segments
open_rows = [ list(zip(repeat(r), range(W))) for r in chain(range(0, 4), range(6, H)) ]
open_cols = [ list(zip(range(H), repeat(c))) for c in chain(range(0, 2), range(4, 6), range(8, W)) ]
lake_rows = [ list(zip(repeat(r), range(c, c + 2))) for r in range(4, 6) for c in (0, 4, 8) ]
lake_cols = [ list(zip(range(r, r + 4), repeat(c))) for r in (0, 6) for c in chain(range(2, 4), range(6, 8)) ]
segments = open_rows + open_cols + lake_rows + lake_cols

# TODO: incorporate the fixed issue https://github.com/Z3Prover/z3/issues/1782 as soon as there is a new release available
at_most_one_scout_per_segment = [
    PbEq([
        (is_scout[r][c], 1)
        for (r, c) in s
    ], 1)
    for s in segments
]

# Scout moves in the left (L), right (R), downward (D) and upward (U) directions
def L_scout_moves_from(r, c):
    if r in chain(range(0, 4), range(6, H)):
        return range(0, c)
    elif c in range(0, 2):
        return range(0, c)
    elif c in range(4, 6):
        return range(4, c)
    elif c in range(8, W):
        return range(8, c)
    else:
Example #16
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")
Example #17
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")
Example #18
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")
Example #19
0
 def __add_single_copy_constraints(self, shape_index, shape):
     sum_terms = []
     for shape_type in self.__shape_type_grid.values():
         sum_terms.append((shape_type == shape_index, 1))
     self.__solver.add(PbEq(sum_terms, len(shape.offsets_with_payloads)))
Example #20
0
                    Or([is_bomb[rb][c] for rb in squares_in_between(rD, r)]))
            for rD in D_scout_moves_rows[r, c]
        ] + [
            Implies(is_scout[rU][c],
                    Or([is_bomb[rb][c] for rb in squares_in_between(r, rU)]))
            for rU in U_scout_moves_rows[r, c]
        ])) for (r, c) in board() if (r, c) not in lakes()
]

# Clauses
s = Solver()
s.add(no_scouts_and_bombs_on_same_square)
s.add(no_scouts_or_bombs_in_lakes)
s.add(no_bombs_in_dmz)
s.add(at_most_one_more_scout_than_bombs_per_segment)
s.add(no_scout_threatens_another_scout)
s.add(at_most_six_bombs_in_red_setup)
s.add(at_most_six_bombs_in_blu_setup)

# Objective
max_scouts = 24
num_scouts = PbEq([(is_scout[r][c], 1) for (r, c) in board()], max_scouts)
s.add(num_scouts)

if s.check() == sat:
    print("Maximum number of scouts satisfying constraints == %s." %
          max_scouts)
    print(diagram(s.model()))
else:
    print("Z3 failed to find a solution.")
Example #21
0
scout_moves_from = np.array([
    list(chain(
        zip(repeat(r), L_scout_moves_from(r, c)),
        zip(repeat(r), R_scout_moves_from(r, c)),
        zip(D_scout_moves_from(r, c), repeat(c)),
        zip(U_scout_moves_from(r, c), repeat(c))
    ))
    for (r, c) in board()
]).reshape(H, W)

scouts_threaten_exactly_one_other_scout = [
    Implies(
        is_scout[r][c],
        PbEq([
            (is_scout[dr][dc], 1)
            for (dr, dc) in scout_moves_from[r, c]
        ], 1)
    )
    for (r, c) in board() if (r, c) not in lakes()
]

# Clauses (Optimize() takes too long on this problem, Solver() will proof max_scouts = 18 instantly, and disproof max_scouts = 20 within a minute)
s = Solver()
s.add(no_scouts_in_lakes)
s.add(at_most_two_scouts_per_segment)
s.add(scouts_threaten_exactly_one_other_scout)

# Objective
max_scouts = 20
num_scouts = PbEq([ (is_scout[r][c], 1) for (r, c) in board() ], max_scouts)
s.add(num_scouts)
Example #22
0
 def __add_single_copy_constraints(self, shape_index, shape):
     sum_terms = []
     for p in self.__shape_type_grid:
         sum_terms.append((self.__shape_type_grid[p] == shape_index, 1))
     self.__solver.add(PbEq(sum_terms, len(shape)))