예제 #1
0
    def test_penchan_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        # 1-2-... wait
        tiles = self._string_to_136_array(sou="12456", man="123456", pin="55")
        win_tile = self._string_to_136_tile(sou="3")
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        fu_details, fu = fu_calculator.calculate_fu(
            hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({"fu": 30, "reason": FuCalculator.BASE} in fu_details)
        self.assertTrue({
            "fu": 2,
            "reason": FuCalculator.PENCHAN
        } in fu_details)
        self.assertEqual(fu, 40)

        # ...-8-9 wait
        tiles = self._string_to_136_array(sou="34589", man="123456", pin="55")
        win_tile = self._string_to_136_tile(sou="7")
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        fu_details, fu = fu_calculator.calculate_fu(
            hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({"fu": 30, "reason": FuCalculator.BASE} in fu_details)
        self.assertTrue({
            "fu": 2,
            "reason": FuCalculator.PENCHAN
        } in fu_details)
        self.assertEqual(fu, 40)
    def test_valued_pair_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(sou='12378', man='123456', honors='11')
        win_tile = self._string_to_136_tile(sou='6')
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        valued_tiles = [EAST]
        fu_details, fu = fu_calculator.calculate_fu(hand,
                                                    win_tile,
                                                    self._get_win_group(hand, win_tile),
                                                    config,
                                                    valued_tiles=valued_tiles,)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({'fu': 30, 'reason': FuCalculator.BASE} in fu_details)
        self.assertTrue({'fu': 2, 'reason': FuCalculator.VALUED_PAIR} in fu_details)
        self.assertEqual(fu, 40)

        # double valued pair
        valued_tiles = [EAST, EAST]
        fu_details, fu = fu_calculator.calculate_fu(hand,
                                                    win_tile,
                                                    self._get_win_group(hand, win_tile),
                                                    config,
                                                    valued_tiles=valued_tiles)
        self.assertEqual(3, len(fu_details))
        self.assertTrue({'fu': 30, 'reason': FuCalculator.BASE} in fu_details)
        self.assertTrue({'fu': 2, 'reason': FuCalculator.VALUED_PAIR} in fu_details)
        self.assertEqual(fu, 40)
    def test_penchan_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        # 1-2-... wait
        tiles = self._string_to_136_array(sou='12456', man='123456', pin='55')
        win_tile = self._string_to_136_tile(sou='3')
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        fu_details, fu = fu_calculator.calculate_fu(hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({'fu': 30, 'reason': FuCalculator.BASE} in fu_details)
        self.assertTrue({'fu': 2, 'reason': FuCalculator.PENCHAN} in fu_details)
        self.assertEqual(fu, 40)

        # ...-8-9 wait
        tiles = self._string_to_136_array(sou='34589', man='123456', pin='55')
        win_tile = self._string_to_136_tile(sou='7')
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        fu_details, fu = fu_calculator.calculate_fu(hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({'fu': 30, 'reason': FuCalculator.BASE} in fu_details)
        self.assertTrue({'fu': 2, 'reason': FuCalculator.PENCHAN} in fu_details)
        self.assertEqual(fu, 40)
예제 #4
0
    def test_closed_terminal_pon_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(sou="11178", man="123456", pin="11")
        win_tile = self._string_to_136_tile(sou="6")
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        fu_details, fu = fu_calculator.calculate_fu(
            hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({"fu": 30, "reason": FuCalculator.BASE} in fu_details)
        self.assertTrue({
            "fu": 8,
            "reason": FuCalculator.CLOSED_TERMINAL_PON
        } in fu_details)
        self.assertEqual(fu, 40)

        # when we ron on the third pon tile we consider pon as open
        tiles = self._string_to_136_array(sou="11678", man="123456", pin="11")
        win_tile = self._string_to_136_tile(sou="1")
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        fu_details, fu = fu_calculator.calculate_fu(
            hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({"fu": 30, "reason": FuCalculator.BASE} in fu_details)
        self.assertTrue({
            "fu": 4,
            "reason": FuCalculator.OPEN_TERMINAL_PON
        } in fu_details)
        self.assertEqual(fu, 40)
예제 #5
0
    def test_closed_pon_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(sou='22278', man='123456', pin='11')
        win_tile = self._string_to_136_tile(sou='6')
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        fu_details, fu = fu_calculator.calculate_fu(
            hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({'fu': 30, 'reason': FuCalculator.BASE} in fu_details)
        self.assertTrue({
            'fu': 4,
            'reason': FuCalculator.CLOSED_PON
        } in fu_details)
        self.assertEqual(fu, 40)

        # when we ron on the third pon tile we consider pon as open
        tiles = self._string_to_136_array(sou='22678', man='123456', pin='11')
        win_tile = self._string_to_136_tile(sou='2')
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        fu_details, fu = fu_calculator.calculate_fu(
            hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({'fu': 30, 'reason': FuCalculator.BASE} in fu_details)
        self.assertTrue({
            'fu': 2,
            'reason': FuCalculator.OPEN_PON
        } in fu_details)
        self.assertEqual(fu, 40)
    def test_tsumo_hand_base(self):
        fu_calculator = FuCalculator()
        config = HandConfig(is_tsumo=True)

        tiles = self._string_to_136_array(sou='22278', man='123456', pin='11')
        win_tile = self._string_to_136_tile(sou='6')
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        fu_details, _ = fu_calculator.calculate_fu(hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertTrue({'fu': 20, 'reason': FuCalculator.BASE} in fu_details)
예제 #7
0
    def test_tsumo_hand_base(self):
        fu_calculator = FuCalculator()
        config = HandConfig(is_tsumo=True)

        tiles = self._string_to_136_array(sou="22278", man="123456", pin="11")
        win_tile = self._string_to_136_tile(sou="6")
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        fu_details, _ = fu_calculator.calculate_fu(
            hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertTrue({"fu": 20, "reason": FuCalculator.BASE} in fu_details)
    def test_pair_wait_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(sou='123678', man='123456', pin='1')
        win_tile = self._string_to_136_tile(pin='1')
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        fu_details, fu = fu_calculator.calculate_fu(hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({'fu': 30, 'reason': FuCalculator.BASE} in fu_details)
        self.assertTrue({'fu': 2, 'reason': FuCalculator.PAIR_WAIT} in fu_details)
        self.assertEqual(fu, 40)
    def test_tsumo_hand_and_disabled_pinfu(self):
        fu_calculator = FuCalculator()
        config = HandConfig(is_tsumo=True, options=OptionalRules(fu_for_pinfu_tsumo=True))

        tiles = self._string_to_136_array(sou='2278', man='123456', pin='123')
        win_tile = self._string_to_136_tile(sou='6')
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        fu_details, fu = fu_calculator.calculate_fu(hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({'fu': 20, 'reason': FuCalculator.BASE} in fu_details)
        self.assertTrue({'fu': 2, 'reason': FuCalculator.TSUMO} in fu_details)
        self.assertEqual(fu, 30)
예제 #10
0
    def test_open_terminal_kan_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(sou='2278', man='123456', pin='111')
        win_tile = self._string_to_136_tile(sou='6')
        hand = self._hand(self._to_34_array(tiles + [win_tile]))
        melds = [self._make_meld(Meld.KAN, pin='111', is_open=True)]

        fu_details, fu = fu_calculator.calculate_fu(hand, win_tile, self._get_win_group(hand, win_tile), config,
                                                    melds=melds)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({'fu': 20, 'reason': FuCalculator.BASE} in fu_details)
        self.assertTrue({'fu': 16, 'reason': FuCalculator.OPEN_TERMINAL_KAN} in fu_details)
        self.assertEqual(fu, 40)
예제 #11
0
    def test_open_hand_base(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(sou='22278', man='123456', pin='11')
        win_tile = self._string_to_136_tile(sou='6')
        hand = self._hand(self._to_34_array(tiles + [win_tile]))
        melds = [self._make_meld(Meld.PON, sou='222')]

        fu_details, fu = fu_calculator.calculate_fu(hand, win_tile, self._get_win_group(hand, win_tile), config,
                                                    melds=melds)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({'fu': 20, 'reason': FuCalculator.BASE} in fu_details)
        self.assertTrue({'fu': 2, 'reason': FuCalculator.OPEN_PON} in fu_details)
        self.assertEqual(fu, 30)
예제 #12
0
    def test_open_hand_withou_additional_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(sou='23478', man='234567', pin='22')
        win_tile = self._string_to_136_tile(sou='6')
        hand = self._hand(self._to_34_array(tiles + [win_tile]))
        melds = [self._make_meld(Meld.CHI, sou='234')]

        config = HandConfig(options=OptionalRules(fu_for_open_pinfu=False))
        fu_details, fu = fu_calculator.calculate_fu(hand, win_tile, self._get_win_group(hand, win_tile), config,
                                                    melds=melds)
        self.assertEqual(1, len(fu_details))
        self.assertTrue({'fu': 20, 'reason': FuCalculator.BASE} in fu_details)
        self.assertEqual(fu, 20)
예제 #13
0
    def test_chitoitsu_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(sou='112244', man='115599', pin='6')
        win_tile = self._string_to_136_tile(pin='6')
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        fu_details, fu = fu_calculator.calculate_fu(hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertEqual(1, len(fu_details))
        self.assertTrue({'fu': 25, 'reason': FuCalculator.BASE} in fu_details)
        self.assertEqual(fu, 25)

        fu_details, fu = fu_calculator.calculate_fu(hand, win_tile, self._get_win_group(hand, win_tile), config)
        self.assertEqual(1, len(fu_details))
        self.assertTrue({'fu': 25, 'reason': FuCalculator.BASE} in fu_details)
        self.assertEqual(fu, 25)
예제 #14
0
    def test_fu_based_on_win_group(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(man='234789', pin='1234566')
        win_tile = self._string_to_136_tile(pin='6')
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        win_groups = [x for x in hand if win_tile // 4 in x]

        # pinfu wait 4-5-6
        fu_details, fu = fu_calculator.calculate_fu(hand, win_tile, win_groups[0], config)
        self.assertEqual(fu, 30)

        # pair wait 66
        fu_details, fu = fu_calculator.calculate_fu(hand, win_tile, win_groups[1], config)
        self.assertEqual(fu, 40)
예제 #15
0
    def test_valued_pair_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(sou="12378",
                                          man="123456",
                                          honors="11")
        win_tile = self._string_to_136_tile(sou="6")
        hand = self._hand(self._to_34_array(tiles + [win_tile]))

        valued_tiles = [EAST]
        fu_details, fu = fu_calculator.calculate_fu(
            hand,
            win_tile,
            self._get_win_group(hand, win_tile),
            config,
            valued_tiles=valued_tiles,
        )
        self.assertEqual(2, len(fu_details))
        self.assertTrue({"fu": 30, "reason": FuCalculator.BASE} in fu_details)
        self.assertTrue({
            "fu": 2,
            "reason": FuCalculator.VALUED_PAIR
        } in fu_details)
        self.assertEqual(fu, 40)

        # double valued pair
        valued_tiles = [EAST, EAST]
        fu_details, fu = fu_calculator.calculate_fu(hand,
                                                    win_tile,
                                                    self._get_win_group(
                                                        hand, win_tile),
                                                    config,
                                                    valued_tiles=valued_tiles)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({"fu": 30, "reason": FuCalculator.BASE} in fu_details)
        self.assertTrue({
            "fu": 4,
            "reason": FuCalculator.DOUBLE_VALUED_PAIR
        } in fu_details)
        self.assertEqual(fu, 40)
예제 #16
0
    def test_open_hand_without_additional_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(sou="23478", man="234567", pin="22")
        win_tile = self._string_to_136_tile(sou="6")
        hand = self._hand(self._to_34_array(tiles + [win_tile]))
        melds = [self._make_meld(Meld.CHI, sou="234")]

        fu_details, fu = fu_calculator.calculate_fu(hand,
                                                    win_tile,
                                                    self._get_win_group(
                                                        hand, win_tile),
                                                    config,
                                                    melds=melds)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({"fu": 20, "reason": FuCalculator.BASE} in fu_details)
        self.assertTrue({
            "fu": 2,
            "reason": FuCalculator.HAND_WITHOUT_FU
        } in fu_details)
        self.assertEqual(fu, 30)
예제 #17
0
    def test_closed_terminal_kan_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(sou="2278", man="123456", pin="111")
        win_tile = self._string_to_136_tile(sou="6")
        hand = self._hand(self._to_34_array(tiles + [win_tile]))
        melds = [self._make_meld(Meld.KAN, pin="111", is_open=False)]

        fu_details, fu = fu_calculator.calculate_fu(hand,
                                                    win_tile,
                                                    self._get_win_group(
                                                        hand, win_tile),
                                                    config,
                                                    melds=melds)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({"fu": 30, "reason": FuCalculator.BASE} in fu_details)
        self.assertTrue({
            "fu": 32,
            "reason": FuCalculator.CLOSED_TERMINAL_KAN
        } in fu_details)
        self.assertEqual(fu, 70)
예제 #18
0
    def test_open_kan_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(sou="22278", man="123456", pin="11")
        win_tile = self._string_to_136_tile(sou="6")
        hand = self._hand(self._to_34_array(tiles + [win_tile]))
        melds = [self._make_meld(Meld.KAN, sou="222", is_open=True)]

        fu_details, fu = fu_calculator.calculate_fu(hand,
                                                    win_tile,
                                                    self._get_win_group(
                                                        hand, win_tile),
                                                    config,
                                                    melds=melds)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({"fu": 20, "reason": FuCalculator.BASE} in fu_details)
        self.assertTrue({
            "fu": 8,
            "reason": FuCalculator.OPEN_KAN
        } in fu_details)
        self.assertEqual(fu, 30)
예제 #19
0
    def test_open_terminal_pon_fu(self):
        fu_calculator = FuCalculator()
        config = HandConfig()

        tiles = self._string_to_136_array(sou="2278",
                                          man="123456",
                                          honors="111")
        win_tile = self._string_to_136_tile(sou="6")
        hand = self._hand(self._to_34_array(tiles + [win_tile]))
        melds = [self._make_meld(Meld.PON, honors="111")]

        fu_details, fu = fu_calculator.calculate_fu(hand,
                                                    win_tile,
                                                    self._get_win_group(
                                                        hand, win_tile),
                                                    config,
                                                    melds=melds)
        self.assertEqual(2, len(fu_details))
        self.assertTrue({"fu": 20, "reason": FuCalculator.BASE} in fu_details)
        self.assertTrue({
            "fu": 4,
            "reason": FuCalculator.OPEN_TERMINAL_PON
        } in fu_details)
        self.assertEqual(fu, 30)
예제 #20
0
    def estimate_hand_value(
        self,
        tiles,
        win_tile,
        melds=None,
        dora_indicators=None,
        config=None,
        scores_calculator_factory=ScoresCalculator,
        use_hand_divider_cache=False,
    ):
        """
        :param tiles: array with 14 tiles in 136-tile format
        :param win_tile: 136 format tile that caused win (ron or tsumo)
        :param melds: array with Meld objects
        :param dora_indicators: array of tiles in 136-tile format
        :param config: HandConfig object
        :param use_hand_divider_cache: could be useful if you are calculating a lot of menchin hands
        :return: HandResponse object
        """

        if not melds:
            melds = []

        if not dora_indicators:
            dora_indicators = []

        self.config = config or HandConfig()

        agari = Agari()
        hand_yaku = []
        scores_calculator = scores_calculator_factory()
        tiles_34 = TilesConverter.to_34_array(tiles)

        fu_calculator = FuCalculator()
        is_aotenjou = isinstance(scores_calculator, Aotenjou)

        opened_melds = [x.tiles_34 for x in melds if x.opened]
        all_melds = [x.tiles_34 for x in melds]
        is_open_hand = len(opened_melds) > 0

        # special situation
        if self.config.is_nagashi_mangan:
            hand_yaku.append(self.config.yaku.nagashi_mangan)
            fu = 30
            han = self.config.yaku.nagashi_mangan.han_closed
            cost = scores_calculator.calculate_scores(han, fu, self.config,
                                                      False)
            return HandResponse(cost, han, fu, hand_yaku)

        if win_tile not in tiles:
            return HandResponse(error=HandCalculator.ERR_NO_WINNING_TILE)

        if self.config.is_riichi and not self.config.is_daburu_riichi and is_open_hand:
            return HandResponse(error=HandCalculator.ERR_OPEN_HAND_RIICHI)

        if self.config.is_daburu_riichi and is_open_hand:
            return HandResponse(error=HandCalculator.ERR_OPEN_HAND_DABURI)

        if self.config.is_ippatsu and not self.config.is_riichi and not self.config.is_daburu_riichi:
            return HandResponse(
                error=HandCalculator.ERR_IPPATSU_WITHOUT_RIICHI)

        if self.config.is_chankan and self.config.is_tsumo:
            return HandResponse(error=HandCalculator.ERR_CHANKAN_WITH_TSUMO)

        if self.config.is_rinshan and not self.config.is_tsumo:
            return HandResponse(error=HandCalculator.ERR_RINSHAN_WITHOUT_TSUMO)

        if self.config.is_haitei and not self.config.is_tsumo:
            return HandResponse(error=HandCalculator.ERR_HAITEI_WITHOUT_TSUMO)

        if self.config.is_houtei and self.config.is_tsumo:
            return HandResponse(error=HandCalculator.ERR_HOUTEI_WITH_TSUMO)

        if self.config.is_haitei and self.config.is_rinshan:
            return HandResponse(error=HandCalculator.ERR_HAITEI_WITH_RINSHAN)

        if self.config.is_houtei and self.config.is_chankan:
            return HandResponse(error=HandCalculator.ERR_HOUTEI_WITH_CHANKAN)

        # raise error only when player wind is defined (and is *not* EAST)
        if self.config.is_tenhou and self.config.player_wind and not self.config.is_dealer:
            return HandResponse(error=HandCalculator.ERR_TENHOU_NOT_AS_DEALER)

        if self.config.is_tenhou and not self.config.is_tsumo:
            return HandResponse(error=HandCalculator.ERR_TENHOU_WITHOUT_TSUMO)

        if self.config.is_tenhou and melds:
            return HandResponse(error=HandCalculator.ERR_TENHOU_WITH_MELD)

        # raise error only when player wind is defined (and is EAST)
        if self.config.is_chiihou and self.config.player_wind and self.config.is_dealer:
            return HandResponse(error=HandCalculator.ERR_CHIIHOU_AS_DEALER)

        if self.config.is_chiihou and not self.config.is_tsumo:
            return HandResponse(error=HandCalculator.ERR_CHIIHOU_WITHOUT_TSUMO)

        if self.config.is_chiihou and melds:
            return HandResponse(error=HandCalculator.ERR_CHIIHOU_WITH_MELD)

        # raise error only when player wind is defined (and is EAST)
        if self.config.is_renhou and self.config.player_wind and self.config.is_dealer:
            return HandResponse(error=HandCalculator.ERR_RENHOU_AS_DEALER)

        if self.config.is_renhou and self.config.is_tsumo:
            return HandResponse(error=HandCalculator.ERR_RENHOU_WITH_TSUMO)

        if self.config.is_renhou and melds:
            return HandResponse(error=HandCalculator.ERR_RENHOU_WITH_MELD)

        if not agari.is_agari(tiles_34, all_melds):
            return HandResponse(error=HandCalculator.ERR_HAND_NOT_WINNING)

        if not self.config.options.has_double_yakuman:
            self.config.yaku.daburu_kokushi.han_closed = 13
            self.config.yaku.suuankou_tanki.han_closed = 13
            self.config.yaku.daburu_chuuren_poutou.han_closed = 13
            self.config.yaku.daisuushi.han_closed = 13
            self.config.yaku.daisuushi.han_open = 13

        hand_options = self.divider.divide_hand(
            tiles_34, melds, use_cache=use_hand_divider_cache)

        calculated_hands = []
        for hand in hand_options:
            is_chiitoitsu = self.config.yaku.chiitoitsu.is_condition_met(hand)
            valued_tiles = [
                HAKU, HATSU, CHUN, self.config.player_wind,
                self.config.round_wind
            ]

            win_groups = self._find_win_groups(win_tile, hand, opened_melds)
            for win_group in win_groups:
                cost = None
                error = None
                hand_yaku = []
                han = 0

                fu_details, fu = fu_calculator.calculate_fu(
                    hand, win_tile, win_group, self.config, valued_tiles,
                    melds)

                is_pinfu = len(
                    fu_details) == 1 and not is_chiitoitsu and not is_open_hand

                pon_sets = [x for x in hand if is_pon(x)]
                kan_sets = [x for x in hand if is_kan(x)]
                chi_sets = [x for x in hand if is_chi(x)]

                if self.config.is_tsumo:
                    if not is_open_hand:
                        hand_yaku.append(self.config.yaku.tsumo)

                if is_pinfu:
                    hand_yaku.append(self.config.yaku.pinfu)

                # let's skip hand that looks like chitoitsu, but it contains open sets
                if is_chiitoitsu and is_open_hand:
                    continue

                if is_chiitoitsu:
                    hand_yaku.append(self.config.yaku.chiitoitsu)

                is_daisharin = self.config.yaku.daisharin.is_condition_met(
                    hand, self.config.options.has_daisharin_other_suits)
                if self.config.options.has_daisharin and is_daisharin:
                    self.config.yaku.daisharin.rename(hand)
                    hand_yaku.append(self.config.yaku.daisharin)

                if self.config.options.has_daichisei and self.config.yaku.daichisei.is_condition_met(
                        hand):
                    hand_yaku.append(self.config.yaku.daichisei)

                is_tanyao = self.config.yaku.tanyao.is_condition_met(hand)
                if is_open_hand and not self.config.options.has_open_tanyao:
                    is_tanyao = False

                if is_tanyao:
                    hand_yaku.append(self.config.yaku.tanyao)

                if self.config.is_riichi and not self.config.is_daburu_riichi:
                    if self.config.is_open_riichi:
                        hand_yaku.append(self.config.yaku.open_riichi)
                    else:
                        hand_yaku.append(self.config.yaku.riichi)

                if self.config.is_daburu_riichi:
                    if self.config.is_open_riichi:
                        hand_yaku.append(self.config.yaku.daburu_open_riichi)
                    else:
                        hand_yaku.append(self.config.yaku.daburu_riichi)

                if (not self.config.is_tsumo
                        and self.config.options.has_sashikomi_yakuman and
                    ((self.config.yaku.daburu_open_riichi in hand_yaku) or
                     (self.config.yaku.open_riichi in hand_yaku))):
                    hand_yaku.append(self.config.yaku.sashikomi)

                if self.config.is_ippatsu:
                    hand_yaku.append(self.config.yaku.ippatsu)

                if self.config.is_rinshan:
                    hand_yaku.append(self.config.yaku.rinshan)

                if self.config.is_chankan:
                    hand_yaku.append(self.config.yaku.chankan)

                if self.config.is_haitei:
                    hand_yaku.append(self.config.yaku.haitei)

                if self.config.is_houtei:
                    hand_yaku.append(self.config.yaku.houtei)

                if self.config.is_renhou:
                    if self.config.options.renhou_as_yakuman:
                        hand_yaku.append(self.config.yaku.renhou_yakuman)
                    else:
                        hand_yaku.append(self.config.yaku.renhou)

                if self.config.is_tenhou:
                    hand_yaku.append(self.config.yaku.tenhou)

                if self.config.is_chiihou:
                    hand_yaku.append(self.config.yaku.chiihou)

                if self.config.yaku.honitsu.is_condition_met(hand):
                    hand_yaku.append(self.config.yaku.honitsu)

                if self.config.yaku.chinitsu.is_condition_met(hand):
                    hand_yaku.append(self.config.yaku.chinitsu)

                if self.config.yaku.tsuisou.is_condition_met(hand):
                    hand_yaku.append(self.config.yaku.tsuisou)

                if self.config.yaku.honroto.is_condition_met(hand):
                    hand_yaku.append(self.config.yaku.honroto)

                if self.config.yaku.chinroto.is_condition_met(hand):
                    hand_yaku.append(self.config.yaku.chinroto)

                if self.config.yaku.ryuisou.is_condition_met(hand):
                    hand_yaku.append(self.config.yaku.ryuisou)

                if self.config.paarenchan > 0 and not self.config.options.paarenchan_needs_yaku:
                    # if no yaku is even needed to win on paarenchan and it is paarenchan condition, just add paarenchan
                    self.config.yaku.paarenchan.set_paarenchan_count(
                        self.config.paarenchan)
                    hand_yaku.append(self.config.yaku.paarenchan)

                # small optimization, try to detect yaku with chi required sets only if we have chi sets in hand
                if len(chi_sets):
                    if self.config.yaku.chantai.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.chantai)

                    if self.config.yaku.junchan.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.junchan)

                    if self.config.yaku.ittsu.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.ittsu)

                    if not is_open_hand:
                        if self.config.yaku.ryanpeiko.is_condition_met(hand):
                            hand_yaku.append(self.config.yaku.ryanpeiko)
                        elif self.config.yaku.iipeiko.is_condition_met(hand):
                            hand_yaku.append(self.config.yaku.iipeiko)

                    if self.config.yaku.sanshoku.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.sanshoku)

                # small optimization, try to detect yaku with pon required sets only if we have pon sets in hand
                if len(pon_sets) or len(kan_sets):
                    if self.config.yaku.toitoi.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.toitoi)

                    if self.config.yaku.sanankou.is_condition_met(
                            hand, win_tile, melds, self.config.is_tsumo):
                        hand_yaku.append(self.config.yaku.sanankou)

                    if self.config.yaku.sanshoku_douko.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.sanshoku_douko)

                    if self.config.yaku.shosangen.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.shosangen)

                    if self.config.yaku.haku.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.haku)

                    if self.config.yaku.hatsu.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.hatsu)

                    if self.config.yaku.chun.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.chun)

                    if self.config.yaku.east.is_condition_met(
                            hand, self.config.player_wind,
                            self.config.round_wind):
                        if self.config.player_wind == EAST:
                            hand_yaku.append(self.config.yaku.yakuhai_place)

                        if self.config.round_wind == EAST:
                            hand_yaku.append(self.config.yaku.yakuhai_round)

                    if self.config.yaku.south.is_condition_met(
                            hand, self.config.player_wind,
                            self.config.round_wind):
                        if self.config.player_wind == SOUTH:
                            hand_yaku.append(self.config.yaku.yakuhai_place)

                        if self.config.round_wind == SOUTH:
                            hand_yaku.append(self.config.yaku.yakuhai_round)

                    if self.config.yaku.west.is_condition_met(
                            hand, self.config.player_wind,
                            self.config.round_wind):
                        if self.config.player_wind == WEST:
                            hand_yaku.append(self.config.yaku.yakuhai_place)

                        if self.config.round_wind == WEST:
                            hand_yaku.append(self.config.yaku.yakuhai_round)

                    if self.config.yaku.north.is_condition_met(
                            hand, self.config.player_wind,
                            self.config.round_wind):
                        if self.config.player_wind == NORTH:
                            hand_yaku.append(self.config.yaku.yakuhai_place)

                        if self.config.round_wind == NORTH:
                            hand_yaku.append(self.config.yaku.yakuhai_round)

                    if self.config.yaku.daisangen.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.daisangen)

                    if self.config.yaku.shosuushi.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.shosuushi)

                    if self.config.yaku.daisuushi.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.daisuushi)

                    # closed kan can't be used in chuuren_poutou
                    if not len(
                            melds
                    ) and self.config.yaku.chuuren_poutou.is_condition_met(
                            hand):
                        if tiles_34[win_tile // 4] == 2 or tiles_34[win_tile //
                                                                    4] == 4:
                            hand_yaku.append(
                                self.config.yaku.daburu_chuuren_poutou)
                        else:
                            hand_yaku.append(self.config.yaku.chuuren_poutou)

                    if not is_open_hand and self.config.yaku.suuankou.is_condition_met(
                            hand, win_tile, self.config.is_tsumo):
                        if tiles_34[win_tile // 4] == 2:
                            hand_yaku.append(self.config.yaku.suuankou_tanki)
                        else:
                            hand_yaku.append(self.config.yaku.suuankou)

                    if self.config.yaku.sankantsu.is_condition_met(
                            hand, melds):
                        hand_yaku.append(self.config.yaku.sankantsu)

                    if self.config.yaku.suukantsu.is_condition_met(
                            hand, melds):
                        hand_yaku.append(self.config.yaku.suukantsu)

                if self.config.paarenchan > 0 and self.config.options.paarenchan_needs_yaku and len(
                        hand_yaku) > 0:
                    # we waited until here to add paarenchan yakuman only if there is any other yaku
                    self.config.yaku.paarenchan.set_paarenchan_count(
                        self.config.paarenchan)
                    hand_yaku.append(self.config.yaku.paarenchan)

                # yakuman is not connected with other yaku
                yakuman_list = [x for x in hand_yaku if x.is_yakuman]
                if yakuman_list:
                    if not is_aotenjou:
                        hand_yaku = yakuman_list
                    else:
                        scores_calculator.aotenjou_filter_yaku(
                            hand_yaku, self.config)
                        yakuman_list = []

                # calculate han
                for item in hand_yaku:
                    if is_open_hand and item.han_open:
                        han += item.han_open
                    else:
                        han += item.han_closed

                if han == 0:
                    error = HandCalculator.ERR_NO_YAKU
                    cost = None

                # we don't need to add dora to yakuman
                if not yakuman_list:
                    tiles_for_dora = tiles[:]

                    count_of_dora = 0
                    count_of_aka_dora = 0

                    for tile in tiles_for_dora:
                        count_of_dora += plus_dora(tile, dora_indicators)

                    for tile in tiles_for_dora:
                        if is_aka_dora(tile, self.config.options.has_aka_dora):
                            count_of_aka_dora += 1

                    if count_of_dora:
                        self.config.yaku.dora.han_open = count_of_dora
                        self.config.yaku.dora.han_closed = count_of_dora
                        hand_yaku.append(self.config.yaku.dora)
                        han += count_of_dora

                    if count_of_aka_dora:
                        self.config.yaku.aka_dora.han_open = count_of_aka_dora
                        self.config.yaku.aka_dora.han_closed = count_of_aka_dora
                        hand_yaku.append(self.config.yaku.aka_dora)
                        han += count_of_aka_dora

                if not is_aotenjou and (
                        self.config.options.limit_to_sextuple_yakuman
                        and han > 78):
                    han = 78

                if fu == 0 and is_aotenjou:
                    fu = 40

                if not error:
                    cost = scores_calculator.calculate_scores(
                        han, fu, self.config,
                        len(yakuman_list) > 0)

                calculated_hand = {
                    "cost": cost,
                    "error": error,
                    "hand_yaku": hand_yaku,
                    "han": han,
                    "fu": fu,
                    "fu_details": fu_details,
                }

                calculated_hands.append(calculated_hand)

        # exception hand
        if not is_open_hand and self.config.yaku.kokushi.is_condition_met(
                None, tiles_34):
            if tiles_34[win_tile // 4] == 2:
                hand_yaku.append(self.config.yaku.daburu_kokushi)
            else:
                hand_yaku.append(self.config.yaku.kokushi)

            if not self.config.is_tsumo and self.config.options.has_sashikomi_yakuman:
                if self.config.is_riichi and not self.config.is_daburu_riichi:
                    if self.config.is_open_riichi:
                        hand_yaku.append(self.config.yaku.sashikomi)

                if self.config.is_daburu_riichi:
                    if self.config.is_open_riichi:
                        hand_yaku.append(self.config.yaku.sashikomi)

            if self.config.is_renhou and self.config.options.renhou_as_yakuman:
                hand_yaku.append(self.config.yaku.renhou_yakuman)

            if self.config.is_tenhou:
                hand_yaku.append(self.config.yaku.tenhou)

            if self.config.is_chiihou:
                hand_yaku.append(self.config.yaku.chiihou)

            if self.config.paarenchan > 0:
                self.config.yaku.paarenchan.set_paarenchan_count(
                    self.config.paarenchan)
                hand_yaku.append(self.config.yaku.paarenchan)

            # calculate han
            han = 0
            for item in hand_yaku:
                if is_open_hand and item.han_open:
                    han += item.han_open
                else:
                    han += item.han_closed

            fu = 0
            if is_aotenjou:
                if self.config.is_tsumo:
                    fu = 30
                else:
                    fu = 40

                tiles_for_dora = tiles[:]

                count_of_dora = 0
                count_of_aka_dora = 0

                for tile in tiles_for_dora:
                    count_of_dora += plus_dora(tile, dora_indicators)

                for tile in tiles_for_dora:
                    if is_aka_dora(tile, self.config.options.has_aka_dora):
                        count_of_aka_dora += 1

                if count_of_dora:
                    self.config.yaku.dora.han_open = count_of_dora
                    self.config.yaku.dora.han_closed = count_of_dora
                    hand_yaku.append(self.config.yaku.dora)
                    han += count_of_dora

                if count_of_aka_dora:
                    self.config.yaku.aka_dora.han_open = count_of_aka_dora
                    self.config.yaku.aka_dora.han_closed = count_of_aka_dora
                    hand_yaku.append(self.config.yaku.aka_dora)
                    han += count_of_aka_dora

            cost = scores_calculator.calculate_scores(han, fu, self.config,
                                                      len(hand_yaku) > 0)
            calculated_hands.append({
                "cost": cost,
                "error": None,
                "hand_yaku": hand_yaku,
                "han": han,
                "fu": fu,
                "fu_details": []
            })

        # let's use cost for most expensive hand
        calculated_hands = sorted(calculated_hands,
                                  key=lambda x: (x["han"], x["fu"]),
                                  reverse=True)
        calculated_hand = calculated_hands[0]

        cost = calculated_hand["cost"]
        error = calculated_hand["error"]
        hand_yaku = calculated_hand["hand_yaku"]
        han = calculated_hand["han"]
        fu = calculated_hand["fu"]
        fu_details = calculated_hand["fu_details"]

        return HandResponse(cost, han, fu, hand_yaku, error, fu_details,
                            is_open_hand)
예제 #21
0
    def estimate_hand_value(self,
                            tiles,
                            win_tile,
                            melds=None,
                            dora_indicators=None,
                            config=None):
        """
        :param tiles: array with 14 tiles in 136-tile format
        :param win_tile: 136 format tile that caused win (ron or tsumo)
        :param melds: array with Meld objects
        :param dora_indicators: array of tiles in 136-tile format
        :param config: HandConfig object
        :return: HandResponse object
        """

        if not melds:
            melds = []

        if not dora_indicators:
            dora_indicators = []

        self.config = config or HandConfig()

        agari = Agari()
        hand_yaku = []
        scores_calculator = ScoresCalculator()
        tiles_34 = TilesConverter.to_34_array(tiles)
        divider = HandDivider()
        fu_calculator = FuCalculator()

        opened_melds = [x.tiles_34 for x in melds if x.opened]
        all_melds = [x.tiles_34 for x in melds]
        is_open_hand = len(opened_melds) > 0

        # special situation
        if self.config.is_nagashi_mangan:
            hand_yaku.append(self.config.yaku.nagashi_mangan)
            fu = 30
            han = self.config.yaku.nagashi_mangan.han_closed
            cost = scores_calculator.calculate_scores(han, fu, self.config,
                                                      False)
            return HandResponse(cost, han, fu, hand_yaku)

        if win_tile not in tiles:
            return HandResponse(error="Win tile not in the hand")

        if self.config.is_riichi and is_open_hand:
            return HandResponse(
                error="Riichi can't be declared with open hand")

        if self.config.is_ippatsu and is_open_hand:
            return HandResponse(
                error="Ippatsu can't be declared with open hand")

        if self.config.is_ippatsu and not self.config.is_riichi and not self.config.is_daburu_riichi:
            return HandResponse(
                error="Ippatsu can't be declared without riichi")

        if not agari.is_agari(tiles_34, all_melds):
            return HandResponse(error='Hand is not winning')

        if not self.config.options.has_double_yakuman:
            self.config.yaku.daburu_kokushi.han_closed = 13
            self.config.yaku.suuankou_tanki.han_closed = 13
            self.config.yaku.daburu_chuuren_poutou.han_closed = 13
            self.config.yaku.daisuushi.han_closed = 13
            self.config.yaku.daisuushi.han_open = 13

        hand_options = divider.divide_hand(tiles_34, melds)

        calculated_hands = []
        for hand in hand_options:
            is_chiitoitsu = self.config.yaku.chiitoitsu.is_condition_met(hand)
            valued_tiles = [
                HAKU, HATSU, CHUN, self.config.player_wind,
                self.config.round_wind
            ]

            win_groups = self._find_win_groups(win_tile, hand, opened_melds)
            for win_group in win_groups:
                cost = None
                error = None
                hand_yaku = []
                han = 0

                fu_details, fu = fu_calculator.calculate_fu(
                    hand, win_tile, win_group, self.config, valued_tiles,
                    melds)

                is_pinfu = len(
                    fu_details) == 1 and not is_chiitoitsu and not is_open_hand

                pon_sets = [x for x in hand if is_pon(x)]
                chi_sets = [x for x in hand if is_chi(x)]

                if self.config.is_tsumo:
                    if not is_open_hand:
                        hand_yaku.append(self.config.yaku.tsumo)

                if is_pinfu:
                    hand_yaku.append(self.config.yaku.pinfu)

                # let's skip hand that looks like chitoitsu, but it contains open sets
                if is_chiitoitsu and is_open_hand:
                    continue

                if is_chiitoitsu:
                    hand_yaku.append(self.config.yaku.chiitoitsu)

                is_daisharin = self.config.yaku.daisharin.is_condition_met(
                    hand, self.config.options.has_daisharin_other_suits)
                if self.config.options.has_daisharin and is_daisharin:
                    self.config.yaku.daisharin.rename(hand)
                    hand_yaku.append(self.config.yaku.daisharin)

                is_tanyao = self.config.yaku.tanyao.is_condition_met(hand)
                if is_open_hand and not self.config.options.has_open_tanyao:
                    is_tanyao = False

                if is_tanyao:
                    hand_yaku.append(self.config.yaku.tanyao)

                if self.config.is_riichi and not self.config.is_daburu_riichi:
                    hand_yaku.append(self.config.yaku.riichi)

                if self.config.is_daburu_riichi:
                    hand_yaku.append(self.config.yaku.daburu_riichi)

                if self.config.is_ippatsu:
                    hand_yaku.append(self.config.yaku.ippatsu)

                if self.config.is_rinshan:
                    hand_yaku.append(self.config.yaku.rinshan)

                if self.config.is_chankan:
                    hand_yaku.append(self.config.yaku.chankan)

                if self.config.is_haitei:
                    hand_yaku.append(self.config.yaku.haitei)

                if self.config.is_houtei:
                    hand_yaku.append(self.config.yaku.houtei)

                if self.config.is_renhou:
                    if self.config.options.renhou_as_yakuman:
                        hand_yaku.append(self.config.yaku.renhou_yakuman)
                    else:
                        hand_yaku.append(self.config.yaku.renhou)

                if self.config.is_tenhou:
                    hand_yaku.append(self.config.yaku.tenhou)

                if self.config.is_chiihou:
                    hand_yaku.append(self.config.yaku.chiihou)

                if self.config.yaku.honitsu.is_condition_met(hand):
                    hand_yaku.append(self.config.yaku.honitsu)

                if self.config.yaku.chinitsu.is_condition_met(hand):
                    hand_yaku.append(self.config.yaku.chinitsu)

                if self.config.yaku.tsuisou.is_condition_met(hand):
                    hand_yaku.append(self.config.yaku.tsuisou)

                if self.config.yaku.honroto.is_condition_met(hand):
                    hand_yaku.append(self.config.yaku.honroto)

                if self.config.yaku.chinroto.is_condition_met(hand):
                    hand_yaku.append(self.config.yaku.chinroto)

                # small optimization, try to detect yaku with chi required sets only if we have chi sets in hand
                if len(chi_sets):
                    if self.config.yaku.chanta.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.chanta)

                    if self.config.yaku.junchan.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.junchan)

                    if self.config.yaku.ittsu.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.ittsu)

                    if not is_open_hand:
                        if self.config.yaku.ryanpeiko.is_condition_met(hand):
                            hand_yaku.append(self.config.yaku.ryanpeiko)
                        elif self.config.yaku.iipeiko.is_condition_met(hand):
                            hand_yaku.append(self.config.yaku.iipeiko)

                    if self.config.yaku.sanshoku.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.sanshoku)

                # small optimization, try to detect yaku with pon required sets only if we have pon sets in hand
                if len(pon_sets):
                    if self.config.yaku.toitoi.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.toitoi)

                    if self.config.yaku.sanankou.is_condition_met(
                            hand, win_tile, melds, self.config.is_tsumo):
                        hand_yaku.append(self.config.yaku.sanankou)

                    if self.config.yaku.sanshoku_douko.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.sanshoku_douko)

                    if self.config.yaku.shosangen.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.shosangen)

                    if self.config.yaku.haku.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.haku)

                    if self.config.yaku.hatsu.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.hatsu)

                    if self.config.yaku.chun.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.chun)

                    if self.config.yaku.east.is_condition_met(
                            hand, self.config.player_wind,
                            self.config.round_wind):
                        if self.config.player_wind == EAST:
                            hand_yaku.append(self.config.yaku.yakuhai_place)

                        if self.config.round_wind == EAST:
                            hand_yaku.append(self.config.yaku.yakuhai_round)

                    if self.config.yaku.south.is_condition_met(
                            hand, self.config.player_wind,
                            self.config.round_wind):
                        if self.config.player_wind == SOUTH:
                            hand_yaku.append(self.config.yaku.yakuhai_place)

                        if self.config.round_wind == SOUTH:
                            hand_yaku.append(self.config.yaku.yakuhai_round)

                    if self.config.yaku.west.is_condition_met(
                            hand, self.config.player_wind,
                            self.config.round_wind):
                        if self.config.player_wind == WEST:
                            hand_yaku.append(self.config.yaku.yakuhai_place)

                        if self.config.round_wind == WEST:
                            hand_yaku.append(self.config.yaku.yakuhai_round)

                    if self.config.yaku.north.is_condition_met(
                            hand, self.config.player_wind,
                            self.config.round_wind):
                        if self.config.player_wind == NORTH:
                            hand_yaku.append(self.config.yaku.yakuhai_place)

                        if self.config.round_wind == NORTH:
                            hand_yaku.append(self.config.yaku.yakuhai_round)

                    if self.config.yaku.daisangen.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.daisangen)

                    if self.config.yaku.shosuushi.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.shosuushi)

                    if self.config.yaku.daisuushi.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.daisuushi)

                    if self.config.yaku.ryuisou.is_condition_met(hand):
                        hand_yaku.append(self.config.yaku.ryuisou)

                    # closed kan can't be used in chuuren_poutou
                    if not len(
                            melds
                    ) and self.config.yaku.chuuren_poutou.is_condition_met(
                            hand):
                        if tiles_34[win_tile // 4] == 2 or tiles_34[win_tile //
                                                                    4] == 4:
                            hand_yaku.append(
                                self.config.yaku.daburu_chuuren_poutou)
                        else:
                            hand_yaku.append(self.config.yaku.chuuren_poutou)

                    if not is_open_hand and self.config.yaku.suuankou.is_condition_met(
                            hand, win_tile, self.config.is_tsumo):
                        if tiles_34[win_tile // 4] == 2:
                            hand_yaku.append(self.config.yaku.suuankou_tanki)
                        else:
                            hand_yaku.append(self.config.yaku.suuankou)

                    if self.config.yaku.sankantsu.is_condition_met(
                            hand, melds):
                        hand_yaku.append(self.config.yaku.sankantsu)

                    if self.config.yaku.suukantsu.is_condition_met(
                            hand, melds):
                        hand_yaku.append(self.config.yaku.suukantsu)

                # yakuman is not connected with other yaku
                yakuman_list = [x for x in hand_yaku if x.is_yakuman]
                if yakuman_list:
                    hand_yaku = yakuman_list

                # calculate han
                for item in hand_yaku:
                    if is_open_hand and item.han_open:
                        han += item.han_open
                    else:
                        han += item.han_closed

                if han == 0:
                    error = 'There are no yaku in the hand'
                    cost = None

                # we don't need to add dora to yakuman
                if not yakuman_list:
                    tiles_for_dora = tiles[:]

                    # we had to search for dora in kan fourth tiles as well
                    for meld in melds:
                        if meld.type == Meld.KAN or meld.type == Meld.CHANKAN:
                            tiles_for_dora.append(meld.tiles[3])

                    count_of_dora = 0
                    count_of_aka_dora = 0

                    for tile in tiles_for_dora:
                        count_of_dora += plus_dora(tile, dora_indicators)

                    for tile in tiles_for_dora:
                        if is_aka_dora(tile, self.config.options.has_aka_dora):
                            count_of_aka_dora += 1

                    if count_of_dora:
                        self.config.yaku.dora.han_open = count_of_dora
                        self.config.yaku.dora.han_closed = count_of_dora
                        hand_yaku.append(self.config.yaku.dora)
                        han += count_of_dora

                    if count_of_aka_dora:
                        self.config.yaku.aka_dora.han_open = count_of_aka_dora
                        self.config.yaku.aka_dora.han_closed = count_of_aka_dora
                        hand_yaku.append(self.config.yaku.aka_dora)
                        han += count_of_aka_dora

                if not error:
                    cost = scores_calculator.calculate_scores(
                        han, fu, self.config,
                        len(yakuman_list) > 0)

                calculated_hand = {
                    'cost': cost,
                    'error': error,
                    'hand_yaku': hand_yaku,
                    'han': han,
                    'fu': fu,
                    'fu_details': fu_details
                }

                calculated_hands.append(calculated_hand)

        # exception hand
        if not is_open_hand and self.config.yaku.kokushi.is_condition_met(
                None, tiles_34):
            if tiles_34[win_tile // 4] == 2:
                hand_yaku.append(self.config.yaku.daburu_kokushi)
            else:
                hand_yaku.append(self.config.yaku.kokushi)

            if self.config.is_renhou and self.config.options.renhou_as_yakuman:
                hand_yaku.append(self.config.yaku.renhou_yakuman)

            if self.config.is_tenhou:
                hand_yaku.append(self.config.yaku.tenhou)

            if self.config.is_chiihou:
                hand_yaku.append(self.config.yaku.chiihou)

            # calculate han
            han = 0
            for item in hand_yaku:
                if is_open_hand and item.han_open:
                    han += item.han_open
                else:
                    han += item.han_closed

            fu = 0
            cost = scores_calculator.calculate_scores(han, fu, self.config,
                                                      len(hand_yaku) > 0)
            calculated_hands.append({
                'cost': cost,
                'error': None,
                'hand_yaku': hand_yaku,
                'han': han,
                'fu': fu,
                'fu_details': []
            })

        # let's use cost for most expensive hand
        calculated_hands = sorted(calculated_hands,
                                  key=lambda x: (x['han'], x['fu']),
                                  reverse=True)
        calculated_hand = calculated_hands[0]

        cost = calculated_hand['cost']
        error = calculated_hand['error']
        hand_yaku = calculated_hand['hand_yaku']
        han = calculated_hand['han']
        fu = calculated_hand['fu']
        fu_details = calculated_hand['fu_details']

        return HandResponse(cost, han, fu, hand_yaku, error, fu_details)