コード例 #1
0
def test_4(solver_status, board, window):
    """ Suppose both cells in the ceiling contain extra candidates.
    Suppose the common candidates are U and V, and none of the cells seen by both ceiling cells contains U.
    Then V can be eliminated from these two cells.
    A rectangle that meets this test is also called a Type 4 Unique Rectangle.
    Rating: 100
    """

    def _get_candidate_count(cells, candidate):
        return ''.join(board[cell] for cell in cells).count(candidate)

    def _check_rectangles(by_row):
        cells_by_x = CELLS_IN_ROW if by_row else CELLS_IN_COL
        cells_by_y = CELLS_IN_COL if by_row else CELLS_IN_ROW
        for floor_id_x in range(9):
            bi_values.clear()
            for floor_id_y in range(9):
                cell = cells_by_x[floor_id_x][floor_id_y]
                if len(board[cell]) == 2:
                    bi_values[board[cell]].add((floor_id_x, floor_id_y, CELL_BOX[cell]))
            for bi_value, coordinates in bi_values.items():
                if len(coordinates) == 2:
                    floor_a = coordinates.pop()
                    floor_b = coordinates.pop()
                    x_ids = _get_xyz(7, board, bi_value, cells_by_y[floor_a[1]], cells_by_y[floor_b[1]])
                    for x_id in x_ids:
                        ceiling_a = x_id * 9 + floor_a[1] if by_row else floor_a[1] * 9 + x_id
                        ceiling_b = x_id * 9 + floor_b[1] if by_row else floor_b[1] * 9 + x_id
                        boxes = {floor_a[2], floor_b[2], CELL_BOX[ceiling_a], CELL_BOX[ceiling_b]}
                        if len(boxes) == 2:
                            to_eliminate = None
                            cells = set(ALL_NBRS[ceiling_a]).intersection(ALL_NBRS[ceiling_b])
                            if not _get_candidate_count(cells, bi_value[0]):
                                to_eliminate = {(bi_value[1], ceiling_a), (bi_value[1], ceiling_b)}
                            elif not _get_candidate_count(cells, bi_value[1]):
                                to_eliminate = {(bi_value[0], ceiling_a), (bi_value[0], ceiling_b)}
                            if to_eliminate:
                                rows = (floor_a[0], x_id) if by_row else (floor_a[1], floor_b[1])
                                columns = (floor_a[1], floor_b[1]) if by_row else (floor_a[0], x_id)
                                rectangle = _get_rectangle(sorted(rows), sorted(columns))
                                other_candidates = set(board[ceiling_a]).union(board[ceiling_b]).difference(bi_value)
                                c_chain = _get_c_chain(rectangle, bi_value, other_candidates)
                                solver_status.capture_baseline(board, window)
                                eliminate_options(solver_status, board, to_eliminate, window)
                                if window:
                                    window.options_visible = window.options_visible.union(c_chain.keys()).union(cells)
                                kwargs["solver_tool"] = "uniqueness_test_4"
                                kwargs["c_chain"] = c_chain
                                kwargs["eliminate"] = to_eliminate
                                test_4.clues += len(solver_status.naked_singles)
                                test_4.options_removed += len(to_eliminate)
                                return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    bi_values = defaultdict(set)
    if _check_rectangles(True) or _check_rectangles(False):
        return kwargs
    return None
コード例 #2
0
def test_1(solver_status, board, window):
    """ If there is only one cell in the rectangle that contains extra candidates,
    then the common candidates can be eliminated from that cell.
    Rating: 100
    """

    set_remaining_candidates(board, solver_status)
    bi_values = _get_bi_values_dictionary(board, range(81))
    for bi_value, coordinates in bi_values.items():
        if len(coordinates) > 2:
            for triplet in combinations(coordinates, 3):
                rows = {position[0] for position in triplet}
                columns = {position[1] for position in triplet}
                boxes = {position[2] for position in triplet}
                if len(rows) == 2 and len(columns) == 2 and len(boxes) == 2:
                    rectangle = _get_rectangle(rows, columns)
                    if all(bi_value[0] in board[corner] and bi_value[1] in board[corner] for corner in rectangle):
                        for corner in rectangle:
                            if len(board[corner]) > 2:
                                to_eliminate = {(candidate, corner) for candidate in bi_value}
                                other_candidates = set(board[corner]).difference(bi_value)
                                c_chain = _get_c_chain(rectangle, bi_value, other_candidates)
                                solver_status.capture_baseline(board, window)
                                eliminate_options(solver_status, board, to_eliminate, window)
                                if window:
                                    window.options_visible = window.options_visible.union(rectangle)
                                kwargs = {"solver_tool": "uniqueness_test_1",
                                          "c_chain": c_chain,
                                          "eliminate": to_eliminate, }
                                test_1.clues += len(solver_status.naked_singles)
                                test_1.options_removed += len(to_eliminate)
                                return kwargs
    return None
コード例 #3
0
def unique_rectangles_(solver_status, board, window):
    """Remove candidates (options) using Unique Rectangle technique
    (see https://www.learn-sudoku.com/unique-rectangle.html)"""

    # 'pairs' data structure:
    # {'xy': [(row, col, blk), ...]}

    # Finding unique rectangles:
    #  - a pair is in at least three cells and the pair values are in options of the fourth one
    #  - the pair is in exactly two rows, to columns and two blocks

    def _reduce_rectangle(a_pair, corners):
        if all(board[corner] == a_pair for corner in corners):
            return False
        to_eliminate = []
        for corner in corners:
            if board[corner] != a_pair:
                subset = [cell for cell in rect if len(board[cell]) == 2]
                if a_pair[0] in board[corner]:
                    to_eliminate.append((a_pair[0], corner))
                if a_pair[1] in board[corner]:
                    to_eliminate.append((a_pair[1], corner))
                if to_eliminate:
                    solver_status.capture_baseline(board, window)
                    eliminate_options(solver_status, board, to_eliminate, window)
                    if window:
                        window.options_visible = window.options_visible.union(set(corners))
                    kwargs["solver_tool"] = "unique_rectangles"
                    kwargs["rectangle"] = rect
                    kwargs["eliminate"] = to_eliminate
                    kwargs["subset"] = subset
                    print('\tUniueness Test 1')
                    return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    pairs = defaultdict(list)
    for i in range(81):
        if len(board[i]) == 2:
            pairs[board[i]].append((CELL_ROW[i], CELL_COL[i], CELL_BOX[i]))

    for pair, positions in pairs.items():
        if len(positions) > 2:
            rows = list(set(pos[0] for pos in positions))
            cols = list(set(pos[1] for pos in positions))
            blocks = set(pos[2] for pos in positions)
            if len(rows) == 2 and len(cols) == 2 and len(blocks) == 2:
                row_1 = CELLS_IN_ROW[rows[0]]
                row_2 = CELLS_IN_ROW[rows[1]]
                rect = sorted([row_1[cols[0]], row_1[cols[1]], row_2[cols[0]], row_2[cols[1]]])
                for val in pair:
                    if not all(val in board[corner] for corner in rect):
                        break
                else:
                    if _reduce_rectangle(pair, rect):
                        return kwargs
    return {}
コード例 #4
0
ファイル: graph_utils.py プロジェクト: marrydzy/Learn_Sudoku
def pencil_mark_btn_clicked(window, _, board, *args, **kwargs):
    """ action on pressing 'Pencil mark' button """
    if window.buttons[pygame.K_p].is_active(
    ) and not window.buttons[pygame.K_p].is_pressed():
        window.buttons[pygame.K_p].set_pressed(True)
        window.buttons[pygame.K_p].draw(window.screen)
        window.buttons[pygame.K_c].set_pressed(False)
        window.buttons[pygame.K_c].draw(window.screen)
        set_remaining_candidates(board, solver_status)
コード例 #5
0
def remote_pairs(solver_status, board, window):
    """ TODO """

    def _find_chain(pair):
        chain_cells = set(pairs_positions[pair])
        ends = [cell_id for cell_id in chain_cells if len(set(ALL_NBRS[cell_id]).intersection(chain_cells)) == 1]
        inner_nodes = [cell_id for cell_id in chain_cells if len(set(ALL_NBRS[cell_id]).intersection(chain_cells)) == 2]
        if len(ends) == 2 and ends[0] not in ALL_NBRS[ends[1]] and len(ends) + len(inner_nodes) == len(chain_cells):
            # print('\n')
            chain = [ends[0]]
            while inner_nodes:
                for node in inner_nodes:
                    if chain[-1] in set(ALL_NBRS[node]):
                        chain.append(node)
                        break
                if chain[-1] in inner_nodes:
                    inner_nodes.remove(chain[-1])
                elif len(chain) % 2 == 0:
                    return []
                else:
                    break
            chain.append(ends[1])
            return chain
        else:
            return []

    set_remaining_candidates(board, solver_status)
    pairs_positions = defaultdict(list)
    for cell in range(81):
        if len(board[cell]) == 2:
            pairs_positions[board[cell]].append(cell)
    pair_chains = [pair for pair in pairs_positions if
                   len(pairs_positions[pair]) > 3 and len(pairs_positions[pair]) % 2 == 0]
    for pair in pair_chains:
        chain = _find_chain(pair)
        if chain:
            impacted_cells = set(ALL_NBRS[chain[0]]).intersection(set(ALL_NBRS[chain[-1]]))
            to_eliminate = [(value, cell) for value in pair for cell in impacted_cells
                            if value in board[cell] and len(board[cell]) > 1]
            if to_eliminate:
                solver_status.capture_baseline(board, window)
                if window:
                    window.options_visible = window.options_visible.union(impacted_cells)
                eliminate_options(solver_status, board, to_eliminate, window)
                kwargs = {
                    "solver_tool": "remote_pairs",
                    "chain": chain,
                    "eliminate": to_eliminate,
                    "impacted_cells": impacted_cells,
                }
                return kwargs

    return {}
コード例 #6
0
def test_6(solver_status, board, window):
    """ Suppose exactly two cells in the rectangle contain extra candidates,
    and they are located diagonally across each other in the rectangle.
    Suppose the common candidates are U and V, and none of the other cells
    in the two rows and two columns containing the rectangle contain U.
    Then U can be eliminated from these two cells.
    This is also called a Type 6 Unique Rectangle.
    Rating: 100
    """

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    bi_values = _get_bi_values_dictionary(board, range(81))
    bi_values = {key: value for key, value in bi_values.items() if len(value) > 1}
    for bi_value in bi_values:
        for pair in combinations(bi_values[bi_value], 2):
            if pair[0][0] != pair[1][0] and pair[0][1] != pair[1][1] and pair[0][2] != pair[1][2]:
                node_b = pair[0][0] * 9 + pair[1][1]
                node_d = pair[1][0] * 9 + pair[0][1]
                if len(set(board[node_b]).intersection(bi_value)) == 2 and \
                        len(set(board[node_d]).intersection(bi_value)) == 2:
                    node_a = pair[0][0] * 9 + pair[0][1]
                    node_c = pair[1][0] * 9 + pair[1][1]
                    nodes = [node_a, node_b, node_c, node_d]
                    boxes = {CELL_BOX[node] for node in nodes}
                    if len(boxes) == 2:
                        other_cells = set(CELLS_IN_ROW[pair[0][0]]).union(CELLS_IN_ROW[pair[1][0]]).union(
                            CELLS_IN_COL[pair[0][1]]).union(CELLS_IN_COL[pair[1][1]])
                        other_candidates = ''.join(board[cell] for cell in other_cells)
                        unique_value = None
                        if other_candidates.count(bi_value[0]) == 4:
                            unique_value = bi_value[0]
                        elif other_candidates.count(bi_value[1]) == 4:
                            unique_value = bi_value[1]
                        if unique_value:
                            other_candidates = set(board[node_b]).union(board[node_d]).difference(bi_value)
                            c_chain = _get_c_chain(nodes, bi_value, other_candidates)
                            to_eliminate = {(unique_value, node_b), (unique_value, node_d)}
                            solver_status.capture_baseline(board, window)
                            eliminate_options(solver_status, board, to_eliminate, window)
                            if window:
                                window.options_visible = window.options_visible.union(other_cells)
                            kwargs["solver_tool"] = "uniqueness_test_6"
                            kwargs["c_chain"] = c_chain
                            kwargs["eliminate"] = to_eliminate
                            test_6.clues += len(solver_status.naked_singles)
                            test_6.options_removed += len(to_eliminate)
                            return kwargs
    return None
コード例 #7
0
def test_2(solver_status, board, window):
    """ Suppose both ceiling cells in the rectangle have exactly one extra candidate X.
    Then X can be eliminated from the cells seen by both of these cells.
    Rating: 100
    """

    def _check_rectangles(by_row):
        cells_by_x = CELLS_IN_ROW if by_row else CELLS_IN_COL
        cells_by_y = CELLS_IN_COL if by_row else CELLS_IN_ROW
        for floor_id_x in range(9):
            bi_values = _get_bi_values_dictionary(board, cells_by_x[floor_id_x], by_row)
            for bi_value, coordinates in bi_values.items():
                if len(coordinates) == 2:
                    floor_a = coordinates.pop()
                    floor_b = coordinates.pop()
                    x_ids = _get_xyz(1, board, bi_value, cells_by_y[floor_a[1]], cells_by_y[floor_b[1]])
                    for x_id in x_ids:
                        ceiling_a = x_id * 9 + floor_a[1] if by_row else floor_a[1] * 9 + x_id
                        ceiling_b = x_id * 9 + floor_b[1] if by_row else floor_b[1] * 9 + x_id
                        boxes = {floor_a[2], floor_b[2], CELL_BOX[ceiling_a], CELL_BOX[ceiling_b]}
                        if board[ceiling_a] == board[ceiling_b] and len(boxes) == 2:
                            z_candidate = board[ceiling_a].replace(bi_value[0], '').replace(bi_value[1], '')
                            to_eliminate = set()
                            for cell in set(ALL_NBRS[ceiling_a]).intersection(ALL_NBRS[ceiling_b]):
                                if z_candidate in board[cell]:
                                    to_eliminate.add((z_candidate, cell))
                            if to_eliminate:
                                rows = (floor_a[0], x_id) if by_row else (floor_a[1], floor_b[1])
                                columns = (floor_a[1], floor_b[1]) if by_row else (floor_a[0], x_id)
                                rectangle = _get_rectangle(sorted(rows), sorted(columns))
                                c_chain = _get_c_chain(rectangle, bi_value, {z_candidate, })
                                solver_status.capture_baseline(board, window)
                                eliminate_options(solver_status, board, to_eliminate, window)
                                if window:
                                    window.options_visible = window.options_visible.union(c_chain.keys())
                                kwargs["solver_tool"] = "uniqueness_test_2"
                                kwargs["c_chain"] = c_chain
                                kwargs["impacted_cells"] = {cell for _, cell in to_eliminate}
                                kwargs["eliminate"] = to_eliminate
                                test_2.clues += len(solver_status.naked_singles)
                                test_2.options_removed += len(to_eliminate)
                                return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    if _check_rectangles(True) or _check_rectangles(False):
        return kwargs
    return None
コード例 #8
0
ファイル: singles.py プロジェクト: marrydzy/Learn_Sudoku
def naked_single(solver_status, board, window):
    """ A naked single is the last remaining candidate in a cell.
    The Naked Single is categorized as a solving technique but you can hardly
    call it a technique. The only 'real work' is done when candidates in unsolved
    cells are not calculated yet: then the algorithm checks all possible candidates
    for each such cell to find the one with only one candidate.
    Otherwise, a naked single is that what remains after you have applied
    your solving techniques, by eliminating other candidates.
    Alternative terms are Forced Digit and Sole Candidate.
    Rating: 4
    """
    kwargs = {}
    if not (window or solver_status.pencilmarks):
        set_remaining_candidates(board, solver_status)
        naked_single.clues += len(solver_status.naked_singles)

    if solver_status.pencilmarks:
        if not solver_status.naked_singles:
            return None
        else:
            naked_singles_on_entry = len(solver_status.naked_singles)
            the_single = list(solver_status.naked_singles)[0]
            eliminate, impacted_cells = place_digit(the_single,
                                                    board[the_single], board,
                                                    solver_status, window)
            naked_single.options_removed += len(eliminate)
            naked_single.clues += len(
                solver_status.naked_singles) - naked_singles_on_entry + 1
            kwargs["solver_tool"] = naked_single.__name__
            if window:
                kwargs["cell_solved"] = the_single
                kwargs["eliminate"] = eliminate
                kwargs["house"] = get_impacted_houses(
                    the_single, base_house=None, to_eliminate=impacted_cells)
            return kwargs
    else:
        for cell in range(81):
            if board[cell] == ".":
                cell_opts = get_cell_candidates(cell, board, solver_status)
                if len(cell_opts) == 1:
                    solver_status.capture_baseline(board, window)
                    board[cell] = cell_opts.pop()
                    kwargs["solver_tool"] = naked_single.__name__
                    kwargs["cell_solved"] = cell
                    solver_status.cells_solved.add(cell)
                    naked_single.clues += 1
                    return kwargs
        return kwargs
コード例 #9
0
def _naked_subset(solver_status, board, window, subset_size):
    """ Generic technique of finding naked subsets
    A Naked Subset is formed by N cells in a house with candidates for exactly N digits.
    N is the size of the subset, which must lie between 2 and the number of unsolved cells
    in the house minus 2.
    Since every Naked Subset is complemented by a Hidden Subset, the smallest of both sets
    will be no larger than 4 in a standard sized Sudoku.
    """
    set_remaining_candidates(board, solver_status)
    subset_strategies = {
        2: (naked_pair, 60),
        3: (naked_triplet, 80),
        4: (naked_quad, 120),
    }
    for house, subset_dict in get_subsets(board, subset_size):
        subset_cells = {
            cell
            for cells in subset_dict.values() for cell in cells
        }
        impacted_cells = get_impacted_cells(board, subset_cells)
        to_eliminate = {
            (candidate, cell)
            for cell in impacted_cells
            for candidate in set(board[cell]).intersection(subset_dict)
        }
        if to_eliminate:
            kwargs = {}
            if window:
                solver_status.capture_baseline(board, window)
            eliminate_options(solver_status, board, to_eliminate, window)
            subset_strategies[subset_size][0].clues += len(
                solver_status.naked_singles)
            subset_strategies[subset_size][0].options_removed += len(
                to_eliminate)
            kwargs["solver_tool"] = subset_strategies[subset_size][0].__name__
            if window:
                window.options_visible = window.options_visible.union(
                    subset_cells).union(impacted_cells)
                kwargs["chain_a"] = _get_chain(subset_cells, subset_dict)
                kwargs["eliminate"] = to_eliminate
                kwargs["house"] = impacted_cells.union(house)
            return kwargs
    return None
コード例 #10
0
def test_5(solver_status, board, window):
    """     Suppose exactly two cells in the rectangle have exactly one extra candidate X,
    and both cells are located diagonally across each other in the rectangle.
    Then X can be eliminated from the cells seen by both of these cells.
    This would be called a Type 5 Unique Rectangle.
    Note that in this case the rectangle does not have a floor or ceiling.
    Rating: 100
    """

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    bi_values = _get_bi_values_dictionary(board, range(81))
    bi_values = {key: value for key, value in bi_values.items() if len(value) > 1}
    for bi_value in bi_values:
        for pair in combinations(bi_values[bi_value], 2):
            if pair[0][0] != pair[1][0] and pair[0][1] != pair[1][1] and pair[0][2] != pair[1][2]:
                node_b = pair[0][0] * 9 + pair[1][1]
                node_d = pair[1][0] * 9 + pair[0][1]
                if board[node_b] == board[node_d] and len(set(board[node_b]).difference(bi_value)) == 1:
                    node_a = pair[0][0] * 9 + pair[0][1]
                    node_c = pair[1][0] * 9 + pair[1][1]
                    nodes = [node_a, node_b, node_c, node_d]
                    boxes = {CELL_BOX[node] for node in nodes}
                    if len(boxes) == 2:
                        other_candidates = set(board[node_b]).difference(bi_value)
                        c_chain = _get_c_chain(nodes, bi_value, other_candidates)
                        z_value = other_candidates.pop()
                        to_eliminate = {(z_value, cell) for cell in set(ALL_NBRS[node_b]).intersection(ALL_NBRS[node_d])
                                        if z_value in board[cell]}
                        if to_eliminate:
                            solver_status.capture_baseline(board, window)
                            eliminate_options(solver_status, board, to_eliminate, window)
                            if window:
                                window.options_visible = window.options_visible.union(c_chain.keys())
                            kwargs["solver_tool"] = "uniqueness_test_4"
                            kwargs["c_chain"] = c_chain
                            kwargs["impacted_cells"] = {cell for _, cell in to_eliminate}
                            kwargs["eliminate"] = to_eliminate
                            test_5.clues += len(solver_status.naked_singles)
                            test_5.options_removed += len(to_eliminate)
                            return kwargs
    return None
コード例 #11
0
def skyscraper(solver_status, board, window):
    Rating: 130
    """ TODO """

    def _find_skyscraper(by_row, option):
        cells = CELLS_IN_ROW if by_row else CELLS_IN_COL
        for row_1 in range(8):
            cols_1 = set(col for col in range(9) if option in board[cells[row_1][col]]
                         and len(board[cells[row_1][col]]) > 1)
            if len(cols_1) == 2:
                for row_2 in range(row_1+1, 9):
                    cols_2 = set(col for col in range(9) if option in board[cells[row_2][col]]
                                 and len(board[cells[row_2][col]]) > 1)
                    if len(cols_2) == 2 and len(cols_1.union(cols_2)) == 3:
                        different_cols = cols_1.symmetric_difference(cols_2)
                        cl_1_list = sorted(list(cols_1))

                        cl_2_list = sorted(list(cols_2))
                        corners = list()
                        corners.append((row_1, cl_1_list[0]) if cl_1_list[0] not in different_cols else
                                       (row_1, cl_1_list[1]))
                        corners.append((row_1, cl_1_list[0]) if cl_1_list[0] in different_cols else
                                       (row_1, cl_1_list[1]))
                        corners.append((row_2, cl_2_list[0]) if cl_2_list[0] in different_cols else
                                       (row_2, cl_2_list[1]))
                        corners.append((row_2, cl_2_list[0]) if cl_2_list[0] not in different_cols else
                                       (row_2, cl_2_list[1]))
                        if by_row:
                            corners_idx = [corners[i][0] * 9 + corners[i][1] for i in range(4)]
                        else:
                            corners_idx = [corners[i][1] * 9 + corners[i][0] for i in range(4)]
                        impacted_cells = set(ALL_NBRS[corners_idx[1]]).intersection(ALL_NBRS[corners_idx[2]])
                        for corner in corners_idx:
                            impacted_cells.discard(corner)
                        clues = [cell for cell in impacted_cells if is_digit(cell, board, solver_status)]
                        for clue_id in clues:
                            impacted_cells.discard(clue_id)
                        corners_idx.insert(0, option)
                        to_eliminate = [(option, cell) for cell in impacted_cells if option in board[cell]]  # TODO - check if not set
                        if to_eliminate:
                            solver_status.capture_baseline(board, window)
                            house = set(cells[row_1]).union(set(cells[row_2]))
                            if window:
                                window.options_visible = window.options_visible.union(house).union(impacted_cells)
                            eliminate_options(solver_status, board, to_eliminate, window)
                            kwargs["solver_tool"] = "skyscraper"
                            kwargs["singles"] = solver_status.naked_singles
                            kwargs["skyscraper"] = corners_idx
                            kwargs["subset"] = [option]
                            kwargs["eliminate"] = to_eliminate
                            kwargs["house"] = house
                            kwargs["impacted_cells"] = impacted_cells
                            skyscraper.clues += len(solver_status.naked_singles)
                            skyscraper.options_removed += len(to_eliminate)

                            # print(f'\t{kwargs["solver_tool"]}')

                            return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    for opt in SUDOKU_VALUES_LIST:
        if _find_skyscraper(True, opt):
            return kwargs
        if _find_skyscraper(False, opt):
            return kwargs
    return kwargs
コード例 #12
0
def test_3(solver_status, board, window):
    """ Suppose both ceiling cells have extra candidates.
    By treating these two cells as one node, find k - 1 other cells (as nodes)
    in the same house as these two cells so that the union of the candidates
    for these k cells has exactly k unique digits. Then the Naked Subset rule
    can be applied to eliminate these k digits from the other cells in the house.
    The algorithm is implemented for k = 2, 3 and 4
    Rating: 100
    """

    def find_naked_subset(subset_size, ceiling_a, ceiling_b, bi_value, subset_candidates, by_row):
        search_area = {cell for cell in set(ALL_NBRS[ceiling_a]).intersection(ALL_NBRS[ceiling_b])
                       if len(board[cell]) > 1}
        possible_subset_nodes = {cell for cell in search_area if len(board[cell]) <= subset_size 
                                 and set(board[cell]).intersection(subset_candidates) 
                                 and not set(board[cell]).intersection(bi_value)}
        houses = [possible_subset_nodes.intersection(CELLS_IN_ROW[CELL_ROW[ceiling_a]] if by_row
                                                     else CELLS_IN_COL[CELL_COL[ceiling_a]])]
        if CELL_BOX[ceiling_a] == CELL_BOX[ceiling_b]:
            houses.append(possible_subset_nodes.intersection(CELLS_IN_BOX[CELL_BOX[ceiling_a]]))
        for house in houses:
            for subset_nodes in combinations(house, subset_size-1):
                naked_subset = set("".join(board[cell] for cell in subset_nodes)).union(subset_candidates)
                if len(naked_subset) == subset_size:
                    impacted_cells = search_area
                    for cell in subset_nodes:
                        impacted_cells = impacted_cells.intersection(ALL_NBRS[cell])
                    for cell in impacted_cells:
                        for candidate in naked_subset.intersection(board[cell]):
                            to_eliminate.add((candidate, cell))
                    if to_eliminate:
                        return naked_subset, subset_nodes
        return None, None

    def _check_rectangles(by_row):
        cells_by_x = CELLS_IN_ROW if by_row else CELLS_IN_COL
        cells_by_y = CELLS_IN_COL if by_row else CELLS_IN_ROW

        for floor_id_x in range(9):
            bi_values = _get_bi_values_dictionary(board, cells_by_x[floor_id_x], by_row)
            for bi_value, coordinates in bi_values.items():
                if len(coordinates) == 2:
                    floor_a = coordinates.pop()
                    floor_b = coordinates.pop()
                    for n in (2, 3, 4):
                        x_ids = _get_xyz(n, board, bi_value, cells_by_y[floor_a[1]], cells_by_y[floor_b[1]])
                        for x_id in x_ids:
                            ceiling_a = x_id * 9 + floor_a[1] if by_row else floor_a[1] * 9 + x_id
                            ceiling_b = x_id * 9 + floor_b[1] if by_row else floor_b[1] * 9 + x_id
                            boxes = {floor_a[2], floor_b[2], CELL_BOX[ceiling_a], CELL_BOX[ceiling_b]}
                            if len(boxes) == 2 and board[ceiling_a] != board[ceiling_b]:
                                z_a = board[ceiling_a].replace(bi_value[0], '').replace(bi_value[1], '')
                                z_b = board[ceiling_b].replace(bi_value[0], '').replace(bi_value[1], '')
                                z_ab = set(z_a).union(z_b)
                                naked_subset, subset_nodes = \
                                    find_naked_subset(n, ceiling_a, ceiling_b, bi_value, z_ab, by_row)
                                if to_eliminate:
                                    rows = (floor_a[0], x_id) if by_row else (floor_a[1], floor_b[1])
                                    columns = (floor_a[1], floor_b[1]) if by_row else (floor_a[0], x_id)
                                    rectangle = _get_rectangle(sorted(rows), sorted(columns))
                                    c_chain = _get_c_chain(rectangle, bi_value, naked_subset, subset_nodes)
                                    solver_status.capture_baseline(board, window)
                                    eliminate_options(solver_status, board, to_eliminate, window)
                                    if window:
                                        window.options_visible = window.options_visible.union(c_chain.keys())
                                    kwargs["solver_tool"] = "uniqueness_test_3"
                                    kwargs["c_chain"] = c_chain
                                    kwargs["impacted_cells"] = {cell for _, cell in to_eliminate}
                                    kwargs["eliminate"] = to_eliminate
                                    test_3.clues += len(solver_status.naked_singles)
                                    test_3.options_removed += len(to_eliminate)
                                    return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    to_eliminate = set()
    if _check_rectangles(True) or _check_rectangles(False):
        return kwargs
    return None
コード例 #13
0
def xyz_wing(solver_status, board, window):
    """ Remove candidates (options) using XYZ Wing technique:
    For explanation of the technique see e.g.:
    - https://www.sudoku9981.com/sudoku-solving/xyz-wing.php
    Rating: 200-180
    """
    def _get_c_chain(root, wing_x, wing_y):
        z_value = set(board[wing_x]).intersection(set(
            board[wing_y])).intersection(set(board[root])).pop()
        x_value = set(board[wing_x]).difference({z_value}).pop()
        y_value = set(board[wing_y]).difference({z_value}).pop()
        return {
            root: {(x_value, 'lime'), (y_value, 'yellow'), (z_value, 'cyan')},
            wing_x: {(x_value, 'yellow'), (z_value, 'cyan')},
            wing_y: {(y_value, 'lime'), (z_value, 'cyan')}
        }

    def _find_xyz_wing(cell_id):
        xyz = set(board[cell_id])
        bi_values = [
            indx for indx in ALL_NBRS[cell_id] if len(board[indx]) == 2
        ]
        for pair in combinations(bi_values, 2):
            xz = set(board[pair[0]])
            yz = set(board[pair[1]])
            if xz != yz and len(xyz.union(xz).union(yz)) == 3:
                z_value = xyz.intersection(xz).intersection(yz).pop()
                impacted_cells = set(ALL_NBRS[cell_id]).intersection(
                    set(ALL_NBRS[pair[0]])).intersection(set(
                        ALL_NBRS[pair[1]]))
                to_eliminate = [
                    (z_value, a_cell) for a_cell in impacted_cells
                    if z_value in board[a_cell] and len(board[cell]) > 1
                ]
                if to_eliminate:
                    solver_status.capture_baseline(board, window)
                    if window:
                        window.options_visible = window.options_visible.union(
                            impacted_cells).union({cell_id, pair[0], pair[1]})
                    eliminate_options(solver_status, board, to_eliminate,
                                      window)
                    kwargs["solver_tool"] = "xyz_wing"
                    kwargs["c_chain"] = _get_c_chain(cell_id, pair[0], pair[1])
                    kwargs["edges"] = [(cell_id, pair[0]), (cell_id, pair[1])]
                    kwargs["eliminate"] = to_eliminate
                    kwargs["impacted_cells"] = {
                        a_cell
                        for _, a_cell in to_eliminate
                    }
                    xyz_wing.options_removed += len(to_eliminate)
                    xyz_wing.clues += len(solver_status.naked_singles)
                    # print('\tXYZ-Wing')
                    return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    for cell in range(81):
        if len(board[cell]) == 3 and _find_xyz_wing(cell):
            return kwargs
    return kwargs
コード例 #14
0
def _hidden_subset(solver_status, board, window, subset_size):
    """ Generic technique of finding hidden subsets
    A Hidden Subset is formed when N digits have only candidates in N cells in a house.
    A Hidden Subset is always complemented by a Naked Subset. Because Hidden Subsets are
    sometimes hard to find, players often prefer to look for Naked Subsets only,
    even when their size is greater.
    In a standard Sudoku, the maximum number of empty cells in a house is 9.
    There is no need to look for subsets larger than 4 cells, because the complementary
    subset will always be size 4 or smaller.
    """

    set_remaining_candidates(board, solver_status)
    subset_strategies = {
        2: (hidden_pair, 70),
        3: (hidden_triplet, 100),
        4: (hidden_quad, 150),
    }

    for cells in (CELLS_IN_ROW, CELLS_IN_COL, CELLS_IN_BOX):
        for house in cells:
            unsolved = {cell for cell in house if len(board[cell]) > 1}
            if len(unsolved) <= subset_size:
                continue
            candidates = set("".join(board[cell] for cell in unsolved))
            if len(unsolved) != len(candidates):
                raise DeadEndException

            candidates_positions = {
                candidate:
                {cell
                 for cell in unsolved if candidate in board[cell]}
                for candidate in candidates
            }
            for subset in itertools.combinations(candidates, subset_size):
                subset_nodes = {
                    cell
                    for candidate in subset
                    for cell in candidates_positions[candidate]
                }
                if len(subset_nodes) == subset_size:
                    to_eliminate = {(candidate, cell)
                                    for cell in subset_nodes
                                    for candidate in board[cell]
                                    if candidate not in subset}
                    if to_eliminate:
                        kwargs = {}
                        if window:
                            solver_status.capture_baseline(board, window)
                        eliminate_options(solver_status, board, to_eliminate,
                                          window)
                        subset_strategies[subset_size][0].clues += len(
                            solver_status.naked_singles)
                        subset_strategies[subset_size][
                            0].options_removed += len(to_eliminate)
                        kwargs["solver_tool"] = subset_strategies[subset_size][
                            0].__name__
                        if window:
                            window.options_visible = window.options_visible.union(
                                unsolved)
                            kwargs["chain_a"] = _get_chain(
                                subset_nodes, candidates_positions)
                            kwargs["eliminate"] = to_eliminate
                            kwargs["house"] = house
                        return kwargs
    return None
コード例 #15
0
def w_wing(solver_status, board, window):
    """Remove candidates (options) using W-Wing technique
    http://hodoku.sourceforge.net/en/tech_wings.php#w
    Rating: 150
    """

    bi_value_cells = get_bi_value_cells(board)
    strong_links = get_strong_links(board)
    set_remaining_candidates(board, solver_status)
    for pair in bi_value_cells:
        if len(bi_value_cells[pair]) > 1:
            va, vb = pair
            w_constraint = None
            w_base = None
            for positions in combinations(bi_value_cells[pair], 2):
                for strong_link in strong_links[va]:
                    if not set(positions).intersection(strong_link):
                        sl_a = set(strong_link).intersection(
                            ALL_NBRS[positions[0]])
                        sl_b = set(strong_link).intersection(
                            ALL_NBRS[positions[1]])
                        if sl_a and sl_b and sl_a != sl_b:
                            w_base = strong_link
                            w_constraint = va
                            break
                if w_constraint:
                    break
                for strong_link in strong_links[vb]:
                    if not set(positions).intersection(strong_link):
                        sl_a = set(strong_link).intersection(
                            ALL_NBRS[positions[0]])
                        sl_b = set(strong_link).intersection(
                            ALL_NBRS[positions[1]])
                        if sl_a and sl_b and sl_a != sl_b:
                            w_base = strong_link
                            w_constraint = vb
                            break
                if w_constraint:
                    break
            else:
                continue
            assert w_constraint
            other_value = vb if w_constraint == va else va
            impacted_cells = set(ALL_NBRS[positions[0]]).intersection(
                ALL_NBRS[positions[1]])
            impacted_cells = {
                cell
                for cell in impacted_cells if len(board[cell]) > 1
            }
            to_eliminate = [(other_value, cell) for cell in impacted_cells
                            if other_value in board[cell]]
            if to_eliminate:
                kwargs = {}
                solver_status.capture_baseline(board, window)
                if window:
                    window.options_visible = window.options_visible.union(
                        impacted_cells).union({positions[0], positions[1]
                                               }).union(get_pair_house(w_base))
                eliminate_options(solver_status, board, to_eliminate, window)
                kwargs["solver_tool"] = "w_wing"
                kwargs["chain_a"] = _get_chain(board, positions[:2],
                                               other_value)
                kwargs["chain_d"] = _get_chain(board, w_base, other_value,
                                               w_constraint)
                kwargs["eliminate"] = to_eliminate
                kwargs["impacted_cells"] = {
                    a_cell
                    for _, a_cell in to_eliminate
                }
                w_wing.options_removed += len(to_eliminate)
                w_wing.clues += len(solver_status.naked_singles)
                # print('\tw_wing')
                return kwargs
    return {}
コード例 #16
0
def _fish(solver_status, board, window, n):
    """ Generic 'fish' technique """

    fish_strategies = {
        2: (x_wing, 100),
        3: (swordfish, 140),
        4: (jellyfish, 470),
        5: (squirmbag, 470),
    }

    def _get_chain(candidate, cells, primary_ids):
        nodes = {
            cell
            for idx in primary_ids for cell in cells[idx]
            if candidate in board[cell]
        }
        return {node: {(candidate, 'cyan')} for node in nodes}

    def _find_fish(by_row):
        for value in SUDOKU_VALUES_LIST:
            lines = get_2_upto_n_candidates(board, value, n, by_row)
            if len(lines) >= n:
                for x_ids in combinations((id_x for id_x in lines), n):
                    y_ids = set(id_y for id_x in x_ids for id_y in lines[id_x])
                    if len(y_ids) == n:
                        cells = CELLS_IN_COL if by_row else CELLS_IN_ROW
                        impacted_cells = {
                            cell
                            for id_y in y_ids for cell in cells[id_y]
                        }
                        cells = CELLS_IN_ROW if by_row else CELLS_IN_COL
                        houses = {
                            cell
                            for id_x in x_ids for cell in cells[id_x]
                        }
                        impacted_cells = impacted_cells.difference(houses)
                        to_eliminate = {(value, cell)
                                        for cell in impacted_cells
                                        if value in board[cell]}
                        if to_eliminate:
                            if window:
                                solver_status.capture_baseline(board, window)
                            kwargs["solver_tool"] = fish_strategies[n][
                                0].__name__
                            eliminate_options(solver_status, board,
                                              to_eliminate, window)
                            fish_strategies[n][0].clues += len(
                                solver_status.naked_singles)
                            fish_strategies[n][0].options_removed += len(
                                to_eliminate)
                            if window:
                                impacted = _get_impacted_lines(
                                    to_eliminate, by_row, board)
                                window.options_visible = window.options_visible.union(
                                    houses).union(impacted_cells)
                                kwargs["chain_a"] = _get_chain(
                                    value, cells, x_ids)
                                kwargs["eliminate"] = to_eliminate
                                kwargs["house"] = impacted.union(houses)
                            return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    if _find_fish(True) or _find_fish(False):
        return kwargs
    return None
コード例 #17
0
def franken_x_wing(solver_status, board, window):
    """ TODO """
    by_row_boxes = {
        0: (3, 6),
        1: (4, 7),
        2: (5, 8),
        3: (0, 6),
        4: (1, 7),
        5: (2, 8),
        6: (0, 3),
        7: (1, 4),
        8: (2, 5),
    }
    by_col_boxes = {
        0: (1, 2),
        1: (0, 2),
        2: (0, 1),
        3: (4, 5),
        4: (3, 5),
        5: (3, 4),
        6: (7, 8),
        7: (6, 8),
        8: (6, 7),
    }

    def _find_franken_x_wing(by_row, option):
        cells = CELLS_IN_ROW if by_row else CELLS_IN_COL
        for row in range(9):
            cols_1 = [
                col for col in range(9) if option in board[cells[row][col]]
                and len(board[cells[row][col]]) > 1
            ]
            if len(cols_1) == 2:
                corner_1 = cells[row][cols_1[0]]
                corner_2 = cells[row][cols_1[1]]
                if CELL_BOX[corner_1] == CELL_BOX[corner_2]:
                    other_boxes = by_row_boxes[
                        CELL_BOX[corner_1]] if by_row else by_col_boxes[
                            CELL_BOX[corner_1]]
                    for box in other_boxes:
                        if by_row:
                            cols_2 = set(CELL_COL[cell]
                                         for cell in CELLS_IN_BOX[box]
                                         if option in board[cell]
                                         and len(board[cell]) > 1)
                        else:
                            cols_2 = set(CELL_ROW[cell]
                                         for cell in CELLS_IN_BOX[box]
                                         if option in board[cell]
                                         and len(board[cell]) > 1)
                        if set(cols_1) == cols_2:
                            if by_row:
                                other_cells = set(
                                    CELLS_IN_COL[cols_1[0]]).union(
                                        set(CELLS_IN_COL[cols_1[1]]))
                            else:
                                other_cells = set(
                                    CELLS_IN_ROW[cols_1[0]]).union(
                                        set(CELLS_IN_ROW[cols_1[1]]))
                            other_cells = other_cells.intersection(
                                set(CELLS_IN_BOX[CELL_BOX[corner_1]]))
                            other_cells.discard(corner_1)
                            other_cells.discard(corner_2)
                            house = set(cells[row]).union(
                                set(CELLS_IN_BOX[box]))
                            corners = [option, corner_1, corner_2]
                            corners.extend(cell for cell in CELLS_IN_BOX[box]
                                           if option in board[cell])
                            to_eliminate = [(option, cell)
                                            for cell in other_cells
                                            if option in board[cell]]
                            if to_eliminate:
                                solver_status.capture_baseline(board, window)
                                if window:
                                    window.options_visible = window.options_visible.union(
                                        house).union(other_cells)
                                eliminate_options(solver_status, board,
                                                  to_eliminate, window)
                                kwargs["solver_tool"] = "franken_x_wing"
                                kwargs["singles"] = solver_status.naked_singles
                                kwargs["finned_x_wing"] = corners
                                kwargs["subset"] = [option]
                                kwargs["eliminate"] = to_eliminate
                                kwargs["house"] = house
                                kwargs["impacted_cells"] = other_cells
                                return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    for opt in SUDOKU_VALUES_LIST:
        if _find_franken_x_wing(True, opt):
            return kwargs
        if _find_franken_x_wing(False, opt):
            return kwargs
    return kwargs
コード例 #18
0
def locked_candidates(solver_status, board, window):
    """ A solving technique that uses the intersections between lines and boxes.
    Aliases include: Intersection Removal, Line-Box Interaction.
    The terms Pointing and Claiming/Box-Line Reduction are often used to distinguish the 2 types.
    This is a basic solving technique. When all candidates for a digit in a house
    are located inside the intersection with another house, we can eliminate the remaining candidates
    from the second house outside the intersection.
    """
    def _paint_locked_candidates(house, locked_candidate):
        return {
            cell: {
                (locked_candidate, "cyan"),
            }
            for cell in house if locked_candidate in board[cell]
        }

    def _type_1():
        """ Type 1 (Pointing)
        All the candidates for digit X in a box are confined to a single line (row or column).
        The surplus candidates are eliminated from the part of the line that does not intersect with this box.
        Rating: 50
        """
        for house in CELLS_IN_BOX:
            candidates = SUDOKU_VALUES_SET - {
                board[cell]
                for cell in house if len(board[cell]) == 1
            }
            unsolved = {cell for cell in house if len(board[cell]) > 1}
            for possibility in candidates:
                in_rows = set(CELL_ROW[cell] for cell in unsolved
                              if possibility in board[cell])
                in_cols = set(CELL_COL[cell] for cell in unsolved
                              if possibility in board[cell])
                impacted_cells = None
                if len(in_rows) == 1:
                    impacted_cells = set(
                        CELLS_IN_ROW[in_rows.pop()]).difference(house)
                elif len(in_cols) == 1:
                    impacted_cells = set(
                        CELLS_IN_COL[in_cols.pop()]).difference(house)
                if impacted_cells:
                    to_eliminate = {(possibility, cell)
                                    for cell in impacted_cells
                                    if possibility in board[cell]}
                    if to_eliminate:
                        if window:
                            solver_status.capture_baseline(board, window)
                        eliminate_options(solver_status, board, to_eliminate,
                                          window)
                        locked_candidates.clues += len(
                            solver_status.naked_singles)
                        locked_candidates.options_removed += len(to_eliminate)
                        kwargs["solver_tool"] = "locked_candidates_type_1"
                        if window:
                            window.options_visible = window.options_visible.union(
                                house).union(impacted_cells)
                            kwargs["house"] = impacted_cells.union(house)
                            kwargs["eliminate"] = to_eliminate
                            kwargs["chain_a"] = _paint_locked_candidates(
                                house, possibility)
                        return True
        return False

    def _type_2():
        """ Type 2 (Claiming or Box-Line Reduction)
        All the candidates for digit X in a line are confined to a single box.
        The surplus candidates are eliminated from the part of the box that does not intersect with this line.
        Rating: 50 - 60
        """
        for cells in (CELLS_IN_ROW, CELLS_IN_COL):
            for house in cells:
                candidates = SUDOKU_VALUES_SET - {
                    board[cell]
                    for cell in house if len(board[cell]) == 1
                }
                unsolved = {cell for cell in house if len(board[cell]) > 1}
                for possibility in candidates:
                    boxes = {
                        CELL_BOX[cell]
                        for cell in unsolved if possibility in board[cell]
                    }
                    if len(boxes) == 1:
                        impacted_cells = set(
                            CELLS_IN_BOX[boxes.pop()]).difference(house)
                        to_eliminate = {(possibility, cell)
                                        for cell in impacted_cells
                                        if possibility in board[cell]}
                        if to_eliminate:
                            if window:
                                solver_status.capture_baseline(board, window)
                            eliminate_options(solver_status, board,
                                              to_eliminate, window)
                            locked_candidates.clues += len(
                                solver_status.naked_singles)
                            locked_candidates.options_removed += len(
                                to_eliminate)
                            kwargs["solver_tool"] = "locked_candidates_type_2"
                            if window:
                                window.options_visible = window.options_visible.union(
                                    house).union(impacted_cells)
                                kwargs["house"] = impacted_cells.union(house)
                                kwargs["eliminate"] = to_eliminate
                                kwargs["chain_a"] = _paint_locked_candidates(
                                    house, possibility)
                            return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    if _type_1() or _type_2():
        return kwargs
    return None
コード例 #19
0
                                    eliminate_options(solver_status, board,
                                                      to_eliminate, window)
                                    fish_strategies[n][0].clues += len(
                                        solver_status.naked_singles)
                                    fish_strategies[n][
                                        0].options_removed += len(to_eliminate)
                                    kwargs["solver_tool"] = fish_strategies[n][
                                        0].__name__
                                    if window:
                                        kwargs["chain_a"] = _get_fish_nodes(
                                            candidate, cells, x_ids)
                                        kwargs["eliminate"] = to_eliminate
                                    return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    if _find_finned_fish(True) or _find_finned_fish(False):
        return kwargs
    return None


def _sashimi_fish(solver_status, board, window, n):
    """ Generic 'sashimi fish' technique """

    fish_strategies = {
        2: (sashimi_x_wing, 150),
        3: (sashimi_swordfish, 240),
        4: (sashimi_jellyfish, 280),
        5: (sashimi_squirmbag, 470),
    }
コード例 #20
0
def empty_rectangle(solver_status, board, window):
    """ The relatively good description of Empty Rectangle strategy is
     available at Sudoku Coach page (http://www.taupierbw.be/SudokuCoach/SC_EmptyRectangle.shtml)
     - although it is not complete
     Rating: 120 - 140 """

    by_row_boxes = [[[3, 6], [4, 7], [5, 8]],
                    [[0, 6], [1, 7], [2, 8]],
                    [[0, 3], [1, 4], [2, 5]]]
    by_col_boxes = [[[1, 2], [4, 5], [7, 8]],
                    [[0, 2], [3, 5], [6, 8]],
                    [[0, 1], [3, 4], [6, 7]]]

    def _find_empty_rectangle(idx, by_row):
        cells_by_x = CELLS_IN_ROW if by_row else CELLS_IN_COL
        cells_by_y = CELLS_IN_COL if by_row else CELLS_IN_ROW
        cells = cells_by_x[idx]
        opts = ''.join(board[cell] for cell in cells if len(board[cell]) > 1)
        for val in SUDOKU_VALUES_LIST:
            if opts.count(val) == 2:
                idy = [j for j in range(9) if val in board[cells[j]]]
                if CELL_BOX[idy[0]] != CELL_BOX[idy[1]]:
                    for i in range(2):
                        for j in range(2):
                            box = by_row_boxes[idx//3][idy[i]//3][j] if by_row else by_col_boxes[idx//3][idy[i]//3][j]
                            central_line = (box // 3) * 3 + 1 if by_row else (box % 3) * 3 + 1
                            box_cells = set(CELLS_IN_BOX[box])
                            central_line_cells = set(cells_by_x[central_line]).intersection(box_cells)
                            cross_cells = box_cells.intersection(central_line_cells.union(set(cells_by_y[idy[i]])))
                            rect_corners = box_cells.difference(cross_cells)
                            corners_values = ''.join(board[cell] for cell in rect_corners)
                            if corners_values.count(val) == 0:
                                hole_cells = list(central_line_cells.difference(set(cells_by_y[idy[i]])))
                                if val in board[hole_cells[0]] or val in board[hole_cells[1]]:
                                    impacted_cell = cells_by_y[idy[(i + 1) % 2]][central_line]
                                    if val in board[impacted_cell]:
                                        to_eliminate = [(val, impacted_cell)]
                                        if to_eliminate:
                                            corners = set(cell for cell in cells_by_x[idx] if val in board[cell])
                                            if val in board[hole_cells[0]]:
                                                corners.add(hole_cells[0])
                                            if val in board[hole_cells[1]]:
                                                corners.add(hole_cells[1])
                                            corners = list(corners)
                                            corners.insert(0, val)
                                            house = set(cells).union(cross_cells)
                                            solver_status.capture_baseline(board, window)
                                            solver_status.capture_baseline(board, window)
                                            if window:
                                                window.options_visible = window.options_visible.union(house)
                                            eliminate_options(solver_status, board, to_eliminate, window)
                                            kwargs["solver_tool"] = "empty_rectangle"
                                            kwargs["house"] = house
                                            kwargs["impacted_cells"] = (impacted_cell,)
                                            kwargs["eliminate"] = [(val, impacted_cell)]
                                            kwargs["nodes"] = corners
                                            empty_rectangle.clues += len(solver_status.naked_singles)
                                            empty_rectangle.options_removed += len(to_eliminate)
                                            return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    for indx in range(9):
        if _find_empty_rectangle(indx, True):
            return kwargs
        if _find_empty_rectangle(indx, False):
            return kwargs
    return kwargs
コード例 #21
0
def finned_x_wing(solver_status, board, window):
    """ TODO """
    def _find_finned_x_wing(by_row, option):
        cells = CELLS_IN_ROW if by_row else CELLS_IN_COL
        for row_1 in range(9):
            r1_cols = set(col for col in range(9)
                          if option in board[cells[row_1][col]]
                          and len(board[cells[row_1][col]]) > 1)
            if len(r1_cols) == 2:
                for row_2 in range(9):
                    if row_2 != row_1:
                        r2_cols = set(col for col in range(9)
                                      if option in board[cells[row_2][col]]
                                      and len(board[cells[row_2][col]]) > 1)
                        if r2_cols.issuperset(r1_cols):
                            fin = r2_cols.difference(r1_cols)
                            other_cells = set()
                            house = set()
                            corners = list()
                            if len(fin) == 1:
                                col_1 = r1_cols.pop()
                                col_2 = r1_cols.pop()
                                col_f = fin.pop()
                                if by_row:
                                    box_1 = CELL_BOX[row_2 * 9 + col_1]
                                    box_2 = CELL_BOX[row_2 * 9 + col_2]
                                    box_f = CELL_BOX[row_2 * 9 + col_f]
                                    corners = [
                                        option, row_1 * 9 + col_1,
                                        row_1 * 9 + col_2, row_2 * 9 + col_1,
                                        row_2 * 9 + col_f, row_2 * 9 + col_2
                                    ]
                                    house = set(CELLS_IN_ROW[row_1]).union(
                                        set(CELLS_IN_ROW[row_2]))
                                    if box_1 == box_f:
                                        other_cells = set(
                                            CELLS_IN_BOX[box_1]).intersection(
                                                set(CELLS_IN_COL[col_1]))
                                        other_cells.discard(row_1 * 9 + col_1)
                                        other_cells.discard(row_2 * 9 + col_1)
                                    elif box_2 == box_f:
                                        other_cells = set(
                                            CELLS_IN_BOX[box_2]).intersection(
                                                set(CELLS_IN_COL[col_2]))
                                        other_cells.discard(row_1 * 9 + col_2)
                                        other_cells.discard(row_2 * 9 + col_2)
                                else:
                                    box_1 = CELL_BOX[col_1 * 9 + row_2]
                                    box_2 = CELL_BOX[col_2 * 9 + row_2]
                                    box_f = CELL_BOX[col_f * 9 + row_2]
                                    corners = [
                                        option, col_1 * 9 + row_1,
                                        col_2 * 9 + row_1, col_1 * 9 + row_2,
                                        col_f * 9 + row_2, col_2 * 9 + row_2
                                    ]
                                    house = set(CELLS_IN_COL[row_1]).union(
                                        set(CELLS_IN_COL[row_2]))
                                    if box_f == box_1:
                                        other_cells = set(
                                            CELLS_IN_BOX[box_1]).intersection(
                                                set(CELLS_IN_ROW[col_1]))
                                        other_cells.discard(col_1 * 9 + row_1)
                                        other_cells.discard(col_1 * 9 + row_2)
                                    elif box_f == box_2:
                                        other_cells = set(
                                            CELLS_IN_BOX[box_2]).intersection(
                                                set(CELLS_IN_ROW[col_2]))
                                        other_cells.discard(col_2 * 9 + row_1)
                                        other_cells.discard(col_2 * 9 + row_2)
                            if other_cells:
                                to_eliminate = [(option, cell)
                                                for cell in other_cells
                                                if option in board[cell]]
                                if to_eliminate:
                                    solver_status.capture_baseline(
                                        board, window)
                                    if window:
                                        window.options_visible = window.options_visible.union(
                                            house).union(other_cells)
                                    eliminate_options(solver_status, board,
                                                      to_eliminate, window)
                                    kwargs["solver_tool"] = "finned_x_wings"
                                    kwargs[
                                        "singles"] = solver_status.naked_singles
                                    kwargs["finned_x_wing"] = corners
                                    kwargs["subset"] = [option]
                                    kwargs["eliminate"] = to_eliminate
                                    kwargs["house"] = house
                                    kwargs["impacted_cells"] = other_cells
                                    finned_x_wing.options_removed += len(
                                        to_eliminate)
                                    finned_x_wing.clues += len(
                                        solver_status.naked_singles)
                                    # print('\tfinned X-wing')
                                    return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    for opt in SUDOKU_VALUES_LIST:
        if _find_finned_x_wing(True, opt):
            return kwargs
        if _find_finned_x_wing(False, opt):
            return kwargs
    return kwargs
コード例 #22
0
def two_string_kite(solver_status, board, window):
    """ Two crossing strong links, weakly connected in a box """
    def _get_strings(digit, lines):
        strings = set()
        for line in lines:
            cells = tuple(cell for cell in line if digit in board[cell])
            in_boxes = tuple(CELL_BOX[cell] for cell in cells)
            if len(cells) in (2, 3) and len(set(in_boxes)) == 2:
                strings.add(ConjugateCells(cells, in_boxes))
        return strings

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    for candidate in SUDOKU_VALUES_LIST:
        x_strings = _get_strings(candidate, CELLS_IN_ROW)
        y_strings = _get_strings(candidate, CELLS_IN_COL)
        for x_string in x_strings:
            for y_string in y_strings:
                nodes = {cell
                         for cell in x_string.cells
                         }.union(cell for cell in y_string.cells)
                boxes = {box
                         for box in x_string.boxes
                         }.union(box for box in y_string.boxes)
                if len(nodes) == (len(x_string.cells) +
                                  len(y_string.cells)) and len(boxes) == 3:
                    end_box_x = boxes.difference(y_string.boxes).pop()
                    end_box_y = boxes.difference(x_string.boxes).pop()
                    if x_string.boxes.count(
                            end_box_x) == 1 and y_string.boxes.count(
                                end_box_y) == 1:
                        end_x = {
                            cell
                            for i, cell in enumerate(x_string.cells)
                            if x_string.boxes[i] == end_box_x
                        }
                        end_y = {
                            cell
                            for i, cell in enumerate(y_string.cells)
                            if y_string.boxes[i] == end_box_y
                        }
                        assert len(end_x) == 1
                        assert len(end_y) == 1
                        end_x = end_x.pop()
                        end_y = end_y.pop()
                        impacted = set(
                            CELLS_IN_COL[CELL_COL[end_x]]).intersection(
                                CELLS_IN_ROW[CELL_ROW[end_y]])
                        assert len(impacted) == 1
                        impacted = impacted.pop()
                        if candidate in board[impacted]:
                            to_eliminate = {
                                (candidate, impacted),
                            }
                            if window:
                                solver_status.capture_baseline(board, window)
                            eliminate_options(solver_status, board,
                                              to_eliminate, window)
                            two_string_kite.clues += len(
                                solver_status.naked_singles)
                            two_string_kite.options_removed += 1
                            kwargs["solver_tool"] = two_string_kite.__name__
                            if window:
                                houses = set(
                                    CELLS_IN_ROW[CELL_ROW[end_x]]).union(
                                        CELLS_IN_COL[CELL_COL[end_y]]).union({
                                            impacted,
                                        })
                                window.options_visible = window.options_visible.union(
                                    houses)
                                kwargs["chain_a"] = {
                                    cell: {
                                        (candidate, 'cyan'),
                                    }
                                    for cell in nodes
                                }
                                kwargs["eliminate"] = to_eliminate
                                kwargs["house"] = houses
                            return kwargs
    return None
コード例 #23
0
def empty_rectangle(solver_status, board, window):
    """ TODO """
    def get_er_boxes(candidate):
        er_boxes = set()
        for box_id in range(9):
            with_candidate = set(cell for cell in CELLS_IN_BOX[box_id]
                                 if candidate in board[cell])
            if len(with_candidate) == 4:
                in_rows = [CELL_ROW[cell] for cell in with_candidate]
                in_columns = [CELL_COL[cell] for cell in with_candidate]
                if len(set(in_rows)) == 3 and len(set(in_columns)) == 3:
                    for row in set(in_rows):
                        if in_rows.count(row) == 2:
                            row_pair = {
                                cell
                                for cell in with_candidate
                                if CELL_ROW[cell] == row
                            }
                            for column in set(in_columns):
                                if in_columns.count(column) == 2:
                                    col_pair = {
                                        cell
                                        for cell in with_candidate
                                        if CELL_COL[cell] == column
                                    }
                                    if len(row_pair.union(col_pair)) == 4:
                                        er_boxes.add(
                                            ErBox(tuple(with_candidate),
                                                  box_id, row, column))
        return er_boxes

    def get_conjugate_pairs(candidate, lines):
        conjugate_pairs = set()
        for line in lines:
            in_cells = set(cell for cell in line if candidate in board[cell])
            if len(in_cells) == 2:
                cell_a = in_cells.pop()
                cell_b = in_cells.pop()
                if CELL_BOX[cell_a] != CELL_BOX[cell_b]:
                    conjugate_pairs.add(ConjugatePair(cell_a, cell_b))
        return conjugate_pairs

    def basic_empty_rectangle():
        """ canonical form of empty rectangle pattern """
        for candidate in SUDOKU_VALUES_LIST:
            er_boxes = get_er_boxes(candidate)

            # if er_boxes:
            #     print(f'\tDupa')
            #     return BasicErPattern(candidate, set(er_boxes.pop().in_cells), 0)

            conjugate_pairs = get_conjugate_pairs(candidate, CELLS_IN_ROW)
            for conjugate_pair in conjugate_pairs:
                for er_box in er_boxes:
                    if CELL_COL[conjugate_pair.cell_a] == er_box.column\
                            and CELL_BOX[conjugate_pair.cell_a] != er_box.box:
                        impacted = er_box.row * 9 + CELL_COL[
                            conjugate_pair.cell_b]
                        if candidate in board[impacted]:
                            return BasicErPattern(
                                candidate,
                                set(er_box.in_cells).union({
                                    conjugate_pair.cell_a,
                                    conjugate_pair.cell_b
                                }), impacted)
                    if CELL_COL[conjugate_pair.cell_b] == er_box.column\
                            and CELL_BOX[conjugate_pair.cell_b] != er_box.box:
                        impacted = er_box.row * 9 + CELL_COL[
                            conjugate_pair.cell_a]
                        if candidate in board[impacted]:
                            return BasicErPattern(
                                candidate,
                                set(er_box.in_cells).union({
                                    conjugate_pair.cell_a,
                                    conjugate_pair.cell_b
                                }), impacted)
            conjugate_pairs = get_conjugate_pairs(candidate, CELLS_IN_COL)
            for conjugate_pair in conjugate_pairs:
                for er_box in er_boxes:
                    if CELL_ROW[conjugate_pair.cell_a] == er_box.row\
                            and CELL_BOX[conjugate_pair.cell_a] != er_box.box:
                        impacted = CELL_ROW[
                            conjugate_pair.cell_b] * 9 + er_box.column
                        if candidate in board[impacted]:
                            return BasicErPattern(
                                candidate,
                                set(er_box.in_cells).union({
                                    conjugate_pair.cell_a,
                                    conjugate_pair.cell_b
                                }), impacted)
                    if CELL_ROW[conjugate_pair.cell_b] == er_box.row\
                            and CELL_BOX[conjugate_pair.cell_b] != er_box.box:
                        impacted = CELL_ROW[
                            conjugate_pair.cell_a] * 9 + er_box.column
                        if candidate in board[impacted]:
                            return BasicErPattern(
                                candidate,
                                set(er_box.in_cells).union({
                                    conjugate_pair.cell_a,
                                    conjugate_pair.cell_b
                                }), impacted)
        return None

    set_remaining_candidates(board, solver_status)
    basic_er_pattern = basic_empty_rectangle()
    if basic_er_pattern:
        kwargs = {}
        to_eliminate = {
            (basic_er_pattern.candidate, basic_er_pattern.impacted),
        }
        if window:
            solver_status.capture_baseline(board, window)
        eliminate_options(solver_status, board, to_eliminate, window)
        empty_rectangle.clues += len(solver_status.naked_singles)
        empty_rectangle.options_removed += 1
        kwargs["solver_tool"] = empty_rectangle.__name__
        if window:
            kwargs["chain_a"] = {
                cell: {
                    (basic_er_pattern.candidate, 'cyan'),
                }
                for cell in basic_er_pattern.pattern
            }
            kwargs["eliminate"] = to_eliminate

        print(f'\t{kwargs["solver_tool"]}')

        return kwargs
    return None
コード例 #24
0
ファイル: coloring.py プロジェクト: marrydzy/Learn_Sudoku
def naked_xy_chain(solver_status, board, window):
    """ Remove candidates (options) using XY Wing technique:
    For explanation of the technique see e.g.:
     - http://www.sudokusnake.com/nakedxychains.php
    The strategy is assessed as 'Hard', 'Unfair', or 'Diabolical'.
    Ranking of the method (called XY-Chain) varies widely
    260 and 900
    Implementation comments:
    Building a graph and identifying potential chains (paths) was straightforward.
    A slightly tricky part was to add checking for possibility of bidirectional traversing
    between end nodes of the potential paths
    """
    def _build_bi_value_cells_graph():
        bi_value_cells = set(cell for cell in range(81)
                             if len(board[cell]) == 2)
        graph = nx.Graph()
        for cell in bi_value_cells:
            neighbours = set(ALL_NBRS[cell]).intersection(bi_value_cells)
            graph.add_edges_from([(cell, other_cell, {
                'candidates':
                set(board[cell]).intersection(set(board[other_cell]))
            }) for other_cell in neighbours if len(
                set(board[cell]).intersection(set(board[other_cell]))) == 1])
        return graph

    set_remaining_candidates(board, solver_status)
    graph = _build_bi_value_cells_graph()
    components = list(nx.connected_components(graph))
    unresolved = [cell for cell in range(81) if len(board[cell]) > 2]
    kwargs = {}
    for cell in unresolved:
        for component in components:
            nodes = component.intersection(set(ALL_NBRS[cell]))
            candidates = ''.join(board[node] for node in nodes)
            for candidate in board[cell]:
                if candidates.count(candidate) == 2:
                    ends = [node for node in nodes if candidate in board[node]]
                    path = nx.algorithms.shortest_paths.generic.shortest_path(
                        graph, ends[0], ends[1])
                    if _check_bidirectional_traversing(candidate, path, board):
                        impacted_cells = {
                            cell
                            for cell in set(ALL_NBRS[path[0]]).intersection(
                                set(ALL_NBRS[path[-1]]))
                            if len(board[cell]) > 1
                        }
                        to_eliminate = [(candidate, cell)
                                        for cell in impacted_cells
                                        if candidate in board[cell]]
                        edges = [(path[n], path[n + 1])
                                 for n in range(len(path) - 1)]
                        solver_status.capture_baseline(board, window)
                        if window:
                            window.options_visible = window.options_visible.union(
                                _get_graph_houses(edges)).union({cell})
                        eliminate_options(solver_status, board, to_eliminate,
                                          window)
                        kwargs["solver_tool"] = "naked_xy_chain"
                        kwargs["impacted_cells"] = impacted_cells
                        kwargs["eliminate"] = to_eliminate
                        kwargs["c_chain"] = _color_naked_xy_chain(
                            graph, path, candidate)
                        kwargs["edges"] = edges
                        naked_xy_chain.options_removed += len(to_eliminate)
                        naked_xy_chain.clues += len(
                            solver_status.naked_singles)
                        return kwargs
    return kwargs
コード例 #25
0
def sue_de_coq(solver_status, board, window):
    """ TODO """

    def _find_sue_de_coq_type_1(box, by_rows):
        for cell_1 in CELLS_IN_BOX[box]:
            if len(board[cell_1]) == 2:
                if by_rows:
                    indexes = [row for row in range((box // 3) * 3, (box // 3) * 3 + 3) if row != CELL_ROW[cell_1]]
                else:
                    indexes = [col for col in range((box % 3) * 3, (box % 3) * 3 + 3) if col != CELL_COL[cell_1]]
                for indx in indexes:
                    cells_b = set(CELLS_IN_BOX[box])
                    cells_1 = set(CELLS_IN_ROW[indx]) if by_rows else set(CELLS_IN_COL[indx])
                    cells_2 = [cell for cell in cells_1.difference(cells_b) if len(board[cell]) > 1]
                    cells_3 = [cell for cell in cells_1.intersection(cells_b) if len(board[cell]) > 1]
                    cells_4 = [cell for cell in cells_b.difference(cells_1) if len(board[cell]) > 1]
                    if len(cells_3) > 1:
                        for cell_2 in cells_2:
                            if len(board[cell_2]) == 2 and not set(board[cell_1]).intersection(set(board[cell_2])):
                                options_12 = set(board[cell_1]).union(set(board[cell_2]))
                                for pair in combinations(cells_3, 2):
                                    if options_12 == set(board[pair[0]]).union(board[pair[1]]):
                                        to_eliminate = []
                                        for opt in board[cell_1]:
                                            for cell in cells_4:
                                                if cell != cell_1 and opt in board[cell]:
                                                    to_eliminate.append((opt, cell))
                                        for opt in board[cell_2]:
                                            for cell in cells_1:
                                                if cell != cell_2 and cell != pair[0] and cell != pair[1] \
                                                        and opt in board[cell]:
                                                    to_eliminate.append((opt, cell))
                                        if to_eliminate:
                                            solver_status.capture_baseline(board, window)
                                            house = cells_b.union(cells_1)
                                            pattern = {cell_1, cell_2, pair[0], pair[1]}
                                            impacted_cells = set(cells_2).union(set(cells_3)).union(set(cells_4))
                                            impacted_cells.difference(pattern)
                                            if window:
                                                window.options_visible = window.options_visible.union(house).union(
                                                    house)
                                            eliminate_options(solver_status, board, to_eliminate, window)
                                            kwargs["solver_tool"] = "sue_de_coq"
                                            kwargs["singles"] = solver_status.naked_singles
                                            kwargs["sue_de_coq"] = pattern
                                            kwargs["eliminate"] = to_eliminate
                                            kwargs["house"] = house
                                            kwargs["impacted_cells"] = impacted_cells
                                            kwargs["subset"] = [to_eliminate[0][0]]
                                            return True
        return False

    def _find_sue_de_coq_type_2(box, by_rows):
        for cell_1 in CELLS_IN_BOX[box]:
            if len(board[cell_1]) == 2:
                if by_rows:
                    indexes = [row for row in range((box // 3) * 3, (box // 3) * 3 + 3) if row != CELL_ROW[cell_1]]
                else:
                    indexes = [col for col in range((box % 3) * 3, (box % 3) * 3 + 3) if col != CELL_COL[cell_1]]
                for indx in indexes:
                    cells_b = set(CELLS_IN_BOX[box])
                    cells_1 = set(CELLS_IN_ROW[indx]) if by_rows else set(CELLS_IN_COL[indx])
                    cells_2 = [cell for cell in cells_1.difference(cells_b) if len(board[cell]) > 1]
                    cells_3 = [cell for cell in cells_1.intersection(cells_b) if len(board[cell]) > 1]
                    cells_4 = [cell for cell in cells_b.difference(cells_1) if len(board[cell]) > 1]
                    if len(cells_3) == 3:
                        for cell_2 in cells_2:
                            if len(board[cell_2]) == 2 and not set(board[cell_1]).intersection(set(board[cell_2])):
                                options_12 = set(board[cell_1]).union(set(board[cell_2]))
                                options_3 = set(board[cells_3[0]]).union(set(board[cells_3[1]])).union(
                                        set(board[cells_3[2]]))
                                if options_3.issuperset(options_12) and len(options_3.difference(options_12)) == 1:
                                    to_eliminate = []
                                    for opt in board[cell_1]:
                                        for cell in cells_4:
                                            if cell != cell_1 and opt in board[cell]:
                                                to_eliminate.append((opt, cell))
                                    for opt in board[cell_2]:
                                        for cell in cells_2:
                                            if cell != cell_2 and opt in board[cell]:
                                                to_eliminate.append((opt, cell))
                                    opt = options_3.difference(options_12).pop()
                                    for cell in set(cells_2).union(set(cells_4)):
                                        if opt in board[cell]:
                                            to_eliminate.append((opt, cell))
                                    if to_eliminate:
                                        solver_status.capture_baseline(board, window)
                                        house = cells_b.union(cells_1)
                                        pattern = {cell_1, cell_2}.union(cells_3)
                                        impacted_cells = set(cells_2).union(set(cells_3)).union(set(cells_4))
                                        impacted_cells.difference(pattern)
                                        if window:
                                            window.options_visible = window.options_visible.union(house).union(
                                                house)
                                        eliminate_options(solver_status, board, to_eliminate, window)
                                        kwargs["solver_tool"] = "sue_de_coq"
                                        kwargs["singles"] = solver_status.naked_singles
                                        kwargs["sue_de_coq"] = pattern
                                        kwargs["eliminate"] = to_eliminate
                                        kwargs["house"] = house
                                        kwargs["impacted_cells"] = impacted_cells
                                        kwargs["subset"] = [to_eliminate[0][0]]
                                        return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    for sqr in range(9):
        if _find_sue_de_coq_type_1(sqr, True):
            return kwargs
        if _find_sue_de_coq_type_2(sqr, True):
            return kwargs
        if _find_sue_de_coq_type_1(sqr, False):
            return kwargs
        if _find_sue_de_coq_type_2(sqr, False):
            return kwargs
    return kwargs
コード例 #26
0
def finned_mutant_x_wing(solver_status, board, window):
    """ TODO """
    def _find_finned_rccb_mutant_x_wing(box_id):
        pairs_dict = get_house_pairs(CELLS_IN_BOX[box_id], board)
        for pair, cells in pairs_dict.items():
            if len(cells) == 2:
                cells_pos = [(CELL_ROW[cells[0]], CELL_COL[cells[1]]),
                             (CELL_ROW[cells[1]], CELL_COL[cells[0]])]
                values = (pair[0], pair[1])
                for value in values:
                    for row, col in cells_pos:
                        col_2 = [
                            CELL_COL[cell] for cell in CELLS_IN_ROW[row] if
                            value in board[cell] and CELL_BOX[cell] != box_id
                        ]
                        row_2 = [
                            CELL_ROW[cell] for cell in CELLS_IN_COL[col] if
                            value in board[cell] and CELL_BOX[cell] != box_id
                        ]
                        if len(col_2) == 1 and len(row_2) == 1:
                            impacted_cell = set(
                                CELLS_IN_COL[col_2[0]]).intersection(
                                    set(CELLS_IN_ROW[row_2[0]]))
                            cell = impacted_cell.pop()
                            if value in board[cell]:
                                house = set(CELLS_IN_ROW[row]).union(
                                    set(CELLS_IN_COL[col]))
                                impacted_cell = {cell}
                                to_eliminate = [
                                    (value, cell),
                                ]
                                corners = [
                                    value, cells[0], cells[1],
                                    row * 9 + col_2[0], row_2[0] * 9 + col
                                ]
                                solver_status.capture_baseline(board, window)
                                if window:
                                    window.options_visible = window.options_visible.union(
                                        house).union(impacted_cell)
                                eliminate_options(solver_status, board,
                                                  to_eliminate, window)
                                kwargs[
                                    "solver_tool"] = "finned_rccb_mutant_x_wing"
                                kwargs["eliminate"] = to_eliminate
                                kwargs["house"] = house
                                kwargs["impacted_cells"] = impacted_cell
                                kwargs["finned_x_wing"] = corners
                                finned_mutant_x_wing.options_removed += len(
                                    to_eliminate)
                                finned_mutant_x_wing.clues += len(
                                    solver_status.naked_singles)
                                return True
        return False

    def _find_finned_cbrc_mutant_x_wing(by_column, indx):
        house_1 = set(CELLS_IN_COL[indx]) if by_column else set(
            CELLS_IN_ROW[indx])
        pairs_dict = get_house_pairs(house_1, board)
        for pair, cells in pairs_dict.items():
            if len(cells) == 2 and CELL_BOX[cells[0]] != CELL_BOX[cells[1]]:
                cells_pos = [(CELL_ROW[cells[0]], CELL_COL[cells[0]]),
                             (CELL_ROW[cells[1]], CELL_COL[cells[1]])]
                values = (pair[0], pair[1])
                for value in values:
                    for row, col in cells_pos:
                        house_2 = set(CELLS_IN_ROW[row]) if by_column else set(
                            CELLS_IN_COL[col])
                        boxes = [
                            box for box in range(9)
                            if set(CELLS_IN_BOX[box]).intersection(house_2) and
                            not set(CELLS_IN_BOX[box]).intersection(house_1)
                        ]
                        for box in boxes:
                            fins = [
                                cell for cell in set(
                                    CELLS_IN_BOX[box]).difference(house_2) if
                                value in board[cell] and len(board[cell]) > 1
                            ]
                            impacted_cell = None
                            if by_column:
                                col_2 = CELL_COL[fins[0]] if fins else None
                                for fin in fins[1:]:
                                    col_2 = col_2 if CELL_COL[
                                        fin] == col_2 else None
                                if col_2 is not None:
                                    row_2 = cells_pos[0][0] if row == cells_pos[
                                        1][0] else cells_pos[1][0]
                                    impacted_cell = row_2 * 9 + col_2
                            else:
                                row_2 = CELL_ROW[fins[0]] if fins else None
                                for fin in fins[1:]:
                                    row_2 = row_2 if CELL_ROW[
                                        fin] == row_2 else None
                                if row_2 is not None:
                                    col_2 = cells_pos[0][1] if col == cells_pos[
                                        1][1] else cells_pos[1][1]
                                    impacted_cell = row_2 * 9 + col_2
                            if impacted_cell is not None and value in board[
                                    impacted_cell]:
                                house = house_1.union(set(CELLS_IN_BOX[box]))
                                to_eliminate = [
                                    (value, impacted_cell),
                                ]
                                corners = [cell for cell in cells]
                                corners.extend(fins)
                                corners.insert(0, value)
                                solver_status.capture_baseline(board, window)
                                if window:
                                    window.options_visible = window.options_visible.union(
                                        house).union({impacted_cell})
                                eliminate_options(solver_status, board,
                                                  to_eliminate, window)
                                kwargs["solver_tool"] = \
                                    "finned_cbrc_mutant_x_wing" if by_column else "finned_rbcc_mutant_x_wing"
                                kwargs["eliminate"] = to_eliminate
                                kwargs["house"] = house
                                kwargs["impacted_cells"] = {impacted_cell}
                                kwargs["finned_x_wing"] = corners
                                finned_mutant_x_wing.options_removed += len(
                                    to_eliminate)
                                finned_mutant_x_wing.clues += len(
                                    solver_status.naked_singles)
                                # if len(fins) > 1:
                                #     print('\nBingo!')
                                return True
        return False

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    for i in range(9):
        if _find_finned_rccb_mutant_x_wing(i):
            # print('\nFinned RCCB Mutant X-Wing')
            return kwargs
        if _find_finned_cbrc_mutant_x_wing(True, i):
            # print('\nFinned CBRC Mutant X-Wing')
            return kwargs
        if _find_finned_cbrc_mutant_x_wing(False, i):
            # print('\nFinned RBCC Mutant X-Wing')
            return kwargs
    return kwargs