def solve_fillomino(height, width, problem, checkered=False, is_non_con=False, is_anti_knight=False): solver = Solver() size = solver.int_array((height, width), 1, height * width) solver.add_answer_key(size) group_id = graph.division_connected_variable_groups(solver, group_size=size) solver.ensure( (group_id[:, :-1] == group_id[:, 1:]) == (size[:, :-1] == size[:, 1:])) solver.ensure( (group_id[:-1, :] == group_id[1:, :]) == (size[:-1, :] == size[1:, :])) for y in range(height): for x in range(width): if problem[y][x] >= 1: solver.ensure(size[y, x] == problem[y][x]) if checkered: color = solver.bool_array((height, width)) solver.ensure( (group_id[:, :-1] == group_id[:, 1:]) == (color[:, :-1] == color[:, 1:])) solver.ensure((group_id[:-1, :] == group_id[1:, :]) == ( color[:-1, :] == color[1:, :])) if is_non_con: graph.numbers_non_consecutive(solver, size) if is_anti_knight: graph.numbers_anti_knight(solver, size) is_sat = solver.solve() return is_sat, size
def solve_building(n, u, d, l, r): solver = Solver() answer = solver.int_array((n, n), 1, n) solver.add_answer_key(answer) for i in range(n): solver.ensure(alldifferent(answer[i, :])) solver.ensure(alldifferent(answer[:, i])) def num_visible_buildings(cells): cells = list(cells) res = 1 for i in range(1, len(cells)): res += fold_and([cells[j] < cells[i] for j in range(i)]).cond(1, 0) return res for i in range(n): if u[i] >= 1: solver.ensure(num_visible_buildings(answer[:, i]) == u[i]) if d[i] >= 1: solver.ensure( num_visible_buildings(reversed(list(answer[:, i]))) == d[i]) if l[i] >= 1: solver.ensure(num_visible_buildings(answer[i, :]) == l[i]) if r[i] >= 1: solver.ensure( num_visible_buildings(reversed(list(answer[i, :]))) == r[i]) is_sat = solver.solve() return is_sat, answer
def generate_latin_square(size, is_anti_knight=False): solver = Solver() latin_array = solver.int_array((size, size), 1, size) line = random.sample(range(1, size + 1), size) row = random.randrange(size) for i in range(size): solver.ensure(latin_array[row, i] == line[i]) solver.ensure(alldifferent(latin_array[i, :])) solver.ensure(alldifferent(latin_array[:, i])) if is_anti_knight: graph.numbers_anti_knight(solver, latin_array) solver.find_answer() latin = [[0 for _ in range(size)] for _ in range(size)] for y in range(size): for x in range(size): latin[x][y] = latin_array[y, x].sol if is_anti_knight: return latin for i in range(3): lines = random.sample(range(size), 2) latin = _swap_latin_square_row(size, latin, lines[0], lines[1]) lines = random.sample(range(size), 2) latin = _swap_latin_square_column(size, latin, lines[0], lines[1]) return latin
def solve_nurikabe(height, width, problem, unknown_low=None): solver = Solver() clues = [] for y in range(height): for x in range(width): if problem[y][x] >= 1 or problem[y][x] == -1: clues.append((y, x, problem[y][x])) division = solver.int_array((height, width), 0, len(clues)) roots = [None] + list(map(lambda x: (x[0], x[1]), clues)) graph.division_connected(solver, division, len(clues) + 1, roots=roots) is_white = solver.bool_array((height, width)) solver.ensure(is_white == (division != 0)) solver.add_answer_key(is_white) solver.ensure( (is_white[:-1, :] & is_white[1:, :]).then(division[:-1, :] == division[1:, :])) solver.ensure((is_white[:, :-1] & is_white[:, 1:]).then(division[:, :-1] == division[:, 1:])) solver.ensure(is_white[:-1, :-1] | is_white[:-1, 1:] | is_white[1:, :-1] | is_white[1:, 1:]) for i, (y, x, n) in enumerate(clues): if n > 0: solver.ensure(count_true(division == (i + 1)) == n) elif n == -1 and unknown_low is not None: solver.ensure(count_true(division == (i + 1)) >= unknown_low) is_sat = solver.solve() return is_sat, is_white
def solve_ripple(height, width, problem): solver = Solver() numbers = solver.int_array((height, width), 1, height * width + 1) solver.add_answer_key(numbers) block_num = max(sum(problem[0:height], [])) + 1 blocks = [[] for _ in range(block_num)] for y in range(height): for x in range(width): blocks[problem[y][x]].append([y, x]) if problem[y + height][x] > 0: solver.ensure(numbers[y, x] == problem[y + height][x]) for block in blocks: solver.ensure(alldifferent([numbers[y, x] for y, x in block])) for y, x in block: solver.ensure(numbers[y, x] <= len(block)) for y in range(height): for x in range(width): for i in range(1, width - x): solver.ensure( (numbers[y, x] == numbers[y, x + i]).then(numbers[y, x] < i)) for i in range(1, height - y): solver.ensure( (numbers[y, x] == numbers[y + i, x]).then(numbers[y, x] < i)) is_sat = solver.solve() return is_sat, numbers
def solve_doppelblock(n, clue_row, clue_column): solver = Solver() answer = solver.int_array((n, n), 0, n - 2) solver.add_answer_key(answer) def sequence_constraint(cells, v): s = 0 for i in range(n): s += (fold_or(cells[:i] == 0) & fold_or(cells[i+1:] == 0)).cond(cells[i], 0) return s == v def occurrence_constraint(cells): solver.ensure(count_true(cells == 0) == 2) for i in range(1, n - 1): solver.ensure(count_true(cells == i) == 1) for i in range(n): occurrence_constraint(answer[i, :]) occurrence_constraint(answer[:, i]) if clue_row[i] >= 0: solver.ensure(sequence_constraint(answer[i, :], clue_row[i])) if clue_column[i] >= 0: solver.ensure(sequence_constraint(answer[:, i], clue_column[i])) is_sat = solver.solve() return is_sat, answer
def solve_koutano(height, width, problem): solver = Solver() col = solver.int_array(width, 0, width - 1) row = solver.int_array(height, 0, height - 1) solver.add_answer_key(col) solver.add_answer_key(row) solver.ensure(alldifferent(col)) solver.ensure(alldifferent(row)) for y in range(height): for x in range(width): if problem[y][x] >= 0: solver.ensure(col[x] + row[y] == problem[y][x]) is_sat = solver.solve() return is_sat, col, row
def solve_view(height, width, problem, is_non_con=False, is_anti_knight=False): solver = Solver() has_number = solver.bool_array((height, width)) graph.active_vertices_connected(solver, has_number) nums = solver.int_array((height, width), 0, height + width) solver.add_answer_key(nums) solver.add_answer_key(has_number) to_up = solver.int_array((height, width), 0, height - 1) solver.ensure(to_up[0, :] == 0) solver.ensure(to_up[1:, :] == has_number[:-1, :].cond(0, to_up[:-1, :] + 1)) to_down = solver.int_array((height, width), 0, height - 1) solver.ensure(to_down[-1, :] == 0) solver.ensure(to_down[:-1, :] == has_number[1:, :].cond(0, to_down[1:, :] + 1)) to_left = solver.int_array((height, width), 0, width - 1) solver.ensure(to_left[:, 0] == 0) solver.ensure(to_left[:, 1:] == has_number[:, :-1].cond(0, to_left[:, :-1] + 1)) to_right = solver.int_array((height, width), 0, width - 1) solver.ensure(to_right[:, -1] == 0) solver.ensure(to_right[:, :-1] == has_number[:, 1:].cond(0, to_right[:, 1:] + 1)) solver.ensure(has_number.then(nums == to_up + to_left + to_down + to_right)) solver.ensure((has_number[:-1, :] & has_number[1:, :]).then(nums[:-1, :] != nums[1:, :])) solver.ensure((has_number[:, :-1] & has_number[:, 1:]).then(nums[:, :-1] != nums[:, 1:])) solver.ensure((~has_number).then(nums == 0)) for y in range(height): for x in range(width): if problem[y][x] >= 0: solver.ensure(nums[y, x] == problem[y][x]) solver.ensure(has_number[y, x]) if is_non_con: graph.numbers_non_consecutive(solver, nums, has_number) if is_anti_knight: graph.numbers_anti_knight(solver, nums, has_number) is_sat = solver.solve() return is_sat, nums, has_number
def check_problem_constraints(height, width, problem, flg, circ=-1): if flg is None and circ == -1: return True for clue in problem: a = 0 for i in range(2, 6): if clue[i] >= 0: a += 1 solver = Solver() roots = map(lambda x: (x[0], x[1]), problem) division = solver.int_array((height, width), 0, len(problem) - 1) graph.division_connected(solver, division, len(problem), roots=roots) solver.add_answer_key(division) for i, (y, x, u, l, d, r) in enumerate(problem): solver.ensure(division[y, x] == i) if flg is not None and flg[i]: solver.ensure(count_true(division == i) >= 4) if u >= 0: solver.ensure(count_true(division[:y, :] == i) == u) if d >= 0: solver.ensure(count_true(division[(y + 1):, :] == i) == d) if l >= 0: solver.ensure(count_true(division[:, :x] == i) == l) if r >= 0: solver.ensure(count_true(division[:, (x + 1):] == i) == r) # encircling constraint if circ != -1: col = solver.bool_array((height, width)) solver.ensure(col[0, :]) solver.ensure(col[-1, :]) solver.ensure(col[:, 0]) solver.ensure(col[:, -1]) solver.ensure( ((division[1:, :] != circ) & (division[:-1, :] != circ)).then(col[1:, :] == col[:-1, :])) solver.ensure( ((division[:, 1:] != circ) & (division[:, :-1] != circ)).then(col[:, 1:] == col[:, :-1])) solver.ensure( ((division[1:, 1:] != circ) & (division[:-1, :-1] != circ)).then(col[1:, 1:] == col[:-1, :-1])) solver.ensure( ((division[:-1, 1:] != circ) & (division[1:, :-1] != circ)).then(col[:-1, 1:] == col[1:, :-1])) solver.ensure(fold_or(col & (division != circ))) solver.ensure(fold_or((~col) & (division != circ))) sat = solver.find_answer() return sat
def solve_amarune(height, width, problem): solver = Solver() numbers = solver.int_array((height, width), 1, height * width) solver.add_answer_key(numbers) solver.ensure(alldifferent(numbers)) for y in range(height): for x in range(width - 1): solver.ensure(numbers[y, x] > numbers[y, x + 1]) for y in range(height): for x in range(width): if problem[y][x] > 0: solver.ensure(numbers[y, x] == problem[y][x]) for y in range(height): for x in range(width - 1): for i in range(height * width): if problem[y + height][x] in [0, 1, 2]: solver.ensure((numbers[y, x + 1] == i).then( numbers[y, x] % i == problem[y + height][x])) elif problem[y + height][x] == 3: solver.ensure( (numbers[y, x + 1] == i).then(numbers[y, x] % i >= 3)) for y in range(height - 1): for x in range(width): for i in range(height * width): if problem[y + height * 2][x] in [0, 1, 2]: solver.ensure( ((numbers[y, x] > numbers[y + 1, x]) & (numbers[y + 1, x] == i)).then( numbers[y, x] % i == problem[y + height * 2][x])) solver.ensure(((numbers[y + 1, x] > numbers[y, x]) & (numbers[y, x] == i)).then( numbers[y + 1, x] % i == problem[y + height * 2][x])) elif problem[y + height * 2][x] == 3: solver.ensure(((numbers[y, x] > numbers[y + 1, x]) & (numbers[y + 1, x] == i)).then( numbers[y, x] % i >= 3)) solver.ensure(((numbers[y + 1, x] > numbers[y, x]) & (numbers[y, x] == i)).then( numbers[y + 1, x] % i >= 3)) is_sat = solver.solve() return is_sat, numbers
def solve_compass(height, width, problem): solver = Solver() roots = map(lambda x: (x[0], x[1]), problem) division = solver.int_array((height, width), 0, len(problem) - 1) graph.division_connected(solver, division, len(problem), roots=roots) solver.add_answer_key(division) for i, (y, x, u, l, d, r) in enumerate(problem): solver.ensure(division[y, x] == i) if u >= 0: solver.ensure(count_true(division[:y, :] == i) == u) if d >= 0: solver.ensure(count_true(division[(y + 1):, :] == i) == d) if l >= 0: solver.ensure(count_true(division[:, :x] == i) == l) if r >= 0: solver.ensure(count_true(division[:, (x + 1):] == i) == r) is_sat = solver.solve() return is_sat, division
def solve_sukoro(height, width, problem, is_anti_knight=False): solver = Solver() has_number = solver.bool_array((height, width)) graph.active_vertices_connected(solver, has_number) nums = solver.int_array((height, width), -1, 4) solver.add_answer_key(nums) solver.add_answer_key(has_number) for y in range(height): for x in range(width): neighbors = [] if y > 0: neighbors.append(has_number[y-1, x]) solver.ensure((has_number[y, x] & has_number[y-1, x]).then(nums[y, x] != nums[y-1, x])) if y < height - 1: neighbors.append(has_number[y+1, x]) solver.ensure((has_number[y, x] & has_number[y+1, x]).then(nums[y, x] != nums[y+1, x])) if x > 0: neighbors.append(has_number[y, x-1]) solver.ensure((has_number[y, x] & has_number[y, x-1]).then(nums[y, x] != nums[y, x-1])) if x < width - 1: neighbors.append(has_number[y, x+1]) solver.ensure((has_number[y, x] & has_number[y, x+1]).then(nums[y, x] != nums[y, x+1])) solver.ensure(has_number[y, x].then(count_true(neighbors) == nums[y, x])) solver.ensure((~has_number).then(nums < 0)) for y in range(height): for x in range(width): if problem[y][x] >= 0: solver.ensure(nums[y, x] == problem[y][x]) solver.ensure(has_number[y, x]) if is_anti_knight: graph.numbers_anti_knight(solver, nums, has_number) is_sat = solver.solve() return is_sat, nums, has_number
def solve_sudoku(problem, n=3, is_non_con=False, is_anti_knight=False, is_non_dicon=False, is_anti_alfil=False): size = n * n solver = Solver() answer = solver.int_array((size, size), 1, size) solver.add_answer_key(answer) for i in range(size): solver.ensure(alldifferent(answer[i, :])) solver.ensure(alldifferent(answer[:, i])) for y in range(n): for x in range(n): solver.ensure( alldifferent(answer[y * n:(y + 1) * n, x * n:(x + 1) * n])) for y in range(size): for x in range(size): if problem[y][x] >= 1: solver.ensure(answer[y, x] == problem[y][x]) if is_non_con: graph.numbers_non_consecutive(solver, answer) if is_anti_knight: graph.numbers_anti_knight(solver, answer) if is_non_dicon: graph.numbers_non_diagonally_consecutive(solver, answer) if is_anti_alfil: graph.numbers_anti_alfil(solver, answer) is_sat = solver.solve() return is_sat, answer
def solve_simplegako(height, width, problem): solver = Solver() numbers = solver.int_array((height, width), 1, height + width - 1) solver.add_answer_key(numbers) for y in range(height): for x in range(width): if problem[y][x] > 0: solver.ensure(numbers[y, x] == problem[y][x]) def _count(y0, x0, n): sum = -1 for y in range(height): sum += (numbers[y, x0] == n).cond(1, 0) for x in range(width): sum += (numbers[y0, x] == n).cond(1, 0) return sum for y in range(height): for x in range(width): solver.ensure(numbers[y, x] == _count(y, x, numbers[y, x])) is_sat = solver.solve() return is_sat, numbers
def solve_slalom(height, width, origin, is_black, gates, reference_sol_loop=None): solver = Solver() loop = BoolGridFrame(solver, height - 1, width - 1) loop_dir = BoolGridFrame(solver, height - 1, width - 1) solver.add_answer_key(loop.all_edges()) graph.active_edges_single_cycle(solver, loop) gate_ord = solver.int_array((height, width), 0, len(gates)) passed = solver.bool_array((height, width)) gate_id = [[None for _ in range(width)] for _ in range(height)] for y, x, d, l, n in gates: if d == 0: # horizontal gate_cells = [(y, x + i) for i in range(l)] elif d == 1: # vertical gate_cells = [(y + i, x) for i in range(l)] for y2, x2 in gate_cells: gate_id[y2][x2] = n solver.ensure( count_true([passed[y2, x2] for y2, x2 in gate_cells]) == 1) solver.ensure(passed[origin]) for y in range(height): for x in range(width): neighbors = [] if y > 0: neighbors.append((y - 1, x)) if y < height - 1: neighbors.append((y + 1, x)) if x > 0: neighbors.append((y, x - 1)) if x < width - 1: neighbors.append((y, x + 1)) # in-degree, out-degree solver.ensure( count_true([ loop[y + y2, x + x2] & (loop_dir[y + y2, x + x2] != ((y2, x2) < (y, x))) for y2, x2 in neighbors ]) == passed[y, x].cond(1, 0)) solver.ensure( count_true([ loop[y + y2, x + x2] & (loop_dir[y + y2, x + x2] == ((y2, x2) < (y, x))) for y2, x2 in neighbors ]) == passed[y, x].cond(1, 0)) if is_black[y][x]: solver.ensure(~passed[y, x]) continue if (y, x) == origin: continue if gate_id[y][x] is None: for y2, x2 in neighbors: solver.ensure((loop[y + y2, x + x2] & (loop_dir[y + y2, x + x2] != ((y2, x2) < (y, x)))).then( (gate_ord[y2, x2] == gate_ord[y, x]))) else: for y2, x2 in neighbors: solver.ensure((loop[y + y2, x + x2] & (loop_dir[y + y2, x + x2] != ((y2, x2) < (y, x)))).then( (gate_ord[y2, x2] == gate_ord[y, x] - 1))) if gate_id[y][x] >= 1: solver.ensure(passed[y, x].then(gate_ord[y, x] == gate_id[y][x])) # auxiliary constraint for y0 in range(height): for x0 in range(width): for y1 in range(height): for x1 in range(width): if (y0, x0) < (y1, x1) and gate_id[y0][ x0] is not None and gate_id[y1][x1] is not None: solver.ensure((passed[y0, x0] & passed[y1, x1]).then( gate_ord[y0, x0] != gate_ord[y1, x1])) if reference_sol_loop is not None: avoid_reference_sol = [] for y in range(height): for x in range(width): if y < height - 1: avoid_reference_sol.append( loop.vertical[y, x] != reference_sol_loop.vertical[y, x].sol) if x < width - 1: avoid_reference_sol.append(loop.horizontal[ y, x] != reference_sol_loop.horizontal[y, x].sol) solver.ensure(fold_or(avoid_reference_sol)) is_sat = solver.find_answer() return is_sat, loop else: is_sat = solver.solve() return is_sat, loop
def solve_firefly(height, width, problem): solver = Solver() has_line = BoolGridFrame(solver, height - 1, width - 1) solver.add_answer_key(has_line) line_ul = BoolGridFrame(solver, height - 1, width - 1) line_dr = BoolGridFrame(solver, height - 1, width - 1) solver.ensure( Array(list(has_line)) == (Array(list(line_ul)) | Array(list(line_dr)))) solver.ensure(~(Array(list(line_ul)) & Array(list(line_dr)))) # unicyclic (= connected) ignored_edge = BoolGridFrame(solver, height - 1, width - 1) solver.ensure(count_true(ignored_edge) == 1) rank = solver.int_array((height, width), 0, height * width - 1) solver.ensure( (line_ul.horizontal & ~ignored_edge.horizontal).then(rank[:, :-1] < rank[:, 1:])) solver.ensure((line_ul.vertical & ~ignored_edge.vertical).then(rank[:-1, :] < rank[1:, :])) solver.ensure( (line_dr.horizontal & ~ignored_edge.horizontal).then(rank[:, :-1] > rank[:, 1:])) solver.ensure((line_dr.vertical & ~ignored_edge.vertical).then(rank[:-1, :] > rank[1:, :])) max_n_turn = 0 for y in range(height): for x in range(width): if problem[y][x][0] != '.' and problem[y][x][1] != '?': max_n_turn = max(max_n_turn, int(problem[y][x][1:])) n_turn_unknown = max_n_turn + 1 n_turn_horizontal = solver.int_array((height, width - 1), 0, max_n_turn + 1) n_turn_vertical = solver.int_array((height - 1, width), 0, max_n_turn + 1) for y in range(height): for x in range(width): # u, d, l, r adj = [] # (line_in, line_out, # turn) if y > 0: adj.append((line_dr.vertical[y - 1, x], line_ul.vertical[y - 1, x], n_turn_vertical[y - 1, x])) else: adj.append(None) if y < height - 1: adj.append((line_ul.vertical[y, x], line_dr.vertical[y, x], n_turn_vertical[y, x])) else: adj.append(None) if x > 0: adj.append( (line_dr.horizontal[y, x - 1], line_ul.horizontal[y, x - 1], n_turn_horizontal[y, x - 1])) else: adj.append(None) if x < width - 1: adj.append((line_ul.horizontal[y, x], line_dr.horizontal[y, x], n_turn_horizontal[y, x])) else: adj.append(None) if problem[y][x][0] != '.': if problem[y][x][0] == '^': out_idx = 0 elif problem[y][x][0] == 'v': out_idx = 1 elif problem[y][x][0] == '<': out_idx = 2 elif problem[y][x][0] == '>': out_idx = 3 else: raise ValueError('invalid direction: {}'.format( problem[y][x][0])) if adj[out_idx] is None: solver.ensure(False) break solver.ensure(adj[out_idx][1]) if problem[y][x][1] != '?': solver.ensure(adj[out_idx][2] == int(problem[y][x][1:])) else: solver.ensure(adj[out_idx][2] == n_turn_unknown) for i in range(4): if adj[i] is not None and i != out_idx: solver.ensure(~adj[i][1]) solver.ensure( adj[i][0].then((adj[i][2] == 0) | (adj[i][2] == n_turn_unknown))) else: adj_present = list(filter(lambda x: x is not None, adj)) solver.ensure( count_true(map(lambda x: x[0], adj_present)) <= 1) solver.ensure( count_true(map(lambda x: x[0], adj_present)) == count_true( map(lambda x: x[1], adj_present))) for i in range(4): for j in range(4): if adj[i] is not None and adj[j] is not None and i != j: if (i // 2) == (j // 2): # straight solver.ensure( (adj[i][0] & adj[j][1]).then(adj[i][2] == adj[j][2])) else: solver.ensure( (adj[i][0] & adj[j][1] ).then(((adj[i][2] == n_turn_unknown) & (adj[j][2] == n_turn_unknown)) | (adj[i][2] == adj[j][2] + 1))) is_sat = solver.solve() return is_sat, has_line
def solve_shakashaka(height, width, problem): # 1 2 3 4 # +-+ + + +-+ # |/ |\ /| \| # + +-+ +-+ + solver = Solver() answer = solver.int_array((height, width), 0, 4) solver.add_answer_key(answer) for y in range(height): for x in range(width): if problem[y][x] is not None: solver.ensure(answer[y, x] == 0) if problem[y][x] >= 0: solver.ensure(count_true(answer.four_neighbors(y, x) != 0) == problem[y][x]) for y in range(height + 1): for x in range(width + 1): diagonals = [] is_empty = [] is_white_angle = [] if y > 0 and x > 0: diagonals.append(answer[y - 1, x - 1] == 4) diagonals.append(answer[y - 1, x - 1] == 2) is_empty.append(answer[y - 1, x - 1] == 0 if problem[y - 1][x - 1] is None else False) if problem[y - 1][x - 1] is None: is_white_angle.append((answer[y - 1, x - 1] == 0) | (answer[y - 1, x - 1] == 1)) else: diagonals += [False, False] is_empty.append(False) if y < height and x > 0: diagonals.append(answer[y, x - 1] == 1) diagonals.append(answer[y, x - 1] == 3) is_empty.append(answer[y, x - 1] == 0 if problem[y][x - 1] is None else False) if problem[y][x - 1] is None: is_white_angle.append((answer[y, x - 1] == 0) | (answer[y, x - 1] == 2)) else: diagonals += [False, False] is_empty.append(False) if y < height and x < width: diagonals.append(answer[y, x] == 2) diagonals.append(answer[y, x] == 4) is_empty.append(answer[y, x] == 0 if problem[y][x] is None else False) if problem[y][x] is None: is_white_angle.append((answer[y, x] == 0) | (answer[y, x] == 3)) else: diagonals += [False, False] is_empty.append(False) if y > 0 and x < width: diagonals.append(answer[y - 1, x] == 3) diagonals.append(answer[y - 1, x] == 1) is_empty.append(answer[y - 1, x] == 0 if problem[y - 1][x] is None else False) if problem[y - 1][x] is None: is_white_angle.append((answer[y - 1, x] == 0) | (answer[y - 1, x] == 4)) else: diagonals += [False, False] is_empty.append(False) for i in range(8): if diagonals[i] is False: continue if i % 2 == 0: solver.ensure(diagonals[i].then( diagonals[(i + 3) % 8] | (is_empty[(i + 3) % 8 // 2] & diagonals[(i + 5) % 8]) )) else: solver.ensure(diagonals[i].then( diagonals[(i + 5) % 8] | (is_empty[(i + 5) % 8 // 2] & diagonals[(i + 3) % 8]) )) solver.ensure(count_true(is_white_angle) != 3) is_sat = solver.solve() return is_sat, answer
def solve_lits(height, width, blocks): solver = Solver() is_black = solver.bool_array((height, width)) solver.add_answer_key(is_black) # black cells are connected graph.active_vertices_connected(solver, is_black) # no 2x2 black cells solver.ensure(~(is_black[1:, 1:] & is_black[1:, :-1] & is_black[:-1, 1:] & is_black[:-1, :-1])) block_id = [[-1 for _ in range(width)] for _ in range(height)] for i, block in enumerate(blocks): for y, x in block: block_id[y][x] = i num_straight = solver.int_array(len(blocks), 0, 2) has_t = solver.bool_array(len(blocks)) for i in range(len(blocks)): solver.ensure(count_true([is_black[p] for p in blocks[i]]) == 4) adjacent_pairs = [] is_straight = [] is_t = [] for y, x in blocks[i]: neighbor_same_block = [] for y2, x2 in is_black.four_neighbor_indices(y, x): if block_id[y2][x2] == i: neighbor_same_block.append((y2, x2)) if (y, x) < (y2, x2): adjacent_pairs.append(is_black[y, x] & is_black[y2, x2]) solver.ensure(is_black[y, x].then(fold_or([is_black[p] for p in neighbor_same_block]))) tmp = [] if 0 < y < height - 1 and block_id[y - 1][x] == i and block_id[y + 1][x] == i: tmp.append(fold_and(is_black[y-1:y+2, x])) if 0 < x < width - 1 and block_id[y][x - 1] == i and block_id[y][x + 1] == i: tmp.append(fold_and(is_black[y, x-1:x+2])) if len(tmp) >= 1: is_straight.append(fold_or(tmp)) if len(neighbor_same_block) >= 3: is_t.append(count_true([is_black[p] for p in neighbor_same_block]) >= 3) solver.ensure(count_true(adjacent_pairs) == 3) solver.ensure(num_straight[i] == count_true(is_straight)) solver.ensure(has_t[i] == fold_or(is_t)) for y in range(height): for x in range(width): if y < height - 1 and block_id[y][x] != block_id[y + 1][x]: i = block_id[y][x] j = block_id[y + 1][x] solver.ensure((is_black[y, x] & is_black[y + 1, x]).then( (num_straight[i] != num_straight[j]) | (has_t[i] != has_t[j]) )) if x < width - 1 and block_id[y][x] != block_id[y][x + 1]: i = block_id[y][x] j = block_id[y][x + 1] solver.ensure((is_black[y, x] & is_black[y, x + 1]).then( (num_straight[i] != num_straight[j]) | (has_t[i] != has_t[j]) )) is_sat = solver.solve() return is_sat, is_black