コード例 #1
0
class ShantenFuroAppendFeature(FuroAppendFeature):
    
    shanten_analysis = RsShantenAnalysis()

    @classmethod
    def get_length(cls)->int:
        return 8 # 1:furo can decrease shanten, 0~5: current_shanten

    @classmethod
    def calc(cls, result:np.array, board_state:BoardState, player_id:int, candidate_furo:Dict, oracle_enable_flag:bool=False):
        player_tehai = board_state.tehais[player_id]
        

        tehais = [0] * 34
        for pai in player_tehai:
            tehais[pai.id] += 1
        
        furo_num = len(board_state.furos[player_id])
        current_shanten = cls.shanten_analysis.calc_shanten(tehais, furo_num)

        # ignore kan
        if candidate_furo["type"] not in [MjMove.ankan.value, MjMove.daiminkan.value, MjMove.kakan.value]:
            add_pai = Pai.from_str(candidate_furo["pai"])
            tehais[add_pai.id] += 1
            added_shanten = cls.shanten_analysis.calc_shanten(tehais, furo_num)
            if current_shanten > added_shanten:
                result[0,:,0] = 1
        
        offset = 1
        target_channel = max(0,min(6,current_shanten)) + offset
        result[target_channel, :, 0] = 1
コード例 #2
0
ファイル: debug_dfs.py プロジェクト: rick0000/mjaigym
def main():
    dfs = Dfs()
    shanten_analysis = RsShantenAnalysis()
    tehai = [
        0,
        2,
        2,
        2,
        2,
        0,
        0,
        0,
        0,
        0,
        0,
        2,
        0,
        0,
        2,
        0,
        0,
        0,
        0,
        0,
        1,
        0,
        0,
        1,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
    ]
    tehai = [
        0, 2, 0, 1, 1, 1, 2, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
        0, 0, 0, 0, 0, 0, 2, 0, 0, 0
    ]

    shanten_noraml, _, shanten_chitoitsu = shanten_analysis.calc_all_shanten(
        tehai, 0)
    # result = dfs.dfs_with_score_chitoitsu(tehai, [], 3, shanten_chitoitsu)
    # print(len(result))
    # print(result)
    for i in range(1):
        result = dfs.dfs_with_score_normal(tehai, [], 2, shanten_noraml)
    print(len(result))
    for r in result:
        print(r)
コード例 #3
0
ファイル: tenpai_analysis.py プロジェクト: rick0000/mjaigym
    def __init__(self, pais):
        self.pais = pais
        self.shanten_analysis = RsShantenAnalysis()
        self.tehai = [0]*34
        for p in pais:
            self.tehai[p.id] += 1
        
        self.furo_num = (14 - len(pais)) // 3
        
        self.shanten = self.shanten_analysis.calc_shanten(self.tehai, self.furo_num)

        self.waiting = []
        if self.shanten != 0:
            return

        for waiting_id in range(34):
            if self.tehai[waiting_id] == 4:
                # already use all pais
                continue

            self.tehai[waiting_id] += 1
            if self.shanten_analysis.calc_shanten(self.tehai, self.furo_num) == -1:
                self.waiting.append(Pai.from_id(waiting_id))
            self.tehai[waiting_id] -= 1
コード例 #4
0
 def __init__(self):
     self.score_cache = {}
     self.shanten_analysis = RsShantenAnalysis()
コード例 #5
0
ファイル: player.py プロジェクト: rick0000/mjaigym
 def __init__(self, board, seat=None):
     self.id = seat
     self.board = board
     self.reset()
     self.shanten_analysis = RsShantenAnalysis()
コード例 #6
0
class HorapointDfsFeature(Feature):
    """this class has unusual interface, because handling dfs object cache.
    """
    shanten_analysis = RsShantenAnalysis()
    target_points = [1000, 2000, 3900, 7700, 12000, 16000, 18000, 24000, 32000]
    DEPTH = 2
    
    YAKU_CH = len(YAKU_CHANNEL_MAP) * DEPTH # depth 1, depth 2, depth 3.
    POINT_CH = len(target_points) * DEPTH
    ONE_PLAYER_LENGTH = YAKU_CH + POINT_CH

    @classmethod
    def get_length(cls)->int:
        
        return cls.ONE_PLAYER_LENGTH * 4
    

    @classmethod
    def calc(cls, result:np.array, board_state:BoardState, player_id:int, oracle_feature_flag:bool, dfs=Dfs()):
        
        for i_from_player, seat_alined_player_id in cls.get_seat_order_ids(player_id):
            
            if not oracle_feature_flag:
                if i_from_player != 0:
                    # print("skip, oracle feature disabled.")
                    continue

            player_tehai = board_state.tehais[seat_alined_player_id]

            # # 比較のためターチャの特徴量は無視
            # if i_from_player != 0:
            #     continue

            nums = [0] * 34
            for t in player_tehai:
                nums[t.id] += 1
            tehai_akadora_num = len([p for p in player_tehai if p.is_red])

            player_furos = board_state.furos[seat_alined_player_id]
            
            # num 14 check
            if len(player_tehai) + len(player_furos) * 3 != 14:
                # return
                pass
            
            furo_akadora_num = 0
            for furo in player_furos:
                furo_akadora_num += len([p for p in furo.pais if p.is_red])

            
            # # ignore -1, more than 2
            dfs_result = None
            
            oya = board_state.oya == seat_alined_player_id
            bakaze = board_state.bakaze
            jikaze = board_state.jikaze[seat_alined_player_id]
            doras = [p.succ for p in Pai.from_list(board_state.dora_markers)]
            uradoras = []
            num_akadoras = tehai_akadora_num + furo_akadora_num
            
            shanten_normal, shanten_kokushi, shanten_chitoitsu = cls.shanten_analysis.calc_all_shanten(nums, len(player_furos))

            results = []
            if 0 <= shanten_normal <= cls.DEPTH-1:
                normal_results = dfs.dfs_with_score_normal(
                    nums,
                    player_furos,
                    cls.DEPTH,
                    oya=oya,
                    bakaze=bakaze,
                    jikaze=jikaze,
                    doras=doras,
                    uradoras=uradoras,
                    num_akadoras=num_akadoras,
                    shanten_normal=shanten_normal,
                )
                results.extend(normal_results)
        
            if 0 <= shanten_chitoitsu <= cls.DEPTH-1:
                chitoitsu_results = dfs.dfs_with_score_chitoitsu(
                    nums, 
                    player_furos, 
                    cls.DEPTH, 
                    oya=oya,
                    bakaze=bakaze,
                    jikaze=jikaze,
                    doras=doras,
                    uradoras=uradoras,
                    num_akadoras=num_akadoras,
                    shanten_chitoitsu=shanten_chitoitsu,
                )
                results.extend(chitoitsu_results)
                    
            if 0 <= shanten_kokushi <= cls.DEPTH-1:
                kokushi_results = dfs.dfs_with_score_kokushi(
                    nums, 
                    player_furos, 
                    cls.DEPTH, 
                    oya=oya,
                    shanten_kokushi=shanten_kokushi,
                )
                results.extend(kokushi_results)

            results = [r for r in results if r.valid()]

            if len(results) == 0:
                continue


            if i_from_player == 0:
                # ある牌を打牌(マイナス)した際に和了可能な役か。
                # プレーヤー(14枚形)の際に適用。
                for i in range(34):
                    i_dahaiable_horas = [r for r in results if r.is_dahaiable(i)]
                    if len(i_dahaiable_horas) == 0:
                        continue
                    
                    yaku_dist_set = set()
                    point_dist_set = set()
                    for hora in i_dahaiable_horas:
                        dist = hora.distance()
                        point = hora.get_point()
                        
                        for yaku, yaku_fan in hora.get_yakus():
                            if yaku == "dora":
                                if yaku_fan > 12:
                                    yaku_fan = 12
                                for dora_num in range(1,yaku_fan+1):
                                    yaku_dist_set.add((yaku+str(dora_num), dist))
                            else:
                                yaku_dist_set.add((yaku, dist))
                            point_dist_set.add((point, dist))
                    
                    for (yaku, dist) in yaku_dist_set:
                        # add yaku feature
                        if yaku in YAKU_CHANNEL_MAP:
                            target_channel = YAKU_CHANNEL_MAP[yaku] + ((dist-1) * len(YAKU_CHANNEL_MAP))
                            player_offset = i_from_player*cls.ONE_PLAYER_LENGTH
                            result[player_offset + target_channel,i,0] = 1

                    for (point, dist) in point_dist_set:
                        # add hora point feature
                        for point_index, target_point in enumerate(cls.target_points):
                            if point >= target_point:
                                target_channel = cls.YAKU_CH + point_index + (dist-1) * len(cls.target_points)
                                
                                player_offset = i_from_player*cls.ONE_PLAYER_LENGTH
                                result[player_offset + target_channel,i,0] = 1
                    

            else:
                # ある牌を追加した際に和了可能な役か。
                # 自分以外のプレーヤー(13枚系)の際に適用。

                for i in range(34):
                    i_need_horas = [r for r in results if r.is_tsumoneed(i)]
                    if len(i_need_horas) == 0:
                        continue
                    
                    yaku_dist_set = set()
                    point_dist_set = set()
                    for hora in i_need_horas:
                        dist = hora.distance()
                        point = hora.get_point()
                        for yaku, yaku_fan in hora.get_yakus():
                            if yaku == "dora":
                                if yaku_fan > 12:
                                    yaku_fan = 12
                                for dora_num in range(1,yaku_fan+1):
                                    yaku_dist_set.add((yaku+str(dora_num), dist))
                            else:
                                yaku_dist_set.add((yaku, dist))
                            point_dist_set.add((point, dist))
                        
                    for (yaku, dist) in yaku_dist_set:
                        # add yaku feature
                        if yaku in YAKU_CHANNEL_MAP:
                            target_channel = YAKU_CHANNEL_MAP[yaku] + ((dist-1) * len(YAKU_CHANNEL_MAP))
                            player_offset = i_from_player*cls.ONE_PLAYER_LENGTH
                            result[player_offset + target_channel,i,0] = 1

                    for (point, dist) in point_dist_set:
                        # add hora point feature
                        for point_index, target_point in enumerate(cls.target_points):
                            if point >= target_point:
                                target_channel = cls.YAKU_CH + point_index + (dist-1) * len(cls.target_points)
                                
                                player_offset = i_from_player*cls.ONE_PLAYER_LENGTH
                                result[player_offset + target_channel,i,0] = 1
コード例 #7
0
ファイル: max_ukeire_client.py プロジェクト: rick0000/mjaigym
class Node():
    shanten_analysis = RsShantenAnalysis()

    def __init__(self, tehais, taken=None):

        # copy tehai state
        self.tehai = [0] * 34
        for t in tehais:
            self.tehai[t.id] += 1
        self.furo_num = (14 - sum(self.tehai)) // 3

        if taken:
            self.tehai[taken.id] += 1

    def valid_change(self, sub_id, add_id):
        return not (self.tehai[add_id] >= 4 or self.tehai[sub_id] <= 0)

    def valid_sub(self, sub_id):
        return self.tehai[sub_id] > 0

    def valid_add(self, add_id):
        return self.tehai[add_id] < 4

    def change(self, sub_id, add_id):
        # assert self.valid_change(sub_id, add_id)
        self.tehai[sub_id] -= 1
        self.tehai[add_id] += 1

    def sub(self, sub_id):
        # assert self.valid_sub(sub_id)
        self.tehai[sub_id] -= 1

    def add(self, add_id):
        # assert self.valid_add(add_id)
        self.tehai[add_id] += 1

    def calc_ukeire_num(self, rest_num=None):
        if rest_num is None:
            rest_num = [4] * 34
            for i in range(34):
                rest_num[i] -= self.tehai[i]

        target_shanten = self.shanten
        target_cache = {}
        node = copy.deepcopy(self)

        for i in range(34):  # da loop
            if self.tehai[i] == 0:
                continue
            for j in range(34):  # tsumo loop
                if rest_num[j] == 0:
                    continue
                if i == j:
                    continue
                if node.valid_change(sub_id=i, add_id=j):
                    node.change(i, j)
                    changed_shanten = node.shanten
                    node.change(j, i)

                    if changed_shanten < target_shanten:
                        target_cache = {}
                        target_shanten = changed_shanten
                    if changed_shanten == target_shanten:
                        target_cache[(i, j)] = changed_shanten

        valid_nums = [0] * 34
        for n in target_cache:
            da_index = n[0]
            tsumo_index = n[1]
            valid_nums[da_index] += rest_num[tsumo_index]

        return valid_nums

    @property
    def shanten(self):
        normal, chitoitsu, kokushi = self.shanten_analysis.calc_all_shanten(
            self.tehai, self.furo_num)
        if chitoitsu < 1 and chitoitsu < normal and chitoitsu < kokushi:
            return chitoitsu
        if kokushi < 3 and kokushi < normal and kokushi < chitoitsu:
            return kokushi
        else:
            return normal
コード例 #8
0
ファイル: max_ukeire_client.py プロジェクト: rick0000/mjaigym
class MaxUkeireClient(Client):
    ShantenAnalyser = RsShantenAnalysis()

    def __init__(self, id=None, name=None):
        if name is None:
            name = f"MaxUkeire{id}"
        super(MaxUkeireClient, self).__init__(id, name)

    def clone(self):
        return self

    def think(self, board_state: BoardState):
        message = board_state.previous_action

        if message['type'] == MjMove.start_game.value and 'id' in message:
            self.id = message['id']
        if len(board_state.possible_actions[self.id]) == 1:
            return board_state.possible_actions[self.id][-1]

        candidates = board_state.possible_actions[self.id]
        if 'actor' in board_state.previous_action and \
            board_state.previous_action['actor'] == self.id:
            return self.think_on_tsumo(board_state, candidates)
        else:
            return self.think_on_other_dahai(board_state, candidates)

    def think_on_tsumo(self, board_state: BoardState, candidates: List[Dict]):

        my_tehais = board_state.tehais[self.id]
        rest_in_view = board_state.restpai_in_view[self.id]

        # if can hora, always do hora
        hora_candidates = [
            c for c in candidates if c['type'] == MjMove.hora.value
        ]
        if len(hora_candidates) == 1:
            return hora_candidates[0]

        # if can reach, always do reach
        reach_candiadtes = [
            c for c in candidates if c['type'] == MjMove.reach.value
        ]
        if len(reach_candiadtes) == 1:
            return reach_candiadtes[0]

        # dahai think
        dahai_candiadtes = [
            c for c in candidates if c['type'] == MjMove.dahai.value
        ]

        # calclate the number of shanten reducing tsumo with each dahai
        node = Node(my_tehais)
        valid_nums = node.calc_ukeire_num(rest_num=rest_in_view)

        max_valid_dahai_action = None
        max_valid_num = -1

        for candidate in dahai_candiadtes:
            valid_dahai_id = Pai.str_to_id(candidate['pai'])
            valid_num = valid_nums[valid_dahai_id]

            if max_valid_num < valid_num:
                max_valid_dahai_action = candidate
                max_valid_num = valid_num

        return max_valid_dahai_action

    def think_on_other_dahai(self, board_state: BoardState,
                             candidates: List[Dict]):

        my_tehais = board_state.tehais[self.id]

        # if there is only one candidate, return that.
        if len(candidates) == 1:
            return candidates[-1]

        # if can hora, always do hora
        hora_candidates = [
            c for c in candidates if c['type'] == MjMove.hora.value
        ]
        if len(hora_candidates) == 1:
            return hora_candidates[0]

        # if there is other player reach, don't furo.
        if any(board_state.reach):
            none_candidates = [
                c for c in candidates if c['type'] == MjMove.none.value
            ]
            if len(none_candidates) == 0:
                raise Exception("not intended path, none candidate not found.")
            else:
                return none_candidates[0]

        # this path assumes there is no other player reach

        # shanten reduceable yakuhai pon is executed.
        furo_types = [
            MjMove.daiminkan.value, MjMove.pon.value, MjMove.chi.value
        ]
        furo_candidates = [c for c in candidates if c['type'] in furo_types]

        for candidate in furo_candidates:
            if candidate["type"] == MjMove.pon.value:
                candidate_pai = Pai.from_str(candidate["pai"])
                is_dragon = candidate_pai.is_sangenpai()
                is_jikaze = candidate_pai.str == board_state.jikaze
                is_bakaze = candidate_pai.str == board_state.bakaze

                # if is_yakuhai, 20% do pon
                if is_dragon or is_jikaze or is_bakaze:
                    before_node = Node(board_state.tehais[self.id])
                    executed_node = Node(board_state.tehais[self.id],
                                         candidate_pai)
                    if before_node.shanten > executed_node.shanten and\
                            random.random() < 0.2:
                        return candidate

            # if already furo, and shanten reduceable, 60% furo.
            if len(board_state.furos[self.id]) > 0:
                candidate_pai = Pai.from_str(candidate["pai"])
                before_node = Node(board_state.tehais[self.id])
                executed_node = Node(board_state.tehais[self.id],
                                     candidate_pai)
                if before_node.shanten > executed_node.shanten and\
                        random.random() < 0.6:
                    return candidate

        none_candidates = [
            c for c in candidates if c['type'] == MjMove.none.value
        ]
        if len(none_candidates) == 0:
            raise Exception("not intended path, none candidate not found.")
        else:
            return none_candidates[0]

    @classmethod
    def calclate_valid_nums(cls, tehais):
        tehai = np.zeros(34, dtype=int)
        for t in tehais:
            tehai[t.id] += 1

        # assert all([t >= 0 and t <= 4 for t in tehai])

        start = datetime.datetime.now()

        # calclate 2 times change
        moves = {}
        for i in range(34):  # remove
            for j in range(34):  # add
                if i == j:
                    continue
                for k in range(34):  # add
                    if i == k:
                        continue

                    tmp_tehai = tehai.copy()
                    tmp_tehai[i] -= 1
                    tmp_tehai[j] += 1
                    tmp_tehai[k] += 1
                    if tmp_tehai[i] >= 0 and tmp_tehai[j] <= 4 and \
                        tmp_tehai[k] <= 4:
                        moves[(i, j,
                               k)] = cls.ShantenAnalyser.calc_all_shanten(
                                   tmp_tehai, 0)

        end = datetime.datetime.now()
        return tehai