Esempio n. 1
0
    def get_chi_dahai(self, chi_action):
        dahai_dic = {}
        chi_pai = Pai.from_str(chi_action['pai'])
        chi_consumed = Pai.from_list(chi_action['consumed'])

        upper_kuikae_exists = False
        lower_kuikae_exists = False
        if chi_consumed[0].number + 1 == chi_consumed[1].number:
            if chi_pai.number + 1 == chi_consumed[0].number and chi_pai.number != 7:
                upper_kuikae_exists = True
            elif chi_pai.number - 2 == chi_consumed[0].number and chi_pai.number != 3:
                lower_kuikae_exists = True
            
        for tehai in self.tehais:
            if tehai.str in dahai_dic:
                continue
            if chi_pai.is_same_symbol(tehai):
                continue
            if upper_kuikae_exists and \
                tehai.type == chi_pai.type and \
                tehai.number - 3 == chi_pai.number:
                    continue
            if lower_kuikae_exists and \
                tehai.type == chi_pai.type and \
                tehai.number + 3 == chi_pai.number:
                    continue
            
            dahai_dic[tehai.str] = ''
        return [d for d in dahai_dic.keys()]
    def calc(cls, result:np.array, board_state:BoardState, player_id:int, candidate_furo:Dict, oracle_enable_flag:bool=False):
        target_index = None
        if candidate_furo["type"] == MjMove.chi.value:
            target_index = 0
        elif candidate_furo["type"] == MjMove.pon.value:
            target_index = 1
        elif candidate_furo["type"] in [MjMove.kakan.value, MjMove.daiminkan.value, MjMove.ankan.value]:
            target_index = 2
        else:
            raise Exception("not intended path")
        result[target_index,:] = 1


        pais = []
        if candidate_furo["type"] == MjMove.ankan.value:
            pais += candidate_furo["consumed"]
        else:
            pais += ([candidate_furo["pai"]] + candidate_furo["consumed"])
        pais = Pai.from_list(pais)

        min_pai_id = min([pai.id for pai in pais])
        result[3,min_pai_id] = 1
        
        contains_red = any([pai.is_red for pai in pais])
        if contains_red:
            result[4,:] = 1
Esempio n. 3
0
class TenpaiAnalysis:
    ALL_YAOCHUS = Pai.from_list([
        "1m","9m","1p","9p","1s","9s","E","S","W","N","P","F","C",
    ])

    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
        

        
        
    
    @property
    def tenpai(self):
        if self.shanten != 0:
            return False
        
        # assert self.shanten.shanten == 0
        return (len(self.pais) % 3 != 1) or (len(self.waited_pais) > 0)
        
    @property
    def waited_pais(self):
        # assert len(self.pais) % 3 == 1, "invalid number of pais"
        # assert self.shanten.shanten == 0, "not tenpai"
        return self.waiting
Esempio n. 4
0
            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
        

        
        
    
    @property
    def tenpai(self):
        if self.shanten != 0:
            return False
        
        # assert self.shanten.shanten == 0
        return (len(self.pais) % 3 != 1) or (len(self.waited_pais) > 0)
        
    @property
    def waited_pais(self):
        # assert len(self.pais) % 3 == 1, "invalid number of pais"
        # assert self.shanten.shanten == 0, "not tenpai"
        return self.waiting



if __name__ == "__main__":
    pais = Pai.from_list([
        "1m","1m","1m","1p","5m","6m","7m","5p","6p","7p","9m","9m","9m",
    ])
    ana = TenpaiAnalysis(pais)
    print(ana.tenpai)
    print(ana.waited_pais)
Esempio n. 5
0
    def update_state(self, action):
        if self.board.previous_action is not None and \
                self.board.previous_action['type'] in [MjMove.dahai.value, MjMove.kakan.value] and \
                'actor' in self.board.previous_action and \
                self.board.previous_action['actor'] != self.id and \
                action['type'] != MjMove.hora.value:
            
            self.extra_anpais.append(Pai.from_str(self.board.previous_action['pai']))





        action_type = action['type']

        if action_type == MjMove.start_game.value:
            if not self.id and 'id' in action:
                self.id = action['id']
                self.name = f"player{self.id}"
            if 'names' in action:
                self.name = action['names'][self.id]
            self.score = 25000
            self.tehais = []
            self.furos = []
            self.ho = []
            self.sutehais = []
            self.extra_anpais = []
            self.reach_state = None
            self.reach_ho_index = None
            self.reach_sutehais_index = None
            self.double_reach = False
            self.ippatsu_chance = False
            self.pao_for_id = None
            self.rinshan = False

        elif action_type == MjMove.start_kyoku.value:
            self.tehais = sorted(Pai.from_list(action['tehais'][self.id]))
            
            self.furos = []
            self.ho = []
            self.sutehais = []
            self.extra_anpais = []
            self.reach_state = "none"
            self.reach_ho_index  = None
            self.reach_sutehais_index = None
            self.double_reach = False
            self.ippatsu_chance = False
            self.pao_for_id = None
            self.rinshan = False

        elif action_type in [
            MjMove.chi.value,
            MjMove.pon.value, 
            MjMove.daiminkan.value, 
            MjMove.ankan.value
            ]:
            self.ippatsu_chance = False
        elif action_type == MjMove.tsumo.value:
            if self.board.previous_action['type'] == MjMove.kakan.value:
                self.ippatsu_chance = False


        if 'actor' in action and action['actor'] == self.id:
            if action_type == MjMove.tsumo.value:
                pai = Pai.from_str(action['pai'])
                self.tehais = sorted(self.tehais)
                self.tehais.append(pai)
            elif action_type == MjMove.dahai.value:
                pai = Pai.from_str(action['pai'])
                self.delete_tehai(pai)
                self.tehais = sorted(self.tehais)
                self.ho.append(pai)
                self.sutehais.append(pai)
                self.ippatsu_chance = False
                self.rinshan = False
                if self.reach == False:
                    self.extra_anpais.clear()
            elif action_type in [
                MjMove.chi.value, 
                MjMove.pon.value, 
                MjMove.daiminkan.value, 
                MjMove.ankan.value
                ]:

                consumed_pais = Pai.from_list(action['consumed'])
                for c in consumed_pais:
                    self.delete_tehai(c)

                furo = {
                        'type':action['type'],
                        'consumed':consumed_pais,   
                    }
                if action_type != MjMove.ankan.value:
                    pai = Pai.from_str(action['pai'])
                    furo['taken'] = pai

                if action_type == MjMove.chi.value:                    
                    furo['pai_id'] = min(
                        Pai.str_to_id(action['pai']),
                        min(
                            Pai.str_to_id(action['consumed'][0]), 
                            Pai.str_to_id(action['consumed'][1]))
                    )
                else:
                    furo['pai_id'] = Pai.str_to_id(action['consumed'][0])

                if action_type == MjMove.ankan.value:
                    furo['target'] = self.id
                else:
                    furo['target'] = action['target']

                self.furos.append(Furo(furo))
                if action_type in [MjMove.daiminkan.value, MjMove.ankan.value]:
                    self.rinshan = True

                # pao
                if action_type in [MjMove.daiminkan.value, MjMove.pon.value]:
                    pai = Pai.from_str(action['pai'])
                    if pai.is_sangenpai():
                        if self.is_daisangen_pao():
                            self.pao_for_id = action['target']
                    elif pai.is_wind():
                        if self.is_daisushi_pao():
                            self.pao_for_id = action['target']
            
            elif action_type == MjMove.kakan.value:
                pai = Pai.from_str(action['pai'])
                self.delete_tehai(pai)
                pon_index = -1
                for i,f in enumerate(self.furos):
                    if f.type == MjMove.pon.value and pai.is_same_symbol(f.taken):
                        pon_index = i
                if pon_index == -1:
                    raise Exception('not have same symbole pon')
                self.furos[pon_index] = Furo({
                    'type':MjMove.kakan.value,
                    'taken':self.furos[pon_index].taken,
                    'consumed':self.furos[pon_index].consumed + [pai],
                    'target':self.furos[pon_index].target,
                    'pai_id':self.furos[pon_index].pai_id,
                })
                self.rinshan = True
            
            elif action_type == MjMove.reach.value:
                self.reach_state = 'declared'
                self.double_reach = self.board.first_turn
            
            elif action_type == MjMove.reach_accepted.value:
                self.reach_state = 'accepted'
                self.reach_ho_index = len(self.ho)-1
                self.reach_sutehais_index = len(self.sutehais)-1
                self.ippatsu_chance = True
                
        
        if 'target' in action and action['target'] == self.id:
            pai = Pai.from_str(action['pai'])
            if action_type in [
                MjMove.pon.value,
                MjMove.chi.value, 
                MjMove.daiminkan.value
                ]:
                taken = self.ho.pop()
                # assert taken == pai
        
        if 'scores' in action:
            self.score = action['scores'][self.id]
    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
Esempio n. 7
0
    "W",
    "N",
    "P",
    "F",
    "C",
] * 4
INITIAL_YAMA.remove('5m')
INITIAL_YAMA.remove('5p')
INITIAL_YAMA.remove('5s')

INITIAL_YAMA.append('5mr')
INITIAL_YAMA.append('5pr')
INITIAL_YAMA.append('5sr')
INITIAL_YAMA = sorted(INITIAL_YAMA)

INITIAL_YAMA = sorted(Pai.from_list(INITIAL_YAMA))


class Yama():
    def __init__(self, seed=None, shuffle=True):
        if not seed:
            random.seed(seed)
        shuffled = copy.copy(INITIAL_YAMA)
        if shuffle:
            random.shuffle(shuffled)
        self._yama = shuffled
        self._pointer = 0
        self._doramarker = []

    def paifu_tsumo(self, pai):
        rest_yama = [y.str for y in self._yama[self._pointer:]]