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
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
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)
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
"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:]]