コード例 #1
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)
コード例 #2
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
コード例 #3
0
ファイル: tenpai_analysis.py プロジェクト: rick0000/mjaigym
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
コード例 #4
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
コード例 #5
0
 def __init__(self):
     self.score_cache = {}
     self.shanten_analysis = RsShantenAnalysis()
コード例 #6
0
class Dfs():
    def __init__(self):
        self.score_cache = {}
        self.shanten_analysis = RsShantenAnalysis()

    def dfs_hora(self, depth, tehais, furos, points, rest=None):
        """return pai change combinations which achive hora and hora score >= points.

        Args:
            depth ([type]): [description]
            tehais ([type]): [description]
            furos ([type]): [description]
            points ([type]): [description]
            rest ([type], optional): [description]. Defaults to None.

        Returns:
            [type]: [description]
        """

        if rest is None:
            rest = [4] * 34
            for i in range(34):
                rest[i] -= tehais[i]

        result = {}
        # initialize
        for point in points:
            result[point] = [0] * 34

        if sum(tehais) % 3 == 2:
            for point in points:
                dfs_results = self._dfs_hora(depth, tehais, furos, point, rest)
                result[point] = np.array(dfs_results, dtype="int8")

        elif sum(tehais) % 3 == 1:
            for point in points:
                result[point] = np.zeros((34, ), dtype="int8")
        else:
            raise Exception("not intended path")

        return result

    def _dfs_hora(self, depth, tehais, furos, point, rest, added=None):
        """returns [bool] * 34
        """

        stack = deque()
        furo_num = len(furos)
        base_shanten = self.shanten_analysis.calc_shanten(tehais, furo_num)

        if added:
            initial_history = [tuple((None, added))]
        else:
            initial_history = []

        stack.append((base_shanten, depth, tehais, furos, initial_history))

        i_found = [False] * 34

        while len(stack) > 0:
            target_shanten, target_depth, target_tehais, target_furos, target_history = stack.pop(
            )

            if target_depth <= 0:
                continue

            if len(target_history) > 0 and i_found[target_history[0][0]]:
                continue

            # apply history for rest
            for h in target_history:
                rest[h[1]] -= 1

            # sub
            for i in range(34):
                temp_i_found = False
                if target_tehais[i] == 0:
                    continue

                target_tehais[i] -= 1
                shanten = self.shanten_analysis.calc_shanten(
                    target_tehais, furo_num)
                if shanten - depth > -1:
                    # cannot reach hora
                    target_tehais[i] += 1
                    continue
                if target_shanten < shanten:
                    target_tehais[i] += 1
                    continue

                rest_depth = target_depth - 1
                # add
                for j in range(34):
                    if temp_i_found:
                        break
                    if target_tehais[j] == 4 or rest[j] == 0:
                        continue

                    target_tehais[j] += 1
                    target_history.append(tuple((i, j)))

                    changed_shanten = self.shanten_analysis.calc_shanten(
                        target_tehais, furo_num)

                    if changed_shanten - (rest_depth) <= -1:
                        if changed_shanten == -1:
                            # if hora, treat as leaf
                            key = get_key(target_tehais, target_furos, j)
                            if key not in self.score_cache:
                                hora = get_score(target_tehais, target_furos,
                                                 target_history[-1][1])
                                self.score_cache[key] = hora.points
                            score = self.score_cache[key]
                            if score >= point:
                                i_found[target_history[0][0]] = True
                                temp_i_found = True

                        else:
                            stack.append((changed_shanten, rest_depth,
                                          copy.copy(target_tehais),
                                          copy.copy(target_furos),
                                          copy.copy(target_history)))

                    target_history.pop()
                    target_tehais[j] -= 1

                target_tehais[i] += 1

            # restore rest
            for h in target_history:
                rest[h[1]] += 1

        return i_found
コード例 #7
0
ファイル: player.py プロジェクト: rick0000/mjaigym
 def __init__(self, board, seat=None):
     self.id = seat
     self.board = board
     self.reset()
     self.shanten_analysis = RsShantenAnalysis()
コード例 #8
0
ファイル: player.py プロジェクト: rick0000/mjaigym
class Player():
    def __init__(self, board, seat=None):
        self.id = seat
        self.board = board
        self.reset()
        self.shanten_analysis = RsShantenAnalysis()

    def reset(self):
        self.score = 25000
        self.tehais = []
        self.furos = []
        self.ho = [] # 鳴かれた牌を含まない
        self.sutehais = [] # 鳴かれた牌を含む
        self.extra_anpais = [] # sutehais以外のこのプレーヤーに対する安牌
        self.reach_state = "none"
        self.reach_ho_index = None
        self.reach_sutehais_index = None
        self.double_reach = False
        self.pao_for_id = None
        self.ippatsu_chance = False
        self.rinshan = False


    @property
    def str_sep_tehais(self):
        return [t.str for t in self.tehais]

    @property
    def red_dora_num(self):
        num = 0
        num += len([t for t in self.tehais if t.is_red])
        for furo in self.furos:
            num += len([p for p in furo.pais if p.is_red])
            
        return num

    @property
    def furo_open_red_dora_num(self):
        num = 0
        for furo in self.furos:
            num += len([p for p in furo.pais if p.is_red])    
        return num


    @property
    def str_tehais(self):
        # add hidden tehai
        type_history = set()
        show_str_reverse = []
        
        if (not self.tehais) or len(self.tehais) == 0:
            return ''
        
        if len(self.tehais) % 3 == 2:
            # tsumo timing
            show_str_reverse.append(self.tehais[-1].str)
            show_str_reverse.append("  ")
            loop_tehais = self.tehais[:-1]
        else:
            loop_tehais = self.tehais
            
        for t in reversed(loop_tehais):
            if t.is_number() and t.type in type_history:
                if t.is_red:
                    show_str_reverse.append(t.str[0]+t.str[2])
                else:
                    show_str_reverse.append(t.str[0])

            else:
                type_history.add(t.type)
                show_str_reverse.append(t.str)
        

        # add furo        

        return ''.join(reversed(show_str_reverse))
        
    @property
    def anpais(self):
        return list(sorted(set(self.sutehais + self.extra_anpais)))

    @property
    def reach(self):
        return self.reach_state == "accepted"
    
    @property
    def menzen(self):
        return len([f for f in self.furos if f.type != MjMove.ankan.value]) == 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]

        

    @property
    def jikaze(self):
        if self.board.oya is not None:
            wind_index = (4 + self.id - self.board.oya) % 4
            return ['E','S','W','N'][wind_index]
        else:
            return None
    
    @property
    def tenpai(self):
        return self.shanten <= 0

    @property
    def furiten(self):
        if len(self.tehais) % 3 != 1:
            return False
        if UNKNOWN_PAI in self.tehais:
            return False
        
        tenpai_info = TenpaiAnalysis(self.tehais)
        
        if tenpai_info.tenpai == False:
            return False
        
        anpais = self.anpais
        return any([a.is_same_symbol(b) for (a,b) in itertools.product(anpais, tenpai_info.waited_pais)])

    def is_daisangen_pao(self):
        return len([f for f in self.furos if f.pais[0].is_sangenpai()]) == 3

    def is_daisushi_pao(self):
        return len([f for f in self.furos if f.pais[0].is_wind()]) == 4


    def delete_tehai(self, pai):
        if pai in self.tehais:
            pai_index = self.tehais.index(pai)
        else:
            pai_index = 0
            # assert self.tehais[pai_index].str == UNKNOWN_PAI_STR
        del self.tehais[pai_index]
            

    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 __str__(self):        
        return f"tehai:{self.tehais}, furo:{self.furos}\n" \
            f"sutehai:{self.sutehais}\n" \
            f"score:{self.score}, reache:{self.reach}"
    
    @property
    def shanten(self):
        tehai = [0] * 34
        for t in self.tehais:
            tehai[t.id] += 1
        return self.shanten_analysis.calc_shanten(tehai, len(self.furos))
    
    
    def calc_dahaied_shanten(self, pai:str):
        pai = Pai.from_str(pai)
        if pai not in self.tehais:
            raise Exception(f'tehais not condains pai:{pai}')
        dahaied_tehai = self.tehais.copy()
        dahaied_tehai.remove(pai)

        tehai = [0] * 34
        for t in dahaied_tehai:
            tehai[t.id] += 1
        return self.shanten_analysis.calc_shanten(tehai, len(self.furos))
    
    def calc_added_shanten(self, pai:str):
        pai = Pai.from_str(pai)
        dahaied_tehai = self.tehais.copy()
        dahaied_tehai.append(pai)

        tehai = [0] * 34
        for t in dahaied_tehai:
            tehai[t.id] += 1
        return self.shanten_analysis.calc_shanten(tehai, len(self.furos))
        

    def can_hora(self):
        previous_action = self.board.previous_action
        if not previous_action:
            return False

        if previous_action['type'] == MjMove.tsumo.value and self.id == previous_action['actor']:
            hora_type = 'tsumo'
            pais = self.tehais
            shanten = self.shanten
        elif previous_action['type'] in [MjMove.dahai.value, MjMove.kakan.value] and self.id != previous_action['actor']:
            hora_type = 'ron'
            pais = self.tehais + [Pai.from_str(previous_action['pai'])]
            shanten = self.calc_added_shanten(previous_action['pai'])
        else:
            return False

        if shanten != -1:
            return False

        action = {
            'type':MjMove.hora.value,
            'pai':previous_action['pai'],
            'actor':self.id,
            'target':previous_action['actor'],    
        }
        hora = self.board.get_hora(action, **{'previous_action':previous_action})
        
        return hora.valid and (hora_type == 'tsumo' or self.furiten == False)
    


    def can_daiminkan(self, pai:str):
        if self.reach_state == "accepted":
            return False, []
        
        target_pai = Pai.from_str(pai)
        candidates = [t.str for t in self.tehais if target_pai.is_same_symbol(t)]
        if len(candidates) < 3:
            return False, []
        else:
            return True, candidates

    def can_ankan(self, pai:str=None):
        if pai is None:
            pai_count = {}
            for t in self.tehais:
                if t.id not in pai_count:
                    pai_count[t.id] = 0
                
                pai_count[t.id] += 1

            can_ankan_ids = [p for p in pai_count if pai_count[p] == 4]
            if len(can_ankan_ids) == 0:
                return False, []
            
            candidates = []
            for can_ankan_id in can_ankan_ids:
                candidate = [p.str for p in self.tehais if p.id == can_ankan_id]
                candidates.append(candidate)
            
            return True, candidates

        else:
            in_tehai = [p for p in self.tehais if p.is_same_symbol(Pai.from_str(pai))]
            return len(in_tehai) == 4, in_tehai

    def can_kakan(self, pai:str=None):
        if self.reach_state == "accepted":
            return False, []

        if pai is None:
            pons = {}
            for f in self.furos:
                if f.type == 'pon':
                    pons[f.pais[0].id] = [p.str for p in f.pais]

            can_kakan_ids = [p.id for p in self.tehais if p.id in pons]
            if len(can_kakan_ids) == 0:
                return False, []
            
            candidates = []
            for can_kakan_id in can_kakan_ids:
                candidate = [p.str for p in self.tehais if p.id == can_kakan_id]
                # assert len(candidate) == 1
                candidates.append([candidate[0], pons[can_kakan_id]])
            
            return True, candidates
        else:
            target_pai = Pai.from_str(pai)
            in_tehai = [p for p in self.tehais if p.is_same_symbol(target_pai)]
            in_pon = [[p.str for p in f.pais] for f in self.furos if f.type == 'pon' and f.pais[0].is_same_symbol(target_pai)]
            return len(in_tehai)==1 & len(in_pon)==1, [in_tehai[0], in_pon]


    def can_pon(self, pai:str):
        if self.reach_state == "accepted":
            return False, []

        target_pai = Pai.from_str(pai)
        candidates_pai = [t for t in self.tehais if target_pai.is_same_symbol(t)]
        
        if len(candidates_pai) < 2:
            return False, []
        
        red_consumed = ([c for c in candidates_pai if c.is_red])
        not_red_consumed = ([c for c in candidates_pai if c.is_red == False])
        if len(red_consumed) > 0:
            if  len(candidates_pai) == 2:
                return True, [
                    [red_consumed[0].str,     not_red_consumed[0].str],
                    ]
            else:
                return True, [
                    [red_consumed[0].str,     not_red_consumed[0].str],
                    [not_red_consumed[0].str, not_red_consumed[0].str],
                    ]
        else:
            return True, [
                [not_red_consumed[0].str, not_red_consumed[1].str]
                ]
    
    def can_chi(self, pai:str):
        if self.reach_state == "accepted":
            return False, []
        

        target_pai = Pai.from_str(pai)
        if not target_pai.is_number():
            return False, []

        
        can_chi = False
        candidates = []
        
        tehais_single = {}
        for t in self.tehais:
            if t.str not in tehais_single:
                tehais_single[t.str] = t

        single_tehais = tehais_single.values()

        minus2_candidates_pai = [t.str for t in single_tehais if t.number == target_pai.number-2  and t.type == target_pai.type]
        minus1_candidates_pai = [t.str for t in single_tehais if t.number == target_pai.number-1  and t.type == target_pai.type]
        plus1_candidates_pai = [t.str for t in single_tehais if t.number == target_pai.number+1  and t.type == target_pai.type]
        plus2_candidates_pai = [t.str for t in single_tehais if t.number == target_pai.number+2  and t.type == target_pai.type]
        
        cannot_dahai_number = []
        # right chi [1,2] [3]
        if len(minus2_candidates_pai) > 0 and len(minus1_candidates_pai) > 0:
            cannot_dahai_number.append(target_pai.number-3)
            cannot_dahai_number.append(target_pai.number)
            for m2, m1 in itertools.product(minus2_candidates_pai, minus1_candidates_pai):
                candidates.append([m2, m1])
                
            
        # center chi [1,3] [2]
        if len(minus1_candidates_pai) > 0 and len(plus1_candidates_pai) > 0:
            cannot_dahai_number.append(target_pai.number)
            for m1, p1 in itertools.product(minus1_candidates_pai, plus1_candidates_pai):
                candidates.append([m1, p1])
        # left chi [2,3] [1]        
        if len(plus1_candidates_pai) > 0 and len(plus2_candidates_pai) > 0:
            cannot_dahai_number.append(target_pai.number)
            cannot_dahai_number.append(target_pai.number+3)
            for p1, p2 in itertools.product(plus1_candidates_pai, plus2_candidates_pai):
                candidates.append([p1, p2])


        # kuikae check
        ignore_candidates = []
        if all([t for t in self.tehais if t.type == target_pai.type]):
            for candidate in candidates:
                temp_tehai = copy.copy(self.tehais)
                temp_tehai.remove(Pai.from_str(candidate[0]))
                temp_tehai.remove(Pai.from_str(candidate[1]))
                if len([t for t in temp_tehai if t.number not in cannot_dahai_number]) == 0:
                    ignore_candidates.append(candidate)
        
        for ignore_candidate in ignore_candidates:
            candidates.remove(ignore_candidate)

        return len(candidates) > 0, candidates
コード例 #9
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
コード例 #10
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
コード例 #11
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