コード例 #1
0
    def test_calculate_suit_tiles_value_and_dora(self):
        table = Table()
        table.dora_indicators = [self._string_to_136_tile(sou='9')]
        player = table.player

        tile = self._string_to_34_tile(sou='1')
        option = DiscardOption(player, tile, 0, [], 0)
        self.assertEqual(option.valuation, 160)

        # double dora
        table.dora_indicators = [self._string_to_136_tile(sou='9'), self._string_to_136_tile(sou='9')]
        tile = self._string_to_34_tile(sou='1')
        option = DiscardOption(player, tile, 0, [], 0)
        self.assertEqual(option.valuation, 210)
コード例 #2
0
def test_calculate_suit_tiles_value_and_tanyao_hand():
    table = Table()
    player = table.player
    table.has_aka_dora = False
    player.ai.current_strategy = TanyaoStrategy(BaseStrategy.TANYAO, player)

    # 0 - 8   man
    # 9 - 17  pin
    # 18 - 26 sou
    results = [
        [0, 110],
        [9, 110],
        [18, 110],
        [1, 120],
        [10, 120],
        [19, 120],
        [2, 130],
        [11, 130],
        [20, 130],
        [3, 150],
        [12, 150],
        [21, 150],
        [4, 140],
        [13, 140],
        [22, 140],
        [5, 150],
        [14, 150],
        [23, 150],
        [6, 130],
        [15, 130],
        [24, 130],
        [7, 120],
        [16, 120],
        [25, 120],
        [8, 110],
        [17, 110],
        [26, 110],
    ]

    for item in results:
        tile = item[0]
        value = item[1]
        assert DiscardOption(player, tile * 4, 0, [], 0).valuation == value
        assert DiscardOption(player, tile * 4 + 1, 0, [], 0).valuation == value
        assert DiscardOption(player, tile * 4 + 2, 0, [], 0).valuation == value
        assert DiscardOption(player, tile * 4 + 3, 0, [], 0).valuation == value
コード例 #3
0
    def find_discard_options(self):
        """
        :param tiles: array of tiles in 136 format
        :param closed_hand: array of tiles in 136 format
        :return:
        """
        self._assert_hand_correctness()

        tiles = self.player.tiles
        closed_hand = self.player.closed_hand

        tiles_34 = TilesConverter.to_34_array(tiles)
        closed_tiles_34 = TilesConverter.to_34_array(closed_hand)
        is_agari = self.ai.agari.is_agari(tiles_34, self.player.meld_34_tiles)

        # we decide beforehand if we need to consider chiitoitsu for all of our possible discards
        min_shanten, use_chiitoitsu = self.calculate_shanten_and_decide_hand_structure(
            closed_tiles_34)

        results = []
        tile_34_prev = None
        # we iterate in reverse order to naturally handle aka-doras, i.e. discard regular 5 if we have it
        for tile_136 in reversed(self.player.closed_hand):
            tile_34 = tile_136 // 4
            # already added
            if tile_34 == tile_34_prev:
                continue
            else:
                tile_34_prev = tile_34

            closed_tiles_34[tile_34] -= 1
            waiting, shanten = self.calculate_waits(
                closed_tiles_34, tiles_34, use_chiitoitsu=use_chiitoitsu)
            assert shanten >= min_shanten
            closed_tiles_34[tile_34] += 1

            if waiting:
                wait_to_ukeire = dict(
                    zip(waiting, [
                        self.count_tiles([x], closed_tiles_34) for x in waiting
                    ]))
                results.append(
                    DiscardOption(
                        player=self.player,
                        shanten=shanten,
                        tile_to_discard_136=tile_136,
                        waiting=waiting,
                        ukeire=sum(wait_to_ukeire.values()),
                        wait_to_ukeire=wait_to_ukeire,
                    ))

        if is_agari:
            shanten = Shanten.AGARI_STATE
        else:
            shanten = min_shanten

        return results, shanten
コード例 #4
0
    def test_calculate_suit_tiles_value_and_dora(self):
        table = Table()
        table.dora_indicators = [self._string_to_136_tile(sou='9')]
        player = table.player

        tile = self._string_to_34_tile(sou='1')
        option = DiscardOption(player, tile, 0, [], 0)
        self.assertEqual(option.valuation, DiscardOption.DORA_VALUE + 110)

        # double dora
        table.dora_indicators = [
            self._string_to_136_tile(sou='9'),
            self._string_to_136_tile(sou='9')
        ]
        tile = self._string_to_34_tile(sou='1')
        option = DiscardOption(player, tile, 0, [], 0)
        self.assertEqual(option.valuation, DiscardOption.DORA_VALUE * 2 + 110)

        # tile close to dora
        table.dora_indicators = [self._string_to_136_tile(sou='9')]
        tile = self._string_to_34_tile(sou='2')
        option = DiscardOption(player, tile, 0, [], 0)
        self.assertEqual(option.valuation,
                         DiscardOption.DORA_FIRST_NEIGHBOUR + 120)

        # tile not far away from dora
        table.dora_indicators = [self._string_to_136_tile(sou='9')]
        tile = self._string_to_34_tile(sou='3')
        option = DiscardOption(player, tile, 0, [], 0)
        self.assertEqual(option.valuation,
                         DiscardOption.DORA_SECOND_NEIGHBOUR + 140)

        # tile from other suit
        table.dora_indicators = [self._string_to_136_tile(sou='9')]
        tile = self._string_to_34_tile(man='3')
        option = DiscardOption(player, tile, 0, [], 0)
        self.assertEqual(option.valuation, 140)
コード例 #5
0
    def calculate_outs(self, tiles, closed_hand, open_sets_34=None):
        """
        :param tiles: array of tiles in 136 format
        :param closed_hand: array of tiles in 136 format
        :param open_sets_34: array of array with tiles in 34 format
        :return:
        """
        tiles_34 = TilesConverter.to_34_array(tiles)
        closed_tiles_34 = TilesConverter.to_34_array(closed_hand)
        is_agari = self.agari.is_agari(tiles_34,
                                       self.player.open_hand_34_tiles)

        results = []
        for hand_tile in range(0, 34):
            if not closed_tiles_34[hand_tile]:
                continue

            tiles_34[hand_tile] -= 1

            shanten = self.shanten.calculate_shanten(tiles_34, open_sets_34)

            waiting = []
            for j in range(0, 34):
                if hand_tile == j or tiles_34[j] == 4:
                    continue

                tiles_34[j] += 1
                if self.shanten.calculate_shanten(tiles_34,
                                                  open_sets_34) == shanten - 1:
                    waiting.append(j)
                tiles_34[j] -= 1

            tiles_34[hand_tile] += 1

            if waiting:
                results.append(
                    DiscardOption(player=self.player,
                                  shanten=shanten,
                                  tile_to_discard=hand_tile,
                                  waiting=waiting,
                                  tiles_count=self.count_tiles(
                                      waiting, tiles_34)))

        if is_agari:
            shanten = Shanten.AGARI_STATE
        else:
            shanten = self.shanten.calculate_shanten(tiles_34, open_sets_34)

        return results, shanten
コード例 #6
0
def test_calculate_suit_tiles_value_and_dora():
    table = Table()
    table.dora_indicators = [string_to_136_tile(sou="9")]
    player = table.player
    table.has_aka_dora = False

    tile = string_to_136_tile(sou="1")
    option = DiscardOption(player, tile, 0, [], 0)
    assert option.valuation == (DiscardOption.DORA_VALUE + 110)

    # double dora
    table.dora_indicators = [
        string_to_136_tile(sou="9"),
        string_to_136_tile(sou="9")
    ]
    tile = string_to_136_tile(sou="1")
    option = DiscardOption(player, tile, 0, [], 0)
    assert option.valuation == ((DiscardOption.DORA_VALUE * 2) + 110)

    # tile close to dora
    table.dora_indicators = [string_to_136_tile(sou="9")]
    tile = string_to_136_tile(sou="2")
    option = DiscardOption(player, tile, 0, [], 0)
    assert option.valuation == (DiscardOption.DORA_FIRST_NEIGHBOUR + 120)

    # tile not far away from dora
    table.dora_indicators = [string_to_136_tile(sou="9")]
    tile = string_to_136_tile(sou="3")
    option = DiscardOption(player, tile, 0, [], 0)
    assert option.valuation == (DiscardOption.DORA_SECOND_NEIGHBOUR + 140)

    # tile from other suit
    table.dora_indicators = [string_to_136_tile(sou="9")]
    tile = string_to_136_tile(man="3")
    option = DiscardOption(player, tile, 0, [], 0)
    assert option.valuation == 140
コード例 #7
0
    def find_discard_options(self, tiles, closed_hand, melds=None):
        """
        :param tiles: array of tiles in 136 format
        :param closed_hand: array of tiles in 136 format
        :param melds:
        :return:
        """
        if melds is None:
            melds = []

        open_sets_34 = [x.tiles_34 for x in melds]

        tiles_34 = TilesConverter.to_34_array(tiles)
        closed_tiles_34 = TilesConverter.to_34_array(closed_hand)
        is_agari = self.ai.agari.is_agari(tiles_34, self.player.meld_34_tiles)

        results = []
        for hand_tile in range(0, 34):
            if not closed_tiles_34[hand_tile]:
                continue

            tiles_34[hand_tile] -= 1
            waiting, shanten = self.calculate_waits(tiles_34, open_sets_34)
            tiles_34[hand_tile] += 1

            if waiting:
                wait_to_ukeire = dict(
                    zip(waiting, [
                        self.count_tiles([x], closed_tiles_34) for x in waiting
                    ]))
                results.append(
                    DiscardOption(player=self.player,
                                  shanten=shanten,
                                  tile_to_discard=hand_tile,
                                  waiting=waiting,
                                  ukeire=self.count_tiles(
                                      waiting, closed_tiles_34),
                                  wait_to_ukeire=wait_to_ukeire))

        if is_agari:
            shanten = Shanten.AGARI_STATE
        else:
            shanten, _ = self.calculate_shanten(tiles_34, open_sets_34)

        return results, shanten
コード例 #8
0
    def test_calculate_suit_tiles_value(self):
        table = Table()
        player = table.player

        # 0 - 8   man
        # 9 - 17  pin
        # 18 - 26 sou
        results = [[0, 110], [9, 110], [18, 110], [1, 120], [10, 120],
                   [19, 120], [2, 140], [11, 140], [20, 140], [3, 150],
                   [12, 150], [21, 150], [4, 130], [13, 130], [22, 130],
                   [5, 150], [14, 150], [23, 150], [6, 140], [15, 140],
                   [24, 140], [7, 120], [16, 120], [25, 120], [8, 110],
                   [17, 110], [26, 110]]

        for item in results:
            tile = item[0]
            value = item[1]
            option = DiscardOption(player, tile, 0, [], 0)
            self.assertEqual(option.valuation, value)
コード例 #9
0
def test_calculate_honor_tiles_value():
    table = Table()
    player = table.player
    player.dealer_seat = 3
    table.has_aka_dora = False

    # valuable honor, wind of the round
    option = DiscardOption(player, EAST * 4, 0, [], 0)
    assert option.valuation == 120

    # valuable honor, wind of the player
    option = DiscardOption(player, SOUTH * 4, 0, [], 0)
    assert option.valuation == 120

    # not valuable wind
    option = DiscardOption(player, WEST * 4, 0, [], 0)
    assert option.valuation == 100

    # not valuable wind
    option = DiscardOption(player, NORTH * 4, 0, [], 0)
    assert option.valuation == 100

    # valuable dragon
    option = DiscardOption(player, HAKU * 4, 0, [], 0)
    assert option.valuation == 120

    # valuable dragon
    option = DiscardOption(player, HATSU * 4, 0, [], 0)
    assert option.valuation == 120

    # valuable dragon
    option = DiscardOption(player, CHUN * 4, 0, [], 0)
    assert option.valuation == 120

    player.dealer_seat = 0

    # double wind
    option = DiscardOption(player, EAST * 4, 0, [], 0)
    assert option.valuation == 140
コード例 #10
0
    def test_calculate_honor_tiles_value(self):
        table = Table()
        player = table.player
        player.dealer_seat = 3

        # valuable honor, wind of the round
        option = DiscardOption(player, EAST, 0, [], 0)
        self.assertEqual(option.valuation, 120)

        # valuable honor, wind of the player
        option = DiscardOption(player, SOUTH, 0, [], 0)
        self.assertEqual(option.valuation, 120)

        # not valuable wind
        option = DiscardOption(player, WEST, 0, [], 0)
        self.assertEqual(option.valuation, 100)

        # not valuable wind
        option = DiscardOption(player, NORTH, 0, [], 0)
        self.assertEqual(option.valuation, 100)

        # valuable dragon
        option = DiscardOption(player, HAKU, 0, [], 0)
        self.assertEqual(option.valuation, 120)

        # valuable dragon
        option = DiscardOption(player, HATSU, 0, [], 0)
        self.assertEqual(option.valuation, 120)

        # valuable dragon
        option = DiscardOption(player, CHUN, 0, [], 0)
        self.assertEqual(option.valuation, 120)

        player.dealer_seat = 0

        # double wind
        option = DiscardOption(player, EAST, 0, [], 0)
        self.assertEqual(option.valuation, 140)
コード例 #11
0
    def test_calculate_suit_tiles_value_and_tanyao_hand(self):
        table = Table()
        player = table.player
        player.ai.current_strategy = TanyaoStrategy(BaseStrategy.TANYAO,
                                                    player)

        # 0 - 8   man
        # 9 - 17  pin
        # 18 - 26 sou
        results = [[0, 110], [9, 110], [18, 110], [1, 120], [10, 120],
                   [19, 120], [2, 130], [11, 130], [20, 130], [3, 150],
                   [12, 150], [21, 150], [4, 140], [13, 140], [22, 140],
                   [5, 150], [14, 150], [23, 150], [6, 130], [15, 130],
                   [24, 130], [7, 120], [16, 120], [25, 120], [8, 110],
                   [17, 110], [26, 110]]

        for item in results:
            tile = item[0]
            value = item[1]
            option = DiscardOption(player, tile, 0, [], 0)
            self.assertEqual(option.valuation, value)
コード例 #12
0
    def try_to_find_safe_tile_to_discard(self, discard_results):
        self.hand_34 = TilesConverter.to_34_array(self.player.tiles)
        self.closed_hand_34 = TilesConverter.to_34_array(
            self.player.closed_hand)

        threatening_players = self._get_threatening_players()

        # safe tiles that can be safe based on the table situation
        safe_tiles = self.impossible_wait.find_tiles_to_discard(
            threatening_players)

        # first try to check common safe tiles to discard for all players
        if len(threatening_players) > 1:
            against_honitsu = []
            for player in threatening_players:
                if player.chosen_suit:
                    against_honitsu += [
                        self._mark_safe_tiles_against_honitsu(player)
                    ]

            common_safe_tiles = [x.all_safe_tiles for x in threatening_players]
            common_safe_tiles += against_honitsu
            # let's find a common tiles that will be safe against all threatening players
            common_safe_tiles = list(
                set.intersection(*map(set, common_safe_tiles)))
            common_safe_tiles = [
                DefenceTile(x, DefenceTile.SAFE) for x in common_safe_tiles
            ]

            # there is no sense to calculate suji tiles for honitsu players
            not_honitsu_players = [
                x for x in threatening_players if x.chosen_suit is None
            ]
            common_suji_tiles = self.suji.find_tiles_to_discard(
                not_honitsu_players)

            if common_safe_tiles:
                # it can be that safe tile will be mark as "almost safe",
                # but we already have "safe" tile in our hand
                validated_safe_tiles = common_safe_tiles
                for tile in safe_tiles:
                    already_added_tile = [
                        x for x in common_safe_tiles if x.value == tile.value
                    ]
                    if not already_added_tile:
                        validated_safe_tiles.append(tile)

                # first try to check 100% safe tiles for all players
                result = self._find_tile_to_discard(validated_safe_tiles,
                                                    discard_results)
                if result:
                    return result

            if common_suji_tiles:
                # if there is no 100% safe tiles try to check common suji tiles
                result = self._find_tile_to_discard(common_suji_tiles,
                                                    discard_results)
                if result:
                    return result

        # there are only one threatening player or we wasn't able to find common safe tiles
        # let's find safe tiles for most dangerous player first
        # and than for all other players if we failed find tile for dangerous player
        for player in threatening_players:
            player_safe_tiles = [
                DefenceTile(x, DefenceTile.SAFE)
                for x in player.player.all_safe_tiles
            ]
            player_suji_tiles = self.suji.find_tiles_to_discard([player])

            # it can be that safe tile will be mark as "almost safe",
            # but we already have "safe" tile in our hand
            validated_safe_tiles = player_safe_tiles
            for tile in safe_tiles:
                already_added_tile = [
                    x for x in player_safe_tiles if x.value == tile.value
                ]
                if not already_added_tile:
                    validated_safe_tiles.append(tile)

            # better to not use suji for honitsu hands
            if not player.chosen_suit:
                validated_safe_tiles += player_suji_tiles

            result = self._find_tile_to_discard(validated_safe_tiles,
                                                discard_results)
            if result:
                return result

            # try to find safe tiles against honitsu
            if player.chosen_suit:
                against_honitsu = self._mark_safe_tiles_against_honitsu(player)
                against_honitsu = [
                    DefenceTile(x, DefenceTile.SAFE) for x in against_honitsu
                ]

                result = self._find_tile_to_discard(against_honitsu,
                                                    discard_results)
                if result:
                    return result

        # we wasn't able to find safe tile to discard
        logger.info("No safe tiles, try to defence in other ways.")
        logger.info("With such a hand: {}".format(
            TilesConverter.to_one_line_string(self.player.closed_hand)))

        # find honors
        for i, c in enumerate(self.closed_hand_34[27:]):
            if c >= 1:
                logger.info("Defence with honors.")
                logger.info("Discard {}".format(
                    TilesConverter.to_one_line_string([(27 + i) * 4])))
                return DiscardOption(self.player, 27 + i, 7, [],
                                     4)  # it's 27+i instead of i

        # find 19
        for i, c in enumerate(self.closed_hand_34):
            if i % 9 in [0, 8] and c >= 1:
                logger.info("Defence with 19.")
                return DiscardOption(self.player, i, 7, [], 4)

        # find triplets
        for i, c in enumerate(self.closed_hand_34):
            if c >= 3:
                logger.info("Defence with triplets.")
                return DiscardOption(self.player, i, 7, [], 4)

        # find pairs
        for i, c in enumerate(self.closed_hand_34):
            if c >= 2:
                logger.info("Defence with pairs.")
                return DiscardOption(self.player, i, 7, [], 4)

        # find 28
        for i, c in enumerate(self.closed_hand_34):
            if i % 9 in [1, 7] and c >= 1:
                return DiscardOption(self.player, i, 7, [], 4)

        # we really wasn't able to find safe tile to discard
        logger.info("Really cannot defence.")
        return None