Пример #1
0
    def is_ready(cls, arr: list, left: list = None):

        # block error parameter
        if not arr:
            raise ValueError("arr is empty")
        length = len(arr)
        if length not in cls.concealed_count:
            raise ValueError(f"concealed length error: {length}")

        if left:
            # left tiles set
            keys = left
        else:
            # total mj set
            MjSet.generate_dictionary()
            keys = [key for key in MjSet.dictionary]

        # need 1 tile
        candidates = []
        for key in keys:
            test = arr + [key]
            test.sort()
            if cls.is_good_concealed(test):
                candidates.append(key)

        if candidates:
            return tuple((True, candidates))
        return tuple((False, []))
Пример #2
0
    def test_mj_set(self):
        mj_set = MjSet(flower=True)
        count = len(mj_set.tiles)
        self.assertEqual(count, 144)

        mj_set = MjSet(flower=False)
        count = len(mj_set.tiles)
        self.assertEqual(count, 136)
Пример #3
0
def test_decide_discard_by_value():
    mj_set = MjSet()
    ai = PlayerAiAdv("bot02")
    for _ in range(3):
        ai.draw(mj_set)
    mj_set.shuffle()
    for _ in range(11):
        ai.draw(mj_set)
    ai.decide_discard_by_value(wall=mj_set)
Пример #4
0
def test_decide_discard_by_remove_melds():
    mj_set = MjSet()
    ai = PlayerAiPro("bot01")
    for _ in range(3):
        ai.draw(mj_set)
    mj_set.shuffle()
    for _ in range(11):
        ai.draw(mj_set)
    ai.decide_discard_by_remove_melds()
Пример #5
0
    def __init__(self,
                 players: list = None,
                 prevailing_wind: str = '东',
                 flower=False):
        if not players:
            raise ValueError("mahjong needs player!")
        self._mj_set = MjSet(flower)
        self.out_of_tiles = False
        self.robbing_a_kong = False
        self.mahjong_on_kong = False
        self._players = players
        self._positions = dict()
        for index, player in enumerate(self._players):
            wind = Suit.get_wind_by_index(index)
            player.position = wind
            self._positions[wind] = player
        self._dealer: Player = self._players[0]
        self._prevailing_wind = prevailing_wind
        self._winner = None
        self.firer = None

        # state machine
        self._state_machine = HandStateMachine()
        self._states = ["begin", "prepared", "playing", "scoring", "end"]
        self._transitions = [
            {
                'trigger': 'prepare',
                'source': 'begin',
                'dest': 'prepared'
            },  # 准备
            {
                'trigger': 'deal',
                'source': 'prepared',
                'dest': 'playing'
            },  # 拿四张
            {
                'trigger': 'mahjong',
                'source': 'playing',
                'dest': 'scoring'
            },  # 胡牌
            {
                'trigger': 'withdraw',
                'source': 'playing',
                'dest': 'scoring'
            },  # 流局
            {
                'trigger': 'score',
                'source': 'scoring',
                'dest': 'end'
            },  # 算分
        ]
        Machine(model=self._state_machine,
                states=self._states,
                transitions=self._transitions,
                initial='begin')
Пример #6
0
def test_convert():
    mj_set = MjSet()
    mj_set.shuffle()
    concealed = []
    for _ in range(13):
        concealed.append(mj_set.draw())
    print(Rule.convert_tiles_to_str(concealed))
    arr = Rule.convert_tiles_to_arr(concealed)
    print(arr)
    tiles = Rule.convert_arr_to_tiles(arr)
    print(Rule.convert_tiles_to_str(tiles))
Пример #7
0
def test_decide_discard_by_left_meld_and_eye():
    mj_set = MjSet()
    ai = PlayerAiAdv("bot02")
    for _ in range(3):
        ai.draw(mj_set)
    mj_set.shuffle()
    for _ in range(11):
        ai.draw(mj_set)
    ai.sort_concealed()
    print("ai.concealed_str =", ai.concealed_str)
    result = ai.decide_discard_by_left_meld_and_eye()
    for x in result:
        print(x,
              Rule.convert_tiles_to_str(Rule.convert_arr_to_tiles(result[x])))
Пример #8
0
 def draw_from_back(self, mj_set: MjSet):
     tile = mj_set.draw_from_back()
     if not tile:
         print("mj_set.draw_from_back() None")
         return None
     while Rule.is_flower(tile) and mj_set.tiles:
         # (self, 'get a flower from back:', tile)
         self.flowers.append(tile)
         self._draw_count += 1
         tile = mj_set.draw_from_back()
         if not tile:
             print("mj_set.draw_from_back() is_flower None")
             return None
     self.add(tile)
     return tile
Пример #9
0
    def test_player_ai_base(self):
        mj_set = MjSet()
        ai = PlayerAi("貂蝉")
        for _ in range(13):
            ai.draw(mj_set)
        self.assertEqual(len(ai.concealed), 13)

        mj_set.shuffle()
        for _ in range(10):
            ai.draw(mj_set)
            tile = ai.decide_discard()
            self.assertIsNotNone(tile)
            ai.discard(tile)

        self.assertEqual(len(ai.concealed), 13)
        self.assertEqual(len(ai.discarded), 10)
Пример #10
0
def test_hand():
    mj_set = MjSet()
    mj_set.shuffle()
    # ai = PlayerAiPro("bot02")
    ai = PlayerAiAdv("bot02")
    for _ in range(13):
        ai.draw(mj_set)
    ai.sort_concealed()
    print("concealed: " + Rule.convert_tiles_to_str(ai.concealed))
    mj_set.shuffle()
    for _ in range(100):
        tile = ai.draw(mj_set)
        print(f"draw: {tile}")
        if Rule.is_mahjong(ai.concealed):
            print("Mahjong!!!")
            break
        tile = ai.decide_discard()
        print(f"discard: {tile}")
        ai.discard(tile)
        ai.sort_concealed()

    print("concealed: " + Rule.convert_tiles_to_str(ai.concealed))
    print("discarded: " + Rule.convert_tiles_to_str(ai.discarded))
    print(f"strategies: {ai.strategies}")
    print(f"strategies_time: {ai.strategies_time}")
    print("draw count: " + str(ai.draw_count))
Пример #11
0
def test_is_ready():
    mj_set = MjSet()
    concealed = []
    for _ in range(4):
        concealed.append(mj_set.draw())
        concealed.append(mj_set.draw())
        concealed.append(mj_set.draw())
        mj_set.draw()
    concealed.append(mj_set.draw())
    print(Rule.convert_tiles_to_str(concealed))
    print(Rule.is_ready(concealed))
Пример #12
0
    def draw(self, mj_set: MjSet):
        tile = mj_set.draw()
        if not tile:
            print("mj_set.draw() None")
            return None
        if not Rule.is_flower(tile):
            self.add(tile)
            self._draw_count += 1
            return tile

        print(self, 'get a flower:', tile)
        self.flowers.append(tile)
        tile = self.draw_from_back(mj_set)
        print("draw_from_back:", tile)
        return tile
Пример #13
0
    def test_ai_mahjong(self):
        mj_set = MjSet()
        ai = PlayerAi("西施")
        mj_set.shuffle()
        for _ in range(13):
            ai.draw(mj_set)
        ai.sort_concealed()

        mj_set.shuffle()
        mahjong = False
        for _ in range(100):
            tile = ai.draw(mj_set)
            if Rule.is_mahjong(ai.concealed):
                mahjong = True
                break
            tile = ai.decide_discard()
            ai.discard(tile)
            ai.sort_concealed()

        # self.assertTrue(mahjong)
        self.assertGreater(len(ai.concealed), 0)
Пример #14
0
    def value_arr(cls,
                  arr: list,
                  left=None,
                  players_count: int = 1,
                  deep: int = 2) -> MjValue:
        if not arr:
            value = MjValue(meld=0,
                            orphan=0,
                            count_down=0,
                            is_ready=False,
                            waiting=[])
            return value

        if not left:
            # total tile types, same chance
            MjSet.generate_dictionary()
            left = [key for key in MjSet.dictionary]
        count_all = len(left)

        # basic info
        melds_count = cls.get_most_melds_length_from_arr(arr)
        orphans_arr = cls.get_orphans(arr)
        orphans_count = len(orphans_arr)
        result = MjValue(meld=melds_count, orphan=orphans_count)

        # is ready test
        ready_info = cls.is_ready(arr)
        is_ready = ready_info[0]
        if is_ready:
            combins = ready_info[1]
            if not combins:
                combins = []
            waiting = []
            fail_chance = 1
            for x in combins:
                chance = cls.chance_of_combin_and_wall(
                    wall=left, combin=[x], players_count=players_count)
                fail_chance *= 1 - chance
                value = 1
                w = ([x], chance, value)
                waiting.append(w)
            total_chance = 1 - fail_chance
            result.is_ready = is_ready
            result.listening = waiting
            result.mahjong_chance = total_chance

        # count down test
        begin = time()
        count_down_result = cls.countdown_of_ready(arr, left=left, deep=deep)
        # ("count_down duration = %0.2f" % (time() - begin))
        # ("count_down_result =", count_down_result)
        count_down = count_down_result[0]
        result.count_down = count_down
        if count_down > max(cls.concealed_count):
            return result
        combins = count_down_result[1]
        waiting = []
        value = 1
        total_chance = 0
        if count_down <= 3 and len(combins) > 0:
            for combin in combins:
                chance = cls.chance_of_combin_and_wall(
                    wall=left, combin=combin, players_count=players_count)
                total_chance += chance
                w = (combin, chance, value)
                waiting.append(w)
        result.waiting = waiting
        result.waiting_chance = total_chance
        return result
Пример #15
0
    def countdown_of_ready(cls,
                           concealed: list,
                           left: list = None,
                           deep: int = 2):
        arr = concealed[:]
        arr.sort()
        # block error parameter
        if left is None:
            left = []
        if not arr:
            raise ValueError("arr is empty")
        length = len(arr)
        if length not in cls.concealed_count:
            raise ValueError(f"concealed length error: {length}")
        if deep < 1 or 4 < deep:
            raise ValueError(f"deep {deep}, should be in [1,2,3,4,5]")

        if left:
            # left tiles set
            keys = left
        else:
            # total mj set
            MjSet.generate_dictionary()
            keys = [key for key in MjSet.dictionary]

        # remove the keys that not relative to 'arr' elements
        for key in keys[::-1]:
            if key not in arr \
                    and key + 1 not in arr \
                    and key - 1 not in arr:
                keys.remove(key)

        # need how many tiles?
        candidates = []
        countdown = []

        # best combinations for arr
        combins = MjMath.get_best_meld_combins_from_arr(arr)
        for combin in combins:
            completed = []
            for meld in combin:
                completed = completed + meld
            shorter = MjMath.list_sub(arr, completed)
            shorter_len = len(shorter)
            if shorter_len >= 11:
                return (999, None)
            for drop in range(1, deep + 1):
                if drop >= shorter_len:
                    continue
                for remove in combinations(shorter, drop):
                    remains = MjMath.list_sub(shorter, list(remove))
                    for add in combinations(keys, drop + 1):
                        test = remains + list(add)
                        more = list(add)
                        more.sort()
                        if more in candidates:
                            continue
                        if MjMath.has_orphan(test):
                            continue
                        if len(set(test)) == len(test):
                            continue
                        test.sort()
                        if cls.is_good_concealed(test):
                            candidates.append(more)
                            countdown.append(drop + 1)

        if candidates:
            return tuple((min(countdown), candidates))

        # default
        return tuple((999, None))
Пример #16
0
    def prepare(self):
        self._mj_set = MjSet()
        self.choice_dealer()


        self._state_machine.prepare()
Пример #17
0
class Hand(object):
    __slots__ = ("_state_machine", "_machine", "_states", "_transitions",
                 "_mj_set", "_players", "_prevailing_wind", "_dealer",
                 '_positions', '_winner', 'firer', 'out_of_tiles',
                 'robbing_a_kong', 'mahjong_on_kong')

    def __init__(self,
                 players: list = None,
                 prevailing_wind: str = '东',
                 flower=False):
        if not players:
            raise ValueError("mahjong needs player!")
        self._mj_set = MjSet(flower)
        self.out_of_tiles = False
        self.robbing_a_kong = False
        self.mahjong_on_kong = False
        self._players = players
        self._positions = dict()
        for index, player in enumerate(self._players):
            wind = Suit.get_wind_by_index(index)
            player.position = wind
            self._positions[wind] = player
        self._dealer: Player = self._players[0]
        self._prevailing_wind = prevailing_wind
        self._winner = None
        self.firer = None

        # state machine
        self._state_machine = HandStateMachine()
        self._states = ["begin", "prepared", "playing", "scoring", "end"]
        self._transitions = [
            {
                'trigger': 'prepare',
                'source': 'begin',
                'dest': 'prepared'
            },  # 准备
            {
                'trigger': 'deal',
                'source': 'prepared',
                'dest': 'playing'
            },  # 拿四张
            {
                'trigger': 'mahjong',
                'source': 'playing',
                'dest': 'scoring'
            },  # 胡牌
            {
                'trigger': 'withdraw',
                'source': 'playing',
                'dest': 'scoring'
            },  # 流局
            {
                'trigger': 'score',
                'source': 'scoring',
                'dest': 'end'
            },  # 算分
        ]
        Machine(model=self._state_machine,
                states=self._states,
                transitions=self._transitions,
                initial='begin')
        # print(self.state)

    def __str__(self):
        return '\r\n'.join([f'{x.position}:{x}' for x in self._players])

    @property
    def state(self):
        return self._state_machine.state

    @property
    def players(self):
        return self._players

    @property
    def mj_set(self):
        return self._mj_set

    @property
    def dealer(self):
        return self._dealer

    @property
    def winner(self):
        return self._winner

    @property
    def positions(self):
        return self._positions

    def _shuffle(self):
        self._mj_set.shuffle()

    def _reset_players(self):
        for player in self._players:
            player.reset()

    def _get_next_player(self, current):
        bingo = False
        for player in cycle(self.players):
            if bingo:
                return player
            if current == player:
                bingo = True
        raise LookupError(f"can not find next player for {current}")

    def prepare(self):
        self._reset_players()
        self._state_machine.prepare()
        self._shuffle()

    def deal(self):
        for _ in range(max(MjMath.concealed_count) // 4):
            for player in self._players:
                player.draw_stack(self._mj_set)

        for player in self.players:
            player.draw(self._mj_set)
            player.sort_concealed()

        self._state_machine.deal()

    def play(self):

        current = self._dealer  # the current player
        player = current
        before = None  # the previous player
        current_discard = None  # discard tile by previous player

        for player in self._players:
            print(f'{player.position}: {player}')
        print("=" * 40)

        have_winner = False
        while not have_winner:

            if current_discard:
                wind = current.position
                player = current
                for index in range(3):
                    # test for hu by other
                    test = player.concealed[:]
                    test.append(current_discard)
                    if Rule.is_mahjong(test):
                        player.concealed.append(current_discard)
                        print(f"winner is {player}, by {before}")
                        self._winner = player
                        self._state_machine.mahjong()
                        have_winner = True
                        break
                    wind = Suit.get_next_wind(wind)
                    player = self.positions[wind]

            if have_winner:
                break

            # interrupted by exposed kong / pong / chow
            interrupted = False
            if current_discard:

                # Melding another for current, +1, +2 players
                player = None

                # try kong ( must have tiles ):
                if self.mj_set.tiles:
                    wind = current.position
                    player = current
                    for index in range(3):
                        if player.try_exposed_kong(tile=current_discard,
                                                   owner=before,
                                                   mj_set=self._mj_set):
                            interrupted = True
                            break
                        wind = Suit.get_next_wind(wind)
                        player = self.positions[wind]

                # try pong:
                if not interrupted:
                    wind = current.position
                    player = current
                    for index in range(3):
                        if player.try_exposed_pong(tile=current_discard,
                                                   owner=before):
                            interrupted = True
                            break
                        wind = Suit.get_next_wind(wind)
                        player = self.positions[wind]

                # try chow:
                if not interrupted:
                    wind = current.position
                    player = current
                    if player.try_exposed_chow(current_discard, before):
                        interrupted = True

                if not interrupted:
                    before.put_on_desk(current_discard)
            # end if current_discard:

            if interrupted:
                current = player
            else:
                # test for out of tiles
                if not self.mj_set.tiles:
                    print("out of tiles!")
                    self._winner = None
                    self._state_machine.withdraw()
                    break

                # draw
                current.draw(self._mj_set)
                # test for hu by self
                if Rule.is_mahjong(current.concealed):
                    print(f"winner is {current}, by self!")
                    self._winner = current
                    self._state_machine.mahjong()
                    break

                # self kong
                current.try_conceal_kong(self.mj_set)

            if isinstance(current, PlayerAiAdv):
                wall = self._mj_set.tiles
                tile = current.decide_discard(players_count=len(self.players),
                                              wall=wall)
            else:
                tile = current.decide_discard()
            print(current, 'discard:', tile)
            current.discard(tile)
            current.sort_concealed()
            current_discard = tile

            # (f'{current} discard {tile}')
            # (Rule.convert_tiles_to_str(current.concealed))

            # next player
            next = self._get_next_player(current)
            before = current
            current = next
        # end while

        print("tiles left :", len(self.mj_set.tiles))
        for player in self.players:
            if player == self.winner:
                print(f"winner {player}: ",
                      Rule.convert_tiles_to_str(player.concealed))
            else:
                print(f"{player}: ",
                      Rule.convert_tiles_to_str(player.concealed))

        left = Rule.convert_tiles_to_arr(self.mj_set.tiles)
        left.sort()
        print(self.players[0].strategies)
        print(self.players[0].strategies_time)

    # end def play()

    def score(self):
        # score the play result
        self._state_machine.score()
        pass