Exemple #1
0
    def _initialize_honitsu_dora_count(self, tiles_136, suit):
        tiles_34 = TilesConverter.to_34_array(tiles_136)

        dora_count_man_not_isolated = 0
        dora_count_pin_not_isolated = 0
        dora_count_sou_not_isolated = 0

        for tile_136 in tiles_136:
            tile_34 = tile_136 // 4

            dora_count = plus_dora(tile_136, self.player.table.dora_indicators)

            if is_aka_dora(tile_136, self.player.table.has_aka_dora):
                dora_count += 1

            if is_man(tile_34):
                if not is_tile_strictly_isolated(tiles_34, tile_34):
                    dora_count_man_not_isolated += dora_count

            if is_pin(tile_34):
                if not is_tile_strictly_isolated(tiles_34, tile_34):
                    dora_count_pin_not_isolated += dora_count

            if is_sou(tile_34):
                if not is_tile_strictly_isolated(tiles_34, tile_34):
                    dora_count_sou_not_isolated += dora_count

        if suit['name'] == 'pin':
            self.dora_count_other_suits_not_isolated = dora_count_man_not_isolated + dora_count_sou_not_isolated
        elif suit['name'] == 'sou':
            self.dora_count_other_suits_not_isolated = dora_count_man_not_isolated + dora_count_pin_not_isolated
        elif suit['name'] == 'man':
            self.dora_count_other_suits_not_isolated = dora_count_sou_not_isolated + dora_count_pin_not_isolated
    def _initialize_honitsu_dora_count(self, tiles_136, suit):
        tiles_34 = TilesConverter.to_34_array(tiles_136)

        dora_count_man_not_isolated = 0
        dora_count_pin_not_isolated = 0
        dora_count_sou_not_isolated = 0

        for tile_136 in tiles_136:
            tile_34 = tile_136 // 4

            dora_count = plus_dora(tile_136, self.player.table.dora_indicators)

            if is_aka_dora(tile_136, self.player.table.has_aka_dora):
                dora_count += 1

            if is_man(tile_34):
                if not is_tile_strictly_isolated(tiles_34, tile_34):
                    dora_count_man_not_isolated += dora_count

            if is_pin(tile_34):
                if not is_tile_strictly_isolated(tiles_34, tile_34):
                    dora_count_pin_not_isolated += dora_count

            if is_sou(tile_34):
                if not is_tile_strictly_isolated(tiles_34, tile_34):
                    dora_count_sou_not_isolated += dora_count

        if suit['name'] == 'pin':
            self.dora_count_other_suits_not_isolated = dora_count_man_not_isolated + dora_count_sou_not_isolated
        elif suit['name'] == 'sou':
            self.dora_count_other_suits_not_isolated = dora_count_man_not_isolated + dora_count_pin_not_isolated
        elif suit['name'] == 'man':
            self.dora_count_other_suits_not_isolated = dora_count_sou_not_isolated + dora_count_pin_not_isolated
    def _choose_best_discard_with_4_or_more_shanten(self, discard_options):
        discard_options = sorted(discard_options,
                                 key=lambda x: (x.shanten, -x.ukeire))
        first_option = discard_options[0]

        # we filter by ukeire
        ukeire_borders = self._choose_ukeire_borders(
            first_option, DiscardOption.UKEIRE_FIRST_FILTER_PERCENTAGE,
            "ukeire")
        possible_options = self._filter_list_by_ukeire_borders(
            discard_options, first_option.ukeire, ukeire_borders)

        possible_options = sorted(possible_options,
                                  key=self._sorting_rule_for_4_or_more_shanten)

        possible_options = self._try_keep_doras(
            possible_options, "ukeire",
            DiscardOption.UKEIRE_FIRST_FILTER_PERCENTAGE)
        assert possible_options

        closed_hand_34 = TilesConverter.to_34_array(self.player.closed_hand)
        isolated_tiles = [
            x for x in possible_options
            if is_tile_strictly_isolated(closed_hand_34, x.tile_to_discard_34)
        ]
        # isolated tiles should be discarded first
        if isolated_tiles:
            possible_options = isolated_tiles

        # let's sort tiles by value and let's choose less valuable tile to discard
        return self._choose_best_tile_considering_threats(
            sorted(possible_options,
                   key=self._sorting_rule_for_4_or_more_shanten),
            self._sorting_rule_for_4_or_more_shanten,
        )
 def _sorting_rule_for_2_3_shanten_with_isolated(x, closed_hand_34):
     return (
         -x.ukeire_second,
         -x.ukeire,
         -is_tile_strictly_isolated(closed_hand_34, x.tile_to_discard_34),
         x.valuation,
     )
    def calculate_waits(self, closed_hand_34: List[int], all_tiles_34: List[int], use_chiitoitsu: bool = False):
        previous_shanten = self.ai.calculate_shanten_or_get_from_cache(closed_hand_34, use_chiitoitsu=use_chiitoitsu)

        waiting = []
        for tile_index in range(0, 34):
            # it is important to check that we don't have all 4 tiles in the all tiles
            # not in the closed hand
            # so, we will not count 1z as waiting when we have 111z meld
            if all_tiles_34[tile_index] == 4:
                continue

            closed_hand_34[tile_index] += 1

            skip_isolated_tile = True
            if closed_hand_34[tile_index] == 4:
                skip_isolated_tile = False
            if use_chiitoitsu and closed_hand_34[tile_index] == 3:
                skip_isolated_tile = False

            # there is no need to check single isolated tile
            if skip_isolated_tile and is_tile_strictly_isolated(closed_hand_34, tile_index):
                closed_hand_34[tile_index] -= 1
                continue

            new_shanten = self.ai.calculate_shanten_or_get_from_cache(closed_hand_34, use_chiitoitsu=use_chiitoitsu)

            if new_shanten == previous_shanten - 1:
                waiting.append(tile_index)

            closed_hand_34[tile_index] -= 1

        return waiting, previous_shanten
    def _initialize_honitsu_dora_count(self, tiles_136, suit):
        tiles_34 = TilesConverter.to_34_array(tiles_136)

        dora_count_man = 0
        dora_count_pin = 0
        dora_count_sou = 0

        dora_count_man_not_isolated = 0
        dora_count_pin_not_isolated = 0
        dora_count_sou_not_isolated = 0

        for tile_136 in tiles_136:
            tile_34 = tile_136 // 4

            dora_count = plus_dora(
                tile_136, self.player.table.dora_indicators, add_aka_dora=self.player.table.has_aka_dora
            )

            if is_man(tile_34):
                dora_count_man += dora_count
                if not is_tile_strictly_isolated(tiles_34, tile_34):
                    dora_count_man_not_isolated += dora_count

            if is_pin(tile_34):
                dora_count_pin += dora_count
                if not is_tile_strictly_isolated(tiles_34, tile_34):
                    dora_count_pin_not_isolated += dora_count

            if is_sou(tile_34):
                dora_count_sou += dora_count
                if not is_tile_strictly_isolated(tiles_34, tile_34):
                    dora_count_sou_not_isolated += dora_count

        if suit["name"] == "pin":
            self.dora_count_our_suit = dora_count_pin
            self.dora_count_other_suits_not_isolated = dora_count_man_not_isolated + dora_count_sou_not_isolated
        elif suit["name"] == "sou":
            self.dora_count_our_suit = dora_count_sou
            self.dora_count_other_suits_not_isolated = dora_count_man_not_isolated + dora_count_pin_not_isolated
        elif suit["name"] == "man":
            self.dora_count_our_suit = dora_count_man
            self.dora_count_other_suits_not_isolated = dora_count_sou_not_isolated + dora_count_pin_not_isolated
Exemple #7
0
    def _calculate_not_suitable_tiles_cnt(self, tiles_34, suit):
        self.tiles_count_other_suits = 0
        self.tiles_count_other_suits_not_isolated = 0

        for x in range(0, 34):
            tile = tiles_34[x]
            if not tile:
                continue

            if not suit(x) and not is_honor(x):
                self.tiles_count_other_suits += tile
                if not is_tile_strictly_isolated(tiles_34, x):
                    self.tiles_count_other_suits_not_isolated += tile
    def _calculate_not_suitable_tiles_cnt(self, tiles_34, suit):
        self.tiles_count_other_suits = 0
        self.tiles_count_other_suits_not_isolated = 0

        for x in range(0, 34):
            tile = tiles_34[x]
            if not tile:
                continue

            if not suit(x) and not is_honor(x):
                self.tiles_count_other_suits += tile
                if not is_tile_strictly_isolated(tiles_34, x):
                    self.tiles_count_other_suits_not_isolated += tile
    def should_activate_strategy(self, tiles_136, meld_tile=None):
        """
        Tanyao hand is a hand without terminal and honor tiles, to achieve this
        we will use different approaches
        :return: boolean
        """

        result = super(TanyaoStrategy,
                       self).should_activate_strategy(tiles_136)
        if not result:
            return False

        tiles = TilesConverter.to_34_array(self.player.tiles)

        closed_hand_34 = TilesConverter.to_34_array(self.player.closed_hand)
        isolated_tiles = [
            x // 4 for x in self.player.tiles
            if is_tile_strictly_isolated(closed_hand_34, x //
                                         4) or is_honor(x // 4)
        ]

        count_of_terminal_pon_sets = 0
        count_of_terminal_pairs = 0
        count_of_valued_pairs = 0
        count_of_not_suitable_tiles = 0
        count_of_not_suitable_not_isolated_tiles = 0
        for x in range(0, 34):
            tile = tiles[x]
            if not tile:
                continue

            if x in self.not_suitable_tiles and tile == 3:
                count_of_terminal_pon_sets += 1

            if x in self.not_suitable_tiles and tile == 2:
                count_of_terminal_pairs += 1

                if x in self.player.valued_honors:
                    count_of_valued_pairs += 1

            if x in self.not_suitable_tiles:
                count_of_not_suitable_tiles += tile

            if x in self.not_suitable_tiles and x not in isolated_tiles:
                count_of_not_suitable_not_isolated_tiles += tile

        # we have too much terminals and honors
        if count_of_not_suitable_tiles >= 5:
            return False

        # if we already have pon of honor\terminal tiles
        # we don't need to open hand for tanyao
        if count_of_terminal_pon_sets > 0:
            return False

        # with valued pair (yakuhai wind or dragon)
        # we don't need to go for tanyao
        if count_of_valued_pairs > 0:
            return False

        # one pair is ok in tanyao pair
        # but 2+ pairs can't be suitable
        if count_of_terminal_pairs > 1:
            return False

        # 3 or more not suitable tiles that
        # are not isolated is too much
        if count_of_not_suitable_not_isolated_tiles >= 3:
            return False

        # if we are 1 shanten, even 2 tiles
        # that are not suitable and not isolated
        # is too much
        if count_of_not_suitable_not_isolated_tiles >= 2 and self.player.ai.shanten == 1:
            return False

        # TODO: don't open from good 1-shanten into tanyao 1-shaten with same ukeire or worse

        # 123 and 789 indices
        indices = [[0, 1, 2], [6, 7, 8], [9, 10, 11], [15, 16, 17],
                   [18, 19, 20], [24, 25, 26]]

        for index_set in indices:
            first = tiles[index_set[0]]
            second = tiles[index_set[1]]
            third = tiles[index_set[2]]
            if first >= 1 and second >= 1 and third >= 1:
                return False

        # if we have 2 or more non-central doras
        # we don't want to go for tanyao
        if self.dora_count_not_central >= 2:
            return False

        # if we have less than two central doras
        # let's not consider open tanyao
        if self.dora_count_central < 2:
            return False

        # if we have only two central doras let's
        # wait for 5th turn before opening our hand
        if self.dora_count_central == 2 and self.player.round_step < 5:
            return False

        return True
Exemple #10
0
    def test_is_strictly_isolated_tile(self):
        hand_34 = TilesConverter.string_to_34_array(sou='1399',
                                                    pin='1567',
                                                    man='25',
                                                    honors='1224')

        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou='1')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou='2')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou='3')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou='4')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou='5')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou='6')), True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou='7')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou='8')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou='9')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin='1')), True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin='2')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin='3')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin='4')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin='5')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin='6')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin='7')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin='8')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin='9')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man='1')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man='2')), True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man='3')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man='4')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man='5')), True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man='6')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man='7')), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man='8')), True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man='9')), True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors='1')),
            True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors='2')),
            False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors='3')),
            True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors='4')),
            True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors='5')),
            True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors='6')),
            True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors='7')),
            True)
    def choose_tile_to_discard(self, tiles, closed_hand, melds, print_log=True):
        """
        Try to find best tile to discard, based on different rules
        """

        discard_options, _ = self.find_discard_options(
            tiles,
            closed_hand,
            melds
        )

        # our strategy can affect discard options
        if self.ai.current_strategy:
            discard_options = self.ai.current_strategy.determine_what_to_discard(
                discard_options,
                closed_hand,
                melds
            )

        had_to_be_discarded_tiles = [x for x in discard_options if x.had_to_be_discarded]
        if had_to_be_discarded_tiles:
            discard_options = sorted(had_to_be_discarded_tiles, key=lambda x: (x.shanten, -x.ukeire, x.valuation))
            DecisionsLogger.debug(
                log.DISCARD_OPTIONS,
                'Discard marked tiles first',
                discard_options,
                print_log=print_log
            )
            return discard_options[0]

        # remove needed tiles from discard options
        discard_options = [x for x in discard_options if not x.had_to_be_saved]

        discard_options = sorted(discard_options, key=lambda x: (x.shanten, -x.ukeire))
        first_option = discard_options[0]
        results_with_same_shanten = [x for x in discard_options if x.shanten == first_option.shanten]

        possible_options = [first_option]
        ukeire_borders = self._choose_ukeire_borders(first_option, 20, 'ukeire')
        for discard_option in results_with_same_shanten:
            # there is no sense to check already chosen tile
            if discard_option.tile_to_discard == first_option.tile_to_discard:
                continue

            # let's choose tiles that are close to the max ukeire tile
            if discard_option.ukeire >= first_option.ukeire - ukeire_borders:
                possible_options.append(discard_option)

        if first_option.shanten in [1, 2, 3]:
            ukeire_field = 'ukeire_second'
            for x in possible_options:
                self.calculate_second_level_ukeire(x, tiles, melds)

            possible_options = sorted(possible_options, key=lambda x: -getattr(x, ukeire_field))

            filter_percentage = 20
            possible_options = self._filter_list_by_percentage(
                possible_options,
                ukeire_field,
                filter_percentage
            )
        else:
            ukeire_field = 'ukeire'
            possible_options = sorted(possible_options, key=lambda x: -getattr(x, ukeire_field))

        # only one option - so we choose it
        if len(possible_options) == 1:
            return possible_options[0]

        # tempai state has a special handling
        if first_option.shanten == 0:
            other_tiles_with_same_shanten = [x for x in possible_options if x.shanten == 0]
            return self._choose_best_discard_in_tempai(tiles, melds, other_tiles_with_same_shanten)

        tiles_without_dora = [x for x in possible_options if x.count_of_dora == 0]

        # we have only dora candidates to discard
        if not tiles_without_dora:
            DecisionsLogger.debug(
                log.DISCARD_OPTIONS,
                context=possible_options,
                print_log=print_log
            )

            min_dora = min([x.count_of_dora for x in possible_options])
            min_dora_list = [x for x in possible_options if x.count_of_dora == min_dora]

            return sorted(min_dora_list, key=lambda x: -getattr(x, ukeire_field))[0]

        # only one option - so we choose it
        if len(tiles_without_dora) == 1:
            return tiles_without_dora[0]

        # 1-shanten hands have special handling - we can consider future hand cost here
        if first_option.shanten == 1:
            return sorted(tiles_without_dora, key=lambda x: (-x.second_level_cost, -x.ukeire_second, x.valuation))[0]

        if first_option.shanten == 2 or first_option.shanten == 3:
            # we filter 10% of options here
            second_filter_percentage = 10
            filtered_options = self._filter_list_by_percentage(
                tiles_without_dora,
                ukeire_field,
                second_filter_percentage
            )
        # we should also consider borders for 3+ shanten hands
        else:
            best_option_without_dora = tiles_without_dora[0]
            ukeire_borders = self._choose_ukeire_borders(best_option_without_dora, 10, ukeire_field)
            filtered_options = []
            for discard_option in tiles_without_dora:
                val = getattr(best_option_without_dora, ukeire_field) - ukeire_borders
                if getattr(discard_option, ukeire_field) >= val:
                    filtered_options.append(discard_option)

        DecisionsLogger.debug(
            log.DISCARD_OPTIONS,
            context=possible_options,
            print_log=print_log
        )

        closed_hand_34 = TilesConverter.to_34_array(closed_hand)
        isolated_tiles = [x for x in filtered_options if is_tile_strictly_isolated(closed_hand_34, x.tile_to_discard)]
        # isolated tiles should be discarded first
        if isolated_tiles:
            # let's sort tiles by value and let's choose less valuable tile to discard
            return sorted(isolated_tiles, key=lambda x: x.valuation)[0]

        # there are no isolated tiles or we don't care about them
        # let's discard tile with greater ukeire/ukeire2
        filtered_options = sorted(filtered_options, key=lambda x: -getattr(x, ukeire_field))
        first_option = filtered_options[0]

        other_tiles_with_same_ukeire = [x for x in filtered_options
                                        if getattr(x, ukeire_field) == getattr(first_option, ukeire_field)]

        # it will happen with shanten=1, all tiles will have ukeire_second == 0
        # or in tempai we can have several tiles with same ukeire
        if other_tiles_with_same_ukeire:
            return sorted(other_tiles_with_same_ukeire, key=lambda x: x.valuation)[0]

        # we have only one candidate to discard with greater ukeire
        return first_option
    def choose_tile_to_discard(self, tiles, closed_hand, melds, print_log=True):
        """
        Try to find best tile to discard, based on different rules
        """

        discard_options, _ = self.find_discard_options(
            tiles,
            closed_hand,
            melds
        )

        # our strategy can affect discard options
        if self.ai.current_strategy:
            discard_options = self.ai.current_strategy.determine_what_to_discard(
                discard_options,
                closed_hand,
                melds
            )

        had_to_be_discarded_tiles = [x for x in discard_options if x.had_to_be_discarded]
        if had_to_be_discarded_tiles:
            discard_options = sorted(had_to_be_discarded_tiles, key=lambda x: (x.shanten, -x.ukeire, x.valuation))
            DecisionsLogger.debug(
                log.DISCARD_OPTIONS,
                'Discard marked tiles first',
                discard_options,
                print_log=print_log
            )
            return discard_options[0]

        # remove needed tiles from discard options
        discard_options = [x for x in discard_options if not x.had_to_be_saved]

        discard_options = sorted(discard_options, key=lambda x: (x.shanten, -x.ukeire))
        first_option = discard_options[0]
        results_with_same_shanten = [x for x in discard_options if x.shanten == first_option.shanten]

        possible_options = [first_option]
        ukeire_borders = self._choose_ukeire_borders(first_option, 20, 'ukeire')
        for discard_option in results_with_same_shanten:
            # there is no sense to check already chosen tile
            if discard_option.tile_to_discard == first_option.tile_to_discard:
                continue

            # let's choose tiles that are close to the max ukeire tile
            if discard_option.ukeire >= first_option.ukeire - ukeire_borders:
                possible_options.append(discard_option)

        if first_option.shanten in [1, 2, 3]:
            ukeire_field = 'ukeire_second'
            for x in possible_options:
                self.calculate_second_level_ukeire(x, tiles, melds)

            possible_options = sorted(possible_options, key=lambda x: -getattr(x, ukeire_field))

            filter_percentage = 20
            possible_options = self._filter_list_by_percentage(
                possible_options,
                ukeire_field,
                filter_percentage
            )
        else:
            ukeire_field = 'ukeire'
            possible_options = sorted(possible_options, key=lambda x: -getattr(x, ukeire_field))

        # only one option - so we choose it
        if len(possible_options) == 1:
            return possible_options[0]

        # tempai state has a special handling
        if first_option.shanten == 0:
            other_tiles_with_same_shanten = [x for x in possible_options if x.shanten == 0]
            return self._choose_best_discard_in_tempai(tiles, melds, other_tiles_with_same_shanten)

        tiles_without_dora = [x for x in possible_options if x.count_of_dora == 0]

        # we have only dora candidates to discard
        if not tiles_without_dora:
            DecisionsLogger.debug(
                log.DISCARD_OPTIONS,
                context=possible_options,
                print_log=print_log
            )

            min_dora = min([x.count_of_dora for x in possible_options])
            min_dora_list = [x for x in possible_options if x.count_of_dora == min_dora]

            return sorted(min_dora_list, key=lambda x: -getattr(x, ukeire_field))[0]

        # only one option - so we choose it
        if len(tiles_without_dora) == 1:
            return tiles_without_dora[0]

        # 1-shanten hands have special handling - we can consider future hand cost here
        if first_option.shanten == 1:
            return sorted(tiles_without_dora, key=lambda x: (-x.second_level_cost, -x.ukeire_second, x.valuation))[0]

        if first_option.shanten == 2 or first_option.shanten == 3:
            # we filter 10% of options here
            second_filter_percentage = 10
            filtered_options = self._filter_list_by_percentage(
                tiles_without_dora,
                ukeire_field,
                second_filter_percentage
            )
        # we should also consider borders for 3+ shanten hands
        else:
            best_option_without_dora = tiles_without_dora[0]
            ukeire_borders = self._choose_ukeire_borders(best_option_without_dora, 10, ukeire_field)
            filtered_options = []
            for discard_option in tiles_without_dora:
                val = getattr(best_option_without_dora, ukeire_field) - ukeire_borders
                if getattr(discard_option, ukeire_field) >= val:
                    filtered_options.append(discard_option)

        DecisionsLogger.debug(
            log.DISCARD_OPTIONS,
            context=possible_options,
            print_log=print_log
        )

        closed_hand_34 = TilesConverter.to_34_array(closed_hand)
        isolated_tiles = [x for x in filtered_options if is_tile_strictly_isolated(closed_hand_34, x.tile_to_discard)]
        # isolated tiles should be discarded first
        if isolated_tiles:
            # let's sort tiles by value and let's choose less valuable tile to discard
            return sorted(isolated_tiles, key=lambda x: x.valuation)[0]

        # there are no isolated tiles or we don't care about them
        # let's discard tile with greater ukeire/ukeire2
        filtered_options = sorted(filtered_options, key=lambda x: -getattr(x, ukeire_field))
        first_option = filtered_options[0]

        other_tiles_with_same_ukeire = [x for x in filtered_options
                                        if getattr(x, ukeire_field) == getattr(first_option, ukeire_field)]

        # it will happen with shanten=1, all tiles will have ukeire_second == 0
        # or in tempai we can have several tiles with same ukeire
        if other_tiles_with_same_ukeire:
            return sorted(other_tiles_with_same_ukeire, key=lambda x: x.valuation)[0]

        # we have only one candidate to discard with greater ukeire
        return first_option
Exemple #13
0
    def test_is_strictly_isolated_tile(self):
        hand_34 = TilesConverter.string_to_34_array(sou="1399",
                                                    pin="1567",
                                                    man="25",
                                                    honors="1224")

        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou="1")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou="2")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou="3")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou="4")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou="5")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou="6")), True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou="7")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou="8")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(sou="9")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin="1")), True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin="2")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin="3")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin="4")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin="5")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin="6")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin="7")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin="8")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(pin="9")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man="1")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man="2")), True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man="3")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man="4")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man="5")), True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man="6")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man="7")), False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man="8")), True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(man="9")), True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors="1")),
            True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors="2")),
            False)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors="3")),
            True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors="4")),
            True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors="5")),
            True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors="6")),
            True)
        self.assertEqual(
            is_tile_strictly_isolated(hand_34,
                                      self._string_to_34_tile(honors="7")),
            True)