Exemple #1
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")
Exemple #2
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()
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")
Exemple #4
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")
Exemple #5
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")
Exemple #6
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()
Exemple #7
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()
Exemple #8
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")
Exemple #9
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")
Exemple #10
0
"""Skyscraper solver example.

Example puzzle can be found at https://www.puzzlemix.com/Skyscraper.
"""

from z3 import Datatype, Distinct, If, IntSort

import grilops
import grilops.sightlines
from grilops.geometry import Point, Vector

SIZE = 5
SYM = grilops.make_number_range_symbol_set(1, SIZE)
GIVEN_TOP = [4, 2, 1, 2, 3]
GIVEN_LEFT = [3, 2, 3, 2, 1]
GIVEN_RIGHT = [3, 4, 1, 2, 2]
GIVEN_BOTTOM = [1, 4, 3, 2, 2]


def main():
    """Skyscraper solver example."""
    lattice = grilops.get_square_lattice(SIZE)
    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:
Exemple #11
0
def main():
    """Greater Than Killer Sudoku solver example."""

    cages = [
        "AABCCDDEE",
        "FFBGGHIJK",
        "FLMNHHIJK",
        "LLMNNOIPP",
        "QQRNSOTTU",
        "VWRSSXTUU",
        "VWYYSXZaa",
        "bccddXZee",
        "bbbdffZgg",
    ]

    cage_sums = {
        "B": 6,
        "D": 16,
        "F": 14,
        "H": 17,
        "I": 9,
        "J": 12,
        "K": 9,
        "L": 20,
        "M": 13,
        "N": 29,
        "O": 4,
        "R": 8,
        "S": 12,
        "V": 8,
        "W": 14,
        "Y": 17,
        "b": 11,
        "d": 11,
        "e": 8,
    }

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

    add_sudoku_constraints(sg)

    # 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))

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

    # Add constraints between cage sums.
    def cage_sum_greater(a, b):
        sg.solver.add(Sum(*cage_cells[a]) > Sum(*cage_cells[b]))

    def cage_sum_equal(a, b):
        sg.solver.add(Sum(*cage_cells[a]) == Sum(*cage_cells[b]))

    cage_sum_equal("C", "G")
    cage_sum_greater("J", "E")
    cage_sum_greater("E", "K")
    cage_sum_greater("W", "c")
    cage_sum_greater("c", "b")
    cage_sum_greater("f", "d")
    cage_sum_greater("X", "f")

    if sg.solve():
        sg.print()
        print()
        if sg.is_unique():
            print("Unique solution")
        else:
            print("Alternate solution")
            sg.print()
    else:
        print("No solution")
Exemple #12
0
def main():
    """Outflight Entertainment sudoku solver example."""

    cages = [
        "AABCCD",
        "EFBCGD",
        "EFFHGI",
        "EJJHGI",
        "EKJHGL",
        "KKJLLL",
    ]

    peaks = [
        (0, 1),
        (0, 2),
        (1, 3),
        (1, 4),
        (1, 5),
        (2, 1),
        (2, 3),
        (3, 0),
        (3, 5),
        (4, 2),
        (5, 1),
        (5, 4),
    ]

    extract = {
        "A": (5, 2),
        "B": (0, 3),
        "C": (3, 2),
        "D": (1, 4),
        "E": (0, 1),
        "F": (1, 5),
        "G": (4, 2),
        "H": (3, 5),
        "I": (1, 0),
        "J": (5, 5),
        "K": (3, 4),
        "L": (5, 1),
        "M": (0, 5),
        "N": (4, 4),
    }

    def answer(sg):
        solved_grid = sg.solved_grid()
        s = ""
        s += chr(64 + solved_grid[extract["A"]] + solved_grid[extract["B"]])
        s += chr(64 + solved_grid[extract["C"]] + solved_grid[extract["D"]])
        s += chr(64 + solved_grid[extract["E"]] + solved_grid[extract["F"]] +
                 solved_grid[extract["G"]])
        s += chr(64 + solved_grid[extract["H"]] + solved_grid[extract["I"]])
        s += chr(64 + solved_grid[extract["J"]] + solved_grid[extract["K"]] +
                 solved_grid[extract["L"]])
        s += chr(64 + solved_grid[extract["M"]] + solved_grid[extract["N"]])
        return s

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

    add_sudoku_constraints(sg)

    # Constrain regions to match the cages and be rooted at the peaks.
    cage_label_to_region_id = {}
    for py, px in peaks:
        cage_label_to_region_id[cages[py][px]] = lattice.point_to_index(
            (py, px))

    rc = grilops.regions.RegionConstrainer(lattice, sg.solver)
    for y, x in lattice.points:
        sg.solver.add(
            rc.region_id_grid[(y, x)] == cage_label_to_region_id[cages[y][x]])

    # Within each region, a parent cell must have a greater value than a child
    # cell, so that the values increase as you approach the root cell (the peak).
    for p in lattice.points:
        for n in sg.edge_sharing_neighbors(p):
            sg.solver.add(
                Implies(
                    rc.edge_sharing_direction_to_index(
                        n.direction) == rc.parent_grid[p],
                    n.symbol > sg.grid[p]))

    if sg.solve():
        sg.print()
        print()
        print(answer(sg))
        while not sg.is_unique():
            print()
            print("Alternate solution")
            sg.print()
            print()
            print(answer(sg))
    else:
        print("No solution")