示例#1
0
    def how_to_elimionation_only_memo(cls, how_anlz: HowToAnalyze) -> Msg:
        """消去法(メモがその枡にしかない)メッセージ生成

        Args:
            how_anlz (HowToAnalyze): 解析方法

        Returns:
            Msg: メッセージ
        """
        return Msg(
            MsgType.SUCCESS,
            cls._get_msg(MsgCode.HOW_TO_ELIMIONATION_ONLY_MEMO).format(
                changedSqu=SudokuUtil.cnv_squ_to_text(how_anlz.changed_squ),
                region=SudokuUtil.cnv_region_to_text(how_anlz.region),
                commitVal=how_anlz.commit_val))
示例#2
0
    def dup_row(cls, pivot_squ: Square, compare_squ: Square) -> Msg:
        """行重複メッセージ生成

        Args:
            pivot_squ (Square): 基準枡
            compare_squ (Square): 比較枡

        Returns:
            Msg: メッセージ
        """
        return Msg(
            MsgType.ERROR,
            cls._get_msg(MsgCode.DUP_ROW).format(
                val=pivot_squ.get_fixed_val(),
                pivotSqu=SudokuUtil.cnv_squ_to_text(pivot_squ),
                compareSqu=SudokuUtil.cnv_squ_to_text(compare_squ)))
示例#3
0
    def how_to_elimionation(cls, how_anlz: HowToAnalyze) -> Msg:
        """消去法(メモ削除)メッセージ生成

        Args:
            how_anlz (HowToAnalyze): 解析方法

        Returns:
            Msg: メッセージ
        """
        return Msg(
            MsgType.INFO,
            cls._get_msg(MsgCode.HOW_TO_ELIMIONATION).format(
                changedSqu=SudokuUtil.cnv_squ_to_text(how_anlz.changed_squ),
                region=SudokuUtil.cnv_region_to_text(how_anlz.region),
                triggerSqu=SudokuUtil.cnv_squ_to_text(
                    how_anlz.trigger_squ_list[0]),
                removeMemo=how_anlz.remove_memo_list[0]))
示例#4
0
    def how_to_simple_chain(cls, how_anlz: HowToAnalyze) -> Msg:
        """シンプルチェーン法メッセージ生成

        Args:
            how_anlz (HowToAnalyze): 解析方法

        Returns:
            Msg: メッセージ
        """

        return Msg(
            MsgType.INFO,
            cls._get_msg(MsgCode.HOW_TO_SIMPLE_CHAIN).format(
                changedSqu=SudokuUtil.cnv_squ_to_text(how_anlz.changed_squ),
                chainSquList=SudokuUtil.cnv_squ_list_to_text(
                    how_anlz.chain_squ_list),
                removeMemo=how_anlz.remove_memo_list[0]))
示例#5
0
    def how_to_locked_candidates(cls, how_anlz: HowToAnalyze) -> Msg:
        """ロックされた候補法メッセージ生成

        Args:
            how_anlz (HowToAnalyze): 解析方法

        Returns:
            Msg: メッセージ
        """
        return Msg(
            MsgType.INFO,
            cls._get_msg(MsgCode.HOW_TO_LOCKED_CANDIDATES).format(
                changedSqu=SudokuUtil.cnv_squ_to_text(how_anlz.changed_squ),
                triggerSqu=SudokuUtil.cnv_squ_to_text(
                    how_anlz.trigger_squ_list[0]),
                removeMemo=how_anlz.remove_memo_list[0],
                regionPos=how_anlz.trigger_squ_list[0].row if how_anlz.region
                == Region.ROW else how_anlz.trigger_squ_list[0].clm,
                region=SudokuUtil.cnv_region_to_text(how_anlz.region)))
def initBeforeAnalyze(wk: AnalyzeWk) -> None:
    """解析前初期設定

    Args:
        wk (AnalyzeWk): ワーク
    """
    # ヒントまたは値がない枡にメモ値を設定
    for squ in SudokuUtil.find_unfixed_squ_from_flame(wk.flame):
        if (squ.get_fixed_val() is None):
            if len(squ.memo_val_list) == 0:
                squ.memo_val_list.extend([1, 2, 3, 4, 5, 6, 7, 8, 9])
示例#7
0
    def how_to_hidden_pair(cls, how_anlz: HowToAnalyze,
                           pair_list: List[int]) -> Msg:
        """隠れペア法メッセージ生成

        Args:
            how_anlz (HowToAnalyze): 解析方法
            pair_list (List[int]): ペアリスト

        Returns:
            Msg: メッセージ
        """
        return Msg(
            MsgType.INFO,
            cls._get_msg(MsgCode.HOW_TO_HIDDEN_PAIR).format(
                changedSqu=SudokuUtil.cnv_squ_to_text(how_anlz.changed_squ),
                region=SudokuUtil.cnv_region_to_text(how_anlz.region),
                triggerSquList=SudokuUtil.cnv_squ_list_to_text(
                    how_anlz.trigger_squ_list),
                pairList=SudokuUtil.cnv_memo_list_to_text(pair_list),
                removeMemo=how_anlz.remove_memo_list[0]))
def _removeMemo(
    how_anlz_list: List[HowToAnalyze],
    region: Region,
    squ_list: List[Square]
) -> None:
    """メモの除外

    ヒント(値)によってメモを除外

    Args:
        how_anlz_list (List[ChangeHistroy]): 解析方法
        region (Region): 領域
        squ_list (List[Square]): 枡リスト
    """
    # 確定枡を抽出
    not_none_squ_list: List[Square] = SudokuUtil.find_fixed_squ_from_region(
        squ_list)

    # 未確定枡を抽出
    none_squ_list: List[Square] = SudokuUtil.find_unfixed_squ_from_region(
        squ_list)

    # メモからヒント(値)を除外
    for none_squ in none_squ_list:
        for memo in none_squ.memo_val_list[:]:
            for not_none_squ in not_none_squ_list:
                if memo == not_none_squ.get_fixed_val():
                    none_squ.memo_val_list.remove(memo)

                    # 解析方法生成
                    how_anlz: HowToAnalyze = HowToAnalyze(
                        Method.ELIMIONATION)
                    how_anlz.region = region
                    how_anlz.remove_memo_list.append(memo)
                    how_anlz.changed_squ = none_squ
                    how_anlz.trigger_squ_list.append(not_none_squ)
                    how_anlz.msg = MsgFactory.how_to_elimionation(how_anlz)

                    how_anlz_list.append(how_anlz)
示例#9
0
    def not_exist_num_area(cls, num: int, squ: Square) -> Msg:
        """エリアに数字がないメッセージ生成

        Args:
            num (int): 数字
            squ (Square): 枡

        Returns:
            Msg: メッセージ
        """
        return Msg(
            MsgType.ERROR,
            cls._get_msg(MsgCode.NOT_EXIST_NUM_AREA).format(
                num=num, squ=SudokuUtil.cnv_squ_to_text(squ)))
示例#10
0
    def howto_summary(cls, idx: int, method: Method, cnt: int) -> Msg:
        """解析方法サマリメッセージ生成

        Args:
            cnt (int): 確定枡数

        Returns:
            Msg: メッセージ
        """
        return Msg(
            MsgType.INFO,
            cls._get_msg(MsgCode.HOW_TO_SUMMARY).format(
                idx=idx, method=SudokuUtil.cnv_method_to_text(method),
                cnt=cnt))
示例#11
0
    def how_to_x_wing(cls, how_anlz: HowToAnalyze,
                      region_pair: Tuple[int, int]) -> Msg:
        """X-Wing法メッセージ生成

        Args:
            how_anlz (HowToAnalyze): 解析方法
            region_pair (Tuple[int, int]): 方向位置

        Returns:
            Msg: メッセージ
        """

        # regionPos1、regionPos2を算出
        pos_set: Set[int] = set()
        for squ in how_anlz.trigger_squ_list:
            if how_anlz.region == Region.ROW:
                pos_set.add(squ.row)
            else:
                pos_set.add(squ.clm)

        pos_list: List[int] = list(pos_set)
        pos_list.sort()

        return Msg(
            MsgType.INFO,
            cls._get_msg(MsgCode.HOW_TO_X_WING).format(
                changedSqu=SudokuUtil.cnv_squ_to_text(how_anlz.changed_squ),
                removeMemo=how_anlz.remove_memo_list[0],
                regionPos1=pos_list[0],
                regionPos2=pos_list[1],
                region=SudokuUtil.cnv_region_to_text(how_anlz.region),
                triggerSqu1=SudokuUtil.cnv_squ_to_text(
                    how_anlz.trigger_squ_list[0]),
                triggerSqu2=SudokuUtil.cnv_squ_to_text(
                    how_anlz.trigger_squ_list[1]),
                triggerSqu3=SudokuUtil.cnv_squ_to_text(
                    how_anlz.trigger_squ_list[2]),
                triggerSqu4=SudokuUtil.cnv_squ_to_text(
                    how_anlz.trigger_squ_list[3])))
示例#12
0
def _analyze_naked_pair(
    wk: AnalyzeWk,
    how_anlz_list: List[HowToAnalyze],
    region: Region,
    squ_list: List[Square]
) -> None:
    """ネイキッドペア解析

    Args:
        wk (AnalyzeWk): ワーク
        how_anlz_list (List[HowToAnalyze]): 解析方法
        region (Region): 領域
    """

    # 未確定枡を取得
    unfixed_list: List[Square] = SudokuUtil.find_unfixed_squ_from_region(
        squ_list)

    # 対象領域の全ての枡が確定している
    if len(unfixed_list) == 0:
        return

    # ペア数(2~8)を大きくしながら解析
    # ※制限時は2~3
    find_pair_cnt: int = 8
    if Method.NAKED_PAIR in wk.limit_method_list:
        find_pair_cnt = 3
    for naked_num in range(2, find_pair_cnt + 1):

        # 未確定枡数-1よりネイキッド数の方が大きくなったら処理終了
        # [補足]
        # 未確定数とネイキッド数が同じ場合、必ず全ての枡がペアになるため除外するメモがなくなる
        # ⇒-1して終了条件の比較を行っている
        if len(unfixed_list) - 1 <= naked_num:
            return

        # ペア可能リストを算出
        can_pair_list = list()
        for unfixed_squ in unfixed_list:
            if len(unfixed_squ.memo_val_list) > naked_num:
                continue

            # naked_num=3の場合
            # +[1]--------------------------------------+
            # | 1:1          1:2        1:3             |
            # | m=[N,M,O](@) m=[N,P](*) ?               |
            # | 2:1          2:2        2:3             |
            # | m=[M,O]($)   m=[M,Q](*) ?               |
            # | 3:1          3:2        3:3             |
            # | m=[N,M](#)   m=[O,R](*) m=[N,M,O,R,Q,S] |
            # +[4]--------------------------------------+
            # @枡、$枡、#枡、*枡3個を抽出
            # 1:1 m=[N,M,O](@)
            # 1:2 m=[N,P](*)
            # 2:1 m=[M,O]($)
            # 2:2 m=[M,Q](*)
            # 3:1 m=[N,M](#)
            # 3:2 m=[O,R](*)
            can_pair_list.append(unfixed_squ)

        # ペア可能リストがペア数より小さい場合は対象外
        # 次のペア数を調べる
        if len(can_pair_list) < naked_num:
            continue

        # ペアを見つける
        # 例に当てはめると
        # 1:1 m=[N,M,O](@)
        # 1:2 m=[N,P](*)
        # 2:1 m=[M,O]($)
        # 2:2 m=[M,Q](*)
        # 3:1 m=[N,M](#)
        # 3:2 m=[O,R](*)
        # から
        # 1:1 m=[N,M,O](@)
        # 2:1 m=[M,O]($)
        # 3:1 m=[N,M](#)
        # を見つける
        for pivot_squ in can_pair_list[:]:
            pivot_pair: Set[int] = set(pivot_squ.memo_val_list)
            for compare_squ in can_pair_list[:]:
                if pivot_squ == compare_squ:
                    continue
                pivot_pair = pivot_pair.union(set(compare_squ.memo_val_list))
                if len(pivot_pair) > naked_num:
                    break
            if len(pivot_pair) > naked_num:
                can_pair_list.remove(pivot_squ)

        # ネイキッド数≒枡数は対象外
        if len(can_pair_list) != naked_num:
            continue

        # ペア発見!
        pair_set: Set[int] = set()
        for squ in can_pair_list:
            pair_set = pair_set.union(squ.memo_val_list)
        pair_list: List[int] = list(pair_set)
        pair_list.sort()

        # 変更枡抽出
        change_squ_list: List[Square] = list()
        for squ in unfixed_list:
            if squ in can_pair_list:
                continue
            for memo in squ.memo_val_list:
                if memo in pair_list:
                    change_squ_list.append(squ)
                    break

        # ペアは見つかったが、変更枡が存在しない
        if len(change_squ_list) == 0:
            continue

        for change_squ in change_squ_list:
            for loop_memo in pair_list:
                if loop_memo not in change_squ.memo_val_list:
                    continue

                # メモを除外
                change_squ.memo_val_list.remove(loop_memo)

                # 解析方法生成
                how_anlz: HowToAnalyze = HowToAnalyze(
                    Method.NAKED_PAIR)
                how_anlz.region = region
                how_anlz.changed_squ = change_squ
                how_anlz.remove_memo_list.append(loop_memo)
                how_anlz.trigger_squ_list.extend(can_pair_list)
                how_anlz.msg = MsgFactory.how_to_naked_pair(
                    how_anlz, pair_list)

                how_anlz_list.append(how_anlz)
def creata_json_response(
    result: bool,
    wk: AnalyzeWk
) -> JsonResponse:
    """JSONレスポンス生成

    Args:
        result (bool): 解析結果
        wk (AnalyzeWk): ワーク

    Returns:
        JsonResponse: JSONレスポンス
    """

    # 解析履歴変換
    history_json_list: List[Dict[str, any]] = list()
    for histroy in wk.histroy_list:
        # wk_flame, wk_change_history_list
        how_anlz_json_list: List[Dict] = list()
        if histroy.how_anlz_list is not None:
            for how_anlz in histroy.how_anlz_list:
                # how_anlz_json_list: List[Dict] = list()
                how_anlz_json_list.append(how_anlz.cnv_to_json())

        history_json: Dict[str, any] = dict()
        history_json["flame"] = histroy.flame.cnv_to_json()
        history_json["howToAnalyzeList"] = how_anlz_json_list
        history_json_list.append(history_json)

    msg_json_list: List[Dict] = list()
    # 解析結果サマリー
    for idx, history in enumerate(wk.histroy_list):
        # 解析方法なしははサマリーしようがないためスキップ
        if history.how_anlz_list is None or len(history.how_anlz_list) == 0:
            continue
        method: Method = history.how_anlz_list[0].method
        # 解析開始はサマリーしようがないためスキップ
        # エラーは後続の処理で出力しているためスキップ
        if method == Method.START or method == Method.ERROR_CHECK:
            continue
        # 例>N国同盟によってN回枡が更新されました。
        msg_json_list.append(
            MsgFactory.howto_summary(
                idx + 1, method, len(history.how_anlz_list))
            .cnv_to_json())

    # エラー抽出
    for wk_msg in wk.msg_list:
        msg_json_list.append(wk_msg.cnv_to_json())

    # N個の枡の答えが判明しました。
    fixed_num: int = \
        len(SudokuUtil.find_fixed_squ_from_flame(wk.flame))\
        - len(wk.hint_list)
    if fixed_num > 0:
        msg_json_list.append(
            MsgFactory.fixed_num(fixed_num)
            .cnv_to_json())

    # N個の枡の答えが判明出来ませんでした。
    unfixed_num: int = len(SudokuUtil.find_unfixed_squ_from_flame(wk.flame))
    if unfixed_num > 0:
        msg_json_list.append(
            MsgFactory.unfixed_num(unfixed_num)
            .cnv_to_json())

    return JsonResponse({
        "result": result,
        "msgList": msg_json_list,
        "historyList": history_json_list
    })
示例#14
0
def analyze(wk: AnalyzeWk) -> bool:
    """解析

    Args:
        wk (AnalyzeWk): 数独WK

    Returns:
        bool: 解析成功時にTrue
    """

    # 初回エラーチェック
    how_anlz_list_err: List[HowToAnalyze] = simpleErrorCheck.errorCheck(
        wk, first_check=True)
    if len(how_anlz_list_err) > 0:
        wk.addHistryForErr(how_anlz_list_err)
        return False

    # 解析前初期化
    initBeforeAnalyze(wk)

    # 解法リスト
    analyze_method_list: List[
        Tuple[
            Method,
            Callable[[AnalyzeWk, List[HowToAnalyze], bool]]
        ]
    ] = []
    # 消去法
    analyze_method_list.append(
        (Method.ELIMIONATION, methodElimionationRemoveMemo.analyze))

    # 消去法one memo
    analyze_method_list.append(
        (Method.ELIMIONATION_ONE_MEMO, methodElimionationOneMemo.analyze))

    # 消去法only memo
    analyze_method_list.append(
        (Method.ELIMIONATION_ONE_MEMO, methodElimionationOnlyMemo.analyze))

    # ロックされた候補法
    if Method.LOCKED_CANDIDATES in wk.use_method_list:
        analyze_method_list.append(
            (Method.LOCKED_CANDIDATES, methodLockedCandidates.analyze))

    # ネイキッドペア法
    if Method.NAKED_PAIR in wk.use_method_list:
        analyze_method_list.append(
            (Method.NAKED_PAIR, methodNakedPair.analyze))

    # 隠れペア法
    if Method.HIDDEN_PAIR in wk.use_method_list:
        analyze_method_list.append(
            (Method.NAKED_PAIR, methodHiddenPair.analyze))

    # X-Wing
    if Method.X_WING in wk.use_method_list:
        analyze_method_list.append(
            (Method.X_WING, methodXWing.analyze))

    # XYチェーン法
    if Method.XY_CHAIN in wk.use_method_list:
        analyze_method_list.append(
            (Method.XY_CHAIN, methodXYChain.analyze))

    # シンプルチェーン法
    if Method.SIMPLE_CHAIN in wk.use_method_list:
        analyze_method_list.append(
            (Method.SIMPLE_CHAIN, methodSimpleChain.analyze))

    # 解析メインループ
    while True:

        # 未確定枡がなくなったら処理終了
        if len(SudokuUtil.find_unfixed_squ_from_flame(wk.flame)) == 0:
            return True

        how_anlz_list: List[HowToAnalyze] = list()

        # 解法リストループ
        for method, analyze_func in analyze_method_list:
            # 解析
            if not analyze_func(wk, how_anlz_list):
                wk.addHistryForErr(how_anlz_list)
                return False
            # 解析結果がない場合は次の解法で解析する
            if len(how_anlz_list) == 0:
                continue

            wk.addHistry(how_anlz_list)
            # エラーチェック
            how_anlz_list_err: List[HowToAnalyze] =\
                simpleErrorCheck.errorCheck(wk)
            if len(how_anlz_list_err) > 0:
                wk.addHistryForErr(how_anlz_list_err)
                return False
            # 解法リストループからbreakし、
            # 最初の解法(消去法)から解析し直す
            break

        # 解析結果がある場合は最初(消去法)から解析し直す
        if len(how_anlz_list) != 0:
            continue

        # 解析結果なし
        break

    return True
示例#15
0
def _find_x_wing(wk: AnalyzeWk, how_anlz_list: List[HowToAnalyze],
                 region: Region) -> None:
    """X-Wing法

    Args:
        wk (AnalyzeWk): ワーク
        how_anlz_list (List[HowToAnalyze]): 解析方法リスト
        region (Region): 領域
    """
    # 行辞書(または列辞書)
    region_dict: Dict[int, List]
    if region == Region.ROW:
        region_dict = wk.row_dict
    else:
        region_dict = wk.clm_dict

    for loop_memo in range(1, 10):
        # X-Wingの対象
        # メモが2個の行(列)を抽出
        two_memo_list: List[List[Square]] = list()
        for squ_list in region_dict.values():
            # 行(列)内のメモを含む枡リストを取得
            # +[1]-------------------------+[2]-------------------------+[3]-------------------------+
            # | 1:1      1:2      1:3      | 1:4      1:5      1:6(*)   | 1:7      1:8      1:9(*)   |
            # | ?        ?        ?        | m=[N,?]  ?        m=[N,?]  | m=[N,?]  ?        m=[N,?]  | <-この行の枡を取得、ただし枡数が2でないので対象外
            # | 2:1      2:2      2:3      | 2:4      2:5      2:6      | 2:7      2:8      2:9      |
            # | ?        ?        val=N    | ?        ?        ?        | ?        ?        ?        |
            # | 3:1      3:2      3:3      | 3:4      3:5      3:6(@)   | 3:7      3:8      3:9($)   |
            # | ?        ?        ?        | ?        ?        m=[N,?]  | ?        ?        m=[N,?]  | <-この行の枡を取得
            # +[4]-------------------------+[5]-------------------------+[6]-------------------------+
            # | 4:1      4:2      4:3      | 4:4      4:5      4:6(*)   | 4:7      4:8      4:9(*)   |
            # | ?        ?        ?        | m=[N,?]  ?        m=[N,?]  | m=[N,?]  ?        m=[N,?]  | <-この行の枡を取得、ただし枡数が2でないので対象外
            # | 5:1      5:2      5:3      | 5:4      5:5      5:6($)   | 5:7      5:8      5:9(@)   |
            # | ?        ?        ?        | ?        ?        m=[N,?]$ | ?        ?        m=[N,?]@ | <-この行の枡を取得
            # | 6:1      6:2      6:3      | 6:4      6:5      6:6      | 6:7      6:8      6:9      |
            # | ?        hint=N   ?        | ?        ?        ?        | ?        ?        ?        |
            # +[7]-------------------------+[8]-------------------------+[9]-------------------------+
            # | 7:1      7:2      7:3      | 7:4      7:5      7:6      | 7:7      7:8      7:9      |
            # | m=[N,?]  ?        ?        | ?        m=[N,?]  ?        | ?        ?        ?        | <-この行の枡を取得
            # | 8:1      8:2      8:3      | 8:4      8:5      8:6      | 8:7      8:8      8:9      |
            # | m=[N,?]  ?        ?        | ?        m=[N,?]  ?        | ?        ?        ?        | <-この行の枡を取得
            # | 9:1      9:2      9:3      | 9:4      9:5      9:6      | 9:7      9:8      9:9      |
            # | ?        ?        ?        | ?        ?        ?        | ?        hint=N   ?        |
            # +----------------------------+----------------------------+----------------------------+
            # loop_memoを含む枡を抽出し、そのメモが2個かどうかを判定
            include_list: List[
                Square] = SudokuUtil.find_squ_include_memo_from_region(
                    squ_list, loop_memo)
            # 行(列)内でメモが2個でないとX-Wingが成立しない
            if len(include_list) != 2:
                continue
            two_memo_list.append(include_list)

        # 発見出来た行(列)が2以下だとそもそもX-Wing対象にならない
        if len(two_memo_list) < 2:
            continue

        # 同一列(行)でグルーピング
        # 以下のような辞書を生成
        # キー:(列、列)
        # 値:[[枡、枡],[枡、枡]]
        region_grouping_dict: Dict[Tuple[int, int],
                                   List[List[Square]]] = dict()
        for include_list in two_memo_list:
            # キー
            region_pair: Tuple[int, int]
            if region == Region.ROW:
                # 行の場合は列でグルーピング
                region_pair = (include_list[0].clm, include_list[1].clm)
            else:
                # 列の場合は行でグルーピング
                region_pair = (include_list[0].row, include_list[1].row)

            # 値
            if region_pair not in region_grouping_dict:
                region_grouping_dict[region_pair] = list()
            region_list: List[List[Square]] = region_grouping_dict[region_pair]
            region_list.append(include_list)

        # グルーピング後、対象行(列)が2つでなければX-Wingの対象外
        for region_pair in list(region_grouping_dict.keys()):
            region_list = region_grouping_dict[region_pair]
            if len(region_list) != 2:
                region_grouping_dict.pop(region_pair)

        # 対象なし
        if len(region_grouping_dict) == 0:
            continue

        # 除外するメモの検索対象辞書
        # 行の場合は列から探し、列の場合は行から探す
        target_dict: Dict[int, List[Square]]
        if region == Region.ROW:
            target_dict = wk.clm_dict
        else:
            target_dict = wk.row_dict

        # 除去するメモを抽出
        for region_pair, region_list in region_grouping_dict.items():

            # X-Wingで見つかった枡
            xwing_squ_list: List[Square] = list()
            for include_list in region_list:
                for squ in include_list:
                    xwing_squ_list.append(squ)

            for region_pos in region_pair:
                # 同一列(行)からメモを含む枡を取得
                # (除外対象枡)
                change_list = SudokuUtil.find_squ_include_memo_from_region(
                    target_dict[region_pos], loop_memo)
                for change_squ in change_list:
                    # 同一枡は消してはいけない
                    if change_squ in xwing_squ_list:
                        continue

                    # メモを除外
                    change_squ.memo_val_list.remove(loop_memo)

                    # 解析方法生成
                    how_anlz: HowToAnalyze = HowToAnalyze(Method.X_WING)
                    how_anlz.region = region
                    how_anlz.changed_squ = change_squ
                    how_anlz.remove_memo_list.append(loop_memo)
                    how_anlz.trigger_squ_list.extend(xwing_squ_list)
                    how_anlz.msg = MsgFactory.how_to_x_wing(
                        how_anlz, region_pair)

                    how_anlz_list.append(how_anlz)

        if len(how_anlz_list) > 0:
            return
def _find_share_squ(wk: AnalyzeWk, squ1: Square, squ2: Square) -> List[Square]:
    """2個の枡の共通枡を検索

    Args:
        wk (AnalyzeWk): 解析WK
        squ1 (Square): 枡1
        squ2 (Square): 枡2

    Returns:
        List[Square]: 共通枡
    """
    share_list: List[Square] = list()
    region_area_level: Region = _which_region_area_level(squ1, squ2)

    # エリアレベルで枡1、枡2が同一行、同一列に存在する場合
    if region_area_level == Region.ROW or\
            region_area_level == Region.CLM:
        region_pos_squ1: int
        region_pos_squ2: int
        if region_area_level == Region.ROW:
            region_pos_squ1 = squ1.row
            region_pos_squ2 = squ2.row
        else:
            region_pos_squ1 = squ1.clm
            region_pos_squ2 = squ2.clm

        # 枡1と枡2が同一行(列)
        if region_pos_squ1 == region_pos_squ2:
            # +[1]-------------------+[2]-------------------+[3]-------------------+
            # | 1:1(1) 1:2(*) 1:3(*) | 1:4(2) 1:5(*) 1:6(*) | 1:7(*) 1:8(*) 1:9(*) |
            # *枡が共通枡になる
            region_squ_list: List[Square]
            if region_area_level == Region.ROW:
                region_squ_list = wk.row_dict[region_pos_squ1]
            else:
                region_squ_list = wk.clm_dict[region_pos_squ1]
            for region_squ in region_squ_list:
                if region_squ == squ1 or region_squ == squ2:
                    continue
                share_list.append(region_squ)

        # 枡1と枡2が別行(列)
        else:
            # +[1]-------------------+[2]-------------------+[3]-------------------+
            # | 1:1(1) 1:2    1:3    | 1:4    1:5    1:6    | 1:7(*) 1:8(*) 1:9(*) |
            # | 2:1    2:2    2:3    | 2:4    2:5    2:6    | 2:7    2:8    2:9    |
            # | 3:1(*) 3:2(*) 3:3(*) | 3:4    3:5    3:6    | 3:7(2) 3:8    3:9    |
            # *枡が共通枡になる

            # 枡1と同一行(列)にある枡を取得
            # +[1]-------------------+[2]-------------------+[3]-------------------+
            # | 1:1(1) 1:2    1:3    | 1:4    1:5    1:6    | 1:7(*) 1:8(*) 1:9(*) |
            #                                                 ^^^^^^ ^^^^^^ ^^^^^^
            region_squ_list1: List[Square]
            if region_area_level == Region.ROW:
                region_squ_list1 = wk.row_dict[squ1.row]
            else:
                region_squ_list1 = wk.clm_dict[squ1.clm]
            for region_squ in region_squ_list1:
                # 枡2と同一エリアにある枡を対象に
                if region_squ == squ1 or\
                        region_squ.area_id != squ2.area_id:
                    continue
                share_list.append(region_squ)

            # 枡2と同一行(列)にある枡を取得
            # | 3:1(*) 3:2(*) 3:3(*) | 3:4    3:5    3:6    | 3:7(2) 3:8    3:9    |
            #   ^^^^^^ ^^^^^^ ^^^^^^
            region_squ_list2: List[Square]
            if region_area_level == Region.ROW:
                region_squ_list2 = wk.row_dict[squ2.row]
            else:
                region_squ_list2 = wk.clm_dict[squ2.clm]
            for region_squ in region_squ_list2:
                # 枡1と同一エリアにある枡を対象に
                if region_squ == squ2 or\
                        region_squ.area_id != squ1.area_id:
                    continue
                share_list.append(region_squ)

    # エリアレベルで枡1、枡2が同一行、同一列に存在しない場合
    else:
        # 交差枡を算出する
        share_list = SudokuUtil.find_cross_squ(wk, squ1, squ2)

    return share_list
def analyze(wk: AnalyzeWk, how_anlz_list: List[HowToAnalyze]) -> bool:
    """シンプルチェーン

    1つの数字に注目して

    枡 =強= 枡 =弱= 枡 =強= 枡 =弱= 枡 =強= 枡

    のように強リンクではじまって弱リンク、強リンク...の組み合わせが続き、
    強リンクで終わるパターンで最初と最後の交差枡にはその数字は入らないという解法
    ※弱リンクの部分は強リンクになっても可
    ※強リンク、弱リンクの解説はLinkType.pyのdocstringを参照

    例>
    +[1]-------------------------+[2]-------------------------+[3]-------------------------+
    | 1:1      1:2      1:3      | 1:4      1:5      1:6      | 1:7      1:8      1:9      |
    | ?        ?        ?        | ?        ?        ?        | ?        ?        hint=N   |
    | 2:1      2:2      2:3      | 2:4      2:5      2:6      | 2:7      2:8      2:9      |
    | ?        ?        ?        | ?        ?        hint=N   | ?        ?        ?        |
    | 3:1      3:2      3:3      | 3:4      3:5      3:6      | 3:7      3:8      3:9      |
    | hint=N   ?        ?        | ?        ?        ?        | ?        ?        ?        |
    +[4]-------------------------+[5]-------------------------+[6]-------------------------+
    | 4:1      4:2      4:3      | 4:4      4:5(#)   4:6      | 4:7      4:8      4:9      |
    | ?        ?        m=[N,?]  | ?        m=[N,?]  ?        | ?        ?        ?        |
    | 5:1      5:2      5:3      | 5:4      5:5      5:6      | 5:7      5:8      5:9      |
    | ?        ?        ?        | ?        ?        ?        | hint=N   ?        ?        |
    | 6:1      6:2(*)   6:3      | 6:4(@)   6:5      6:6      | 6:7      6:8      6:9      |
    | ?        m=[N,?]  m=[N,?]  | m=[N,?]  ?        ?        | ?        ?        ?        |
    +[7]-------------------------+[8]-------------------------+[9]-------------------------+
    | 7:1      7:2      7:3      | 7:4      7:5      7:6      | 7:7      7:8      7:9      |
    | ?        m=[N,?]  m=[N,?]  | ?        ?        ?        | ?        ?        ?        |
    | 8:1      8:2      8:3      | 8:4      8:5      8:6      | 8:7      8:8      8:9      |
    | ?        ?        ?        | m=[N,?]  m=[N,?]  ?        | ?        hint=N   ?        |
    | 9:1      9:2(+)   9:3      | 9:4      9:5(!)   9:6      | 9:7      9:8      9:9      |
    | ?        m=[N,?]  ?        | ?        m=[N,?]  ?        | ?        ?        ?        |
    +----------------------------+----------------------------+----------------------------+
    ※8:8がNで確定している、上記のような状態の枠は本処理には来ないが、、、説明用に目をつぶる
    数字Nで以下のチェーンが形成されている
    @枡 =強= #枡 =弱= !枡 =強= +枡
    チェーンの最初の@枡と最後の+枡の交差枡(*枡)からNを除外することが出来る。

    検証>
    上記例に当てはめて交差枡*がNで確定すると仮定
    ・@枡からNが除外される
        @枡 =強= #枡 =弱= !枡 =強= +枡
        ^^^
    ・#枡がNで確定する
        @枡 =強= #枡 =弱= !枡 =強= +枡
                ^^^
    ・!枡からNが除外される
        @枡 =強= #枡 =弱= !枡 =強= +枡
                         ^^^
    ・+枡がNで確定する
        @枡 =強= #枡 =弱= !枡 =強= +枡
                                  ^^^
    +[1]-------------------------+[2]-------------------------+[3]-------------------------+
    | 1:1      1:2      1:3      | 1:4      1:5      1:6      | 1:7      1:8      1:9      |
    | ?        ?        ?        | ?        ?        ?        | ?        ?        hint=N   |
    | 2:1      2:2      2:3      | 2:4      2:5      2:6      | 2:7      2:8      2:9      |
    | ?        ?        ?        | ?        ?        hint=N   | ?        ?        ?        |
    | 3:1      3:2      3:3      | 3:4      3:5      3:6      | 3:7      3:8      3:9      |
    | hint=N   ?        ?        | ?        ?        ?        | ?        ?        ?        |
    +[4]-------------------------+[5]-------------------------+[6]-------------------------+
    | 4:1      4:2      4:3      | 4:4      4:5(#)   4:6      | 4:7      4:8      4:9      |
    | ?        ?        m=[?]    | ?        v=N      ?        | ?        ?        ?        |
    | 5:1      5:2      5:3      | 5:4      5:5      5:6      | 5:7      5:8      5:9      |
    | ?        ?        ?        | ?        ?        ?        | hint=N   ?        ?        |
    | 6:1      6:2(*)   6:3      | 6:4(@)   6:5      6:6      | 6:7      6:8      6:9      |
    | ?        v=N      m=[?]    | m=[?]    ?        ?        | ?        ?        ?        |
    +[7]-------------------------+[8]-------------------------+[9]-------------------------+
    | 7:1      7:2      7:3      | 7:4      7:5      7:6      | 7:7      7:8      7:9      |
    | ?        m=[?]    m=[N,?]  | ?        ?        ?        | ?        ?        ?        |
    | 8:1      8:2      8:3      | 8:4      8:5      8:6      | 8:7      8:8      8:9      |
    | ?        ?        ?        | m=[N,?]  m=[?]    ?        | ?        hint=N   ?        |
    | 9:1      9:2(+)   9:3      | 9:4      9:5(!)   9:6      | 9:7      9:8      9:9      |
    | ?        v=N      ?        | ?        m=[?]    ?        | ?        ?        ?        |
    +----------------------------+----------------------------+----------------------------+
    2列目でNが2つ出てくるため矛盾が生じる。

    Args:
        wk (AnalyzeWk): ワーク
        how_anlz_list (List[HowToAnalyze]): 解析方法

    Returns:
        bool: エラーの場合にFalse
    """

    for loop_memo in range(1, 10):
        # チェーンを作成
        all_chain_list: List[List[Chain]] =\
            _create_simple_chain(wk, loop_memo)
        for chain_list in all_chain_list:
            first_squ = chain_list[0].squ
            last_squ = chain_list[len(chain_list) - 1].squ

            # 交差枡取得
            cross_squ_list: List[Square] = SudokuUtil.find_cross_squ(
                wk, first_squ, last_squ)

            # 交差枡が変更対象かどうか判定
            change_squ_list: List[Square] = list()
            for cross_squ in cross_squ_list:
                # 未確定かつメモが存在する枡が対象となる
                if cross_squ.get_fixed_val() is None and\
                        loop_memo in cross_squ.memo_val_list:
                    change_squ_list.append(cross_squ)

            # 対象なし
            # ⇒次のチェーンをチェック
            if len(change_squ_list) == 0:
                continue

            # 対象あり
            chain_squ_list: List[Square] = list()
            for chain in chain_list:
                chain_squ_list.append(chain.squ)

            for change_squ in change_squ_list:

                # メモを除外
                change_squ.memo_val_list.remove(loop_memo)

                # 解析方法生成
                how_anlz: HowToAnalyze = HowToAnalyze(Method.SIMPLE_CHAIN)
                how_anlz.changed_squ = change_squ
                how_anlz.remove_memo_list.append(loop_memo)
                how_anlz.trigger_squ_list.extend(chain_squ_list)
                how_anlz.chain_squ_list.extend(chain_squ_list)
                how_anlz.msg = MsgFactory.how_to_simple_chain(how_anlz)

                how_anlz_list.append(how_anlz)

            return True

    return True