def main(): import datetime manzu = [1,0,0,1,0,0,0,0,0] pinzu = [0,0,0,0,0,2,2,0,0] souzu = [0,0,0,2,0,0,2,0,0] ji = [0,3,0,0,0,0,0] """ manzu = [1,0,0,0,0,0,0,0,1] pinzu = [1,0,0,0,0,0,0,0,1] souzu = [1,0,3,0,0,0,0,0,1] ji = [1,1,1,1,0,0,1] """ tehai = manzu + pinzu + souzu + ji tehai = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] depth = 3 furo_num = (14-sum(tehai))//3 from mjaigym.board.function.furo import Furo w_pai = Pai.from_str("W") pai_3m =Pai.from_str("3m") furos = [ Furo({"type":"pon", "actor":0, "target":2, "taken":pai_3m, "consumed":[pai_3m,pai_3m]}), # Furo({"type":"pon", "actor":0, "target":2, "taken":w_pai, "consumed":[w_pai,w_pai]}), # Furo({"type":"pon", "actor":0, "target":2, "taken":pai_3m, "consumed":[pai_3m,pai_3m]}), ] assert 13 <= sum(tehai) + 3 * len(furos) <= 14 dfs = Dfs() shanten_normal, shanten_kokushi, shanten_chitoitsu = shanten.get_shanten_all(tehai, len(furos)) start = datetime.datetime.now() for i in range(40): tehai = get_random_tehai() # if 0 <= shanten_normal < depth-1: result = dfs.dfs_with_score_normal(tehai, furos, depth, oya=True, shanten_normal=shanten_normal) # result = dfs.dfs_with_score_chitoitsu(tehai, furos, depth, doras=Pai.from_list(["1m","3m"]), shanten_chitoitsu=shanten_chitoitsu) # result = dfs.dfs_with_score_kokushi(tehai, furos, depth, oya=True, shanten_kokushi=shanten_kokushi) # result = dfs.dfs(tehai, furo_num, depth) end = datetime.datetime.now() print(tehai) for r in result: print(r) pass print(len(result)) print(end - start)
def compare(): import datetime depth = 3 dfs = Dfs() print("start dfs a") start = datetime.datetime.now() manzu = [0,0,0,0,0,0,0,0,0] pinzu = [0,0,0,0,0,0,0,0,0] souzu = [0,0,0,0,0,0,0,0,0] ji = [1,0,0,0,0,3,1] tehai_a = manzu + pinzu + souzu + ji depth = 3 furo_num = (14-sum(tehai_a))//3 from mjaigym.board.function.furo import Furo w_pai = Pai.from_str("W") pai_3m =Pai.from_str("3m") furos = [ Furo({"type":"pon", "actor":0, "target":2, "taken":w_pai, "consumed":[w_pai,w_pai]}), Furo({"type":"pon", "actor":0, "target":2, "taken":w_pai, "consumed":[w_pai,w_pai]}), Furo({"type":"pon", "actor":0, "target":2, "taken":pai_3m, "consumed":[pai_3m,pai_3m]}), ] assert 13 <= sum(tehai_a) + 3 * len(furos) <= 14 shanten_normal, shanten_kokushi, shanten_chitoitsu = shanten.get_shanten_all(tehai_a, len(furos)) result_a = dfs.dfs_with_score_normal(tehai_a, furos, depth, shanten_normal=shanten_normal) # a_changes = set() # for ra in result_a: # a_changes.add(tuple(ra[0])) print("start dfs b") dfs = Dfs() manzu = [0,0,0,0,0,0,0,0,0] pinzu = [0,0,0,0,0,0,0,0,0] souzu = [0,0,0,0,0,0,0,0,0] ji = [1,0,0,0,0,1,3] tehai_b = manzu + pinzu + souzu + ji shanten_normal, shanten_kokushi, shanten_chitoitsu = shanten.get_shanten_all(tehai_b, len(furos)) result_b = dfs.dfs_with_score_normal(tehai_b, furos, depth, shanten_normal=shanten_normal) result_a = sorted(result_a, key=lambda x:x[0]) result_b = sorted(result_b, key=lambda x:x[0]) print(len(result_a)) print(len(result_b)) for i in range(max(len(result_a),len(result_b))): if i >= len(result_b): print(result_a[i][0]) else: print(result_a[i][0], result_b[i][0])
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_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 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 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 update_oya(self, renchan, ryukyoku_reason): if renchan == False: self.oya = (self.oya + 1) % self.PLAYER_NUM if self.oya == self.chicha: self.bakaze = Pai.from_str(self.bakaze).succ.str if renchan or ryukyoku_reason: self.honba += 1 else: self.honba = 0 if self.game_type == 'tonpu': self.last = self.decide_last(Pai.from_str('E'), renchan, ryukyoku_reason) elif self.game_type == 'tonnan': self.last = self.decide_last(Pai.from_str('S'), renchan, ryukyoku_reason)
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_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 get_hora(self, action, **params): if action['type'] != MjMove.hora.value: raise Exception('shoud not happen') hora_type = 'tsumo' if action['actor'] == action['target'] else 'ron' if hora_type == 'tsumo': tehais = self.players[action['actor']].tehais[:-1] # remove tsumo else: tehais = self.players[action['actor']].tehais doras = [Pai.from_str(p).succ for p in self.dora_markers] if 'uradora_markers' in params: uradoras = [ Pai.from_str(p).succ for p in params['uradora_markers'] ] else: uradoras = [] hora_player = self.players[action['actor']] # return Hora( return HoraRs( tehais=tehais, furos=hora_player.furos, taken=Pai.from_str(action['pai']), hora_type=hora_type, oya=self.oya == action['actor'], bakaze=self.bakaze, jikaze=self.players[action['actor']].jikaze, doras=doras, uradoras=uradoras, reach=hora_player.reach_state == 'accepted', double_reach=hora_player.double_reach, ippatsu=hora_player.ippatsu_chance, rinshan=hora_player.rinshan, haitei=(self.yama.get_rest_num() == 0) and (hora_player.rinshan == False), first_turn=self.first_turn, chankan=('previous_action' in params) and (params['previous_action']['type'] == MjMove.kakan.value), )
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(cls, result:np.array, board_state:BoardState, player_id:int, oracle_enable_flag:bool=False): dora_markers = board_state.dora_markers nums = {} for dora_maker in dora_markers: pai = Pai.from_str(dora_maker) dora_pai = pai.succ if dora_pai.id not in nums: nums[dora_pai.id] = 0 nums[dora_pai.id] += 1 for pai_id, n in nums.items(): if n > 0: result[0:n, pai_id] = 1
def calc(cls, result: np.array, board_state: BoardState, player_id: int, oracle_enable_flag: bool = False): if board_state.previous_action["type"] != MjMove.dahai.value: return if board_state.previous_action["actor"] != player_id: return last_dahai_pai = Pai.from_str(board_state.previous_action['pai']) result[0, last_dahai_pai.id] = 1 if last_dahai_pai.is_red: result[1, :] = 1
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
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 tsumo(self, pai: str): self._consumed_num += 1 if pai == UNKNOWN_PAI_STR: return self.rest.remove(Pai.from_str(pai))
def restnum_update(self, action): if action is None or "type" not in action: return if action["type"] == MjMove.start_kyoku.value: # initialize self.rest_pai_view = [[4] * 34 for _ in range(4)] dora_marker_id = Pai.str_to_id(action["dora_marker"]) for i in range(4): self.rest_pai_view[i][dora_marker_id] -= 1 for pai in action["tehais"][i]: pai_id = Pai.str_to_id(pai) self.rest_pai_view[i][pai_id] -= 1 elif action["type"] == MjMove.tsumo.value: pai_id = Pai.from_str(action["pai"]).id actor = action["actor"] self.rest_pai_view[actor][pai_id] -= 1 elif action["type"] == MjMove.dahai.value: pai_id = Pai.from_str(action["pai"]).id actor = action["actor"] for i in range(4): if actor != i: self.rest_pai_view[i][pai_id] -= 1 elif action["type"] == MjMove.dora.value: pai_id = Pai.from_str(action["dora_marker"]).id for i in range(4): self.rest_pai_view[i][pai_id] -= 1 elif action["type"] == MjMove.chi.value: actor = action["actor"] for i in range(4): if actor != i: for consume in action["consumed"]: self.rest_pai_view[i][Pai.str_to_id(consume)] -= 1 elif action["type"] == MjMove.pon.value: actor = action["actor"] for i in range(4): if actor != i: for consume in action["consumed"]: self.rest_pai_view[i][Pai.str_to_id(consume)] -= 1 elif action["type"] == MjMove.daiminkan.value: actor = action["actor"] for i in range(4): if actor != i: for consume in action["consumed"]: self.rest_pai_view[i][Pai.str_to_id(consume)] -= 1 elif action["type"] == MjMove.ankan.value: actor = action["actor"] for i in range(4): if actor != i: for consume in action["consumed"]: self.rest_pai_view[i][Pai.str_to_id(consume)] -= 1 elif action["type"] == MjMove.kakan.value: actor = action["actor"] pai_id = Pai.str_to_id(action["pai"]) for i in range(4): if actor != i: self.rest_pai_view[i][pai_id] -= 1
def possible_actions(self): # calclate valid move if self.previous_action is None or \ self.previous_action['type'] in [ MjMove.start_game.value, MjMove.start_kyoku.value, MjMove.reach_accepted.value, MjMove.ryukyoku.value, MjMove.end_kyoku.value, MjMove.end_game.value, ]: # pattern of all player returns none return self.get_all_none_response() elif self.previous_action['type'] == MjMove.tsumo.value: # tsumo next actions tsumo_actor = self.previous_action['actor'] tehais = self.players[tsumo_actor].tehais actions = [] if self.players[tsumo_actor].reach or self.players[ tsumo_actor].double_reach: # add dahai action = { 'type': MjMove.dahai.value, 'actor': tsumo_actor, 'pai': self.previous_action['pai'], 'tsumogiri': True, } actions.append(action) else: dahais_dic = {} for i, tehai in enumerate(tehais[:-1]): # remove duplicate if tehai.str in dahais_dic: continue dahais_dic[tehai.str] = '' action = { 'type': MjMove.dahai.value, 'actor': tsumo_actor, 'pai': tehai.str, 'tsumogiri': False, } actions.append(action) # add tsumogiri action = { 'type': MjMove.dahai.value, 'actor': tsumo_actor, 'pai': tehais[-1].str, # tsumo pai is in last index. 'tsumogiri': True, } actions.append(action) # add reach if self.players[tsumo_actor].reach_state == 'none' and \ self.players[tsumo_actor].menzen and \ self.players[tsumo_actor].shanten <= 0 and \ self.yama.get_rest_num() >= 1: action = { 'type': MjMove.reach.value, 'actor': tsumo_actor, } actions.append(action) # add ankan can_ankan, consumeds = self.players[tsumo_actor].can_ankan() yama_can_kan = self.yama.get_rest_num() > 0 if can_ankan and yama_can_kan: for consumed in consumeds: action = { 'type': MjMove.ankan.value, 'actor': tsumo_actor, 'consumed': consumed, } actions.append(action) # add kakan can_kakan, pai_consumeds = self.players[tsumo_actor].can_kakan() if can_kakan and yama_can_kan: for pai_consumed in pai_consumeds: action = { 'type': MjMove.kakan.value, 'actor': tsumo_actor, 'pai': pai_consumed[0], 'consumed': pai_consumed[1], } actions.append(action) # add hora can_hora = self.players[tsumo_actor].can_hora() if can_hora: previous_actor = self.previous_action['actor'] previous_pai = self.previous_action['pai'] action = { 'type': MjMove.hora.value, 'actor': tsumo_actor, 'target': previous_actor, 'pai': previous_pai, } actions.append(action) response = self.get_all_none_response() response[tsumo_actor] = actions # not use none return response elif self.previous_action['type'] == MjMove.reach.value: tsumo_actor = self.previous_action['actor'] tehais = self.players[tsumo_actor].tehais actions = [] dahais_dic = {} for i, tehai in enumerate(tehais[:-1]): # remove duplicate if tehai.str in dahais_dic: continue dahais_dic[tehai.str] = '' action = { 'type': MjMove.dahai.value, 'actor': tsumo_actor, 'pai': tehai.str, 'tsumogiri': False, } actions.append(action) # add tsumogiri action = { 'type': MjMove.dahai.value, 'actor': tsumo_actor, 'pai': tehais[-1].str, # tsumo pai is in last index. 'tsumogiri': True, } actions.append(action) tenpai_actions = [] for a in actions: if self.players[tsumo_actor].calc_dahaied_shanten( a['pai']) <= 0: tenpai_actions.append(a) # assert len(tenpai_actions) > 0 response = self.get_all_none_response() response[tsumo_actor] = tenpai_actions # not use none return response elif self.previous_action['type'] == MjMove.dahai.value: # check ryukyoku if self.yama.get_rest_num() == 0: # none return self.get_all_none_response() # dahai next actions previous_actor = self.previous_action['actor'] previous_pai = self.previous_action['pai'] response = self.get_all_none_response() for i in range(self.PLAYER_NUM): if i == previous_actor: continue # add hora if self.players[i].can_hora(): action = { 'type': MjMove.hora.value, 'actor': i, 'target': previous_actor, 'pai': previous_pai } response[i].append(action) # add daiminkan can_daiminkan, consumed = self.players[i].can_daiminkan( previous_pai) if can_daiminkan: action = { 'type': MjMove.daiminkan.value, 'actor': i, 'target': previous_actor, 'pai': previous_pai, 'consumed': consumed } response[i].append(action) # add pon # NOTE: consumed could be 2 pattern, with red, without red. can_pon, consumed_list = self.players[i].can_pon(previous_pai) if can_pon: for consumed in consumed_list: action = { 'type': MjMove.pon.value, 'actor': i, 'target': previous_actor, 'pai': previous_pai, 'consumed': consumed } response[i].append(action) # add chi # check distance if self.distance(i, previous_actor) == 1: can_chi, consumed_list = self.players[i].can_chi( previous_pai) if can_chi: for consumed in consumed_list: action = { 'type': MjMove.chi.value, 'actor': i, 'target': previous_actor, 'pai': previous_pai, 'consumed': consumed } response[i].append(action) return response elif self.previous_action['type'] == MjMove.pon.value: previous_actor = self.previous_action['actor'] previous_pai = Pai.from_str(self.previous_action['pai']) actions = [] tehais = self.players[previous_actor].tehais dahais_dic = {} for i, tehai in enumerate(tehais): # ignore kuikae if previous_pai.is_same_symbol(tehai): continue # ignore already considerd symbol if tehai.str in dahais_dic: continue dahais_dic[tehai.str] = 0 action = { 'type': MjMove.dahai.value, 'actor': previous_actor, 'pai': tehai.str, 'tsumogiri': False, } actions.append(action) response = self.get_all_none_response() response[previous_actor] = actions # not use none return response elif self.previous_action['type'] == MjMove.chi.value: previous_actor = self.previous_action['actor'] dahais = self.players[previous_actor].get_chi_dahai( self.previous_action) actions = [] for d in dahais: action = { 'type': MjMove.dahai.value, 'actor': previous_actor, 'pai': d, 'tsumogiri': False, } actions.append(action) response = self.get_all_none_response() response[previous_actor] = actions # not use none return response elif self.previous_action['type'] == MjMove.daiminkan.value: response = self.get_all_none_response() return response elif self.previous_action['type'] == MjMove.dora.value: if self.previous_without_dora_action['type'] == MjMove.ankan.value: response = self.get_all_none_response() elif self.previous_without_dora_action[ 'type'] == MjMove.dahai.value: response = self.get_all_none_response() elif self.previous_without_dora_action[ 'type'] == MjMove.kakan.value: response = self.get_all_none_response() elif self.previous_without_dora_action[ 'type'] == MjMove.daiminkan.value: # 大明槓即乗り response = self.get_all_none_response() elif self.previous_without_dora_action[ 'type'] == MjMove.tsumo.value: # 大明槓後めくり時の打牌 tsumo_actor = self.previous_two_action['actor'] tehais = self.players[tsumo_actor].tehais actions = [] dahais_dic = {} for i, tehai in enumerate(tehais[:-1]): # remove duplicate if tehai.str in dahais_dic: continue dahais_dic[tehai.str] = '' action = { 'type': MjMove.dahai.value, 'actor': tsumo_actor, 'pai': tehai.str, 'tsumogiri': False, } actions.append(action) # add tsumogiri action = { 'type': MjMove.dahai.value, 'actor': tsumo_actor, 'pai': tehais[-1].str, # tsumo pai is in last index. 'tsumogiri': True, } actions.append(action) response = self.get_all_none_response() response[tsumo_actor] = actions # not use none return response else: raise Exception('invalid path') return response elif self.previous_action['type'] == MjMove.ankan.value: response = self.get_all_none_response() return response elif self.previous_action['type'] == MjMove.kakan.value: response = self.get_all_none_response() # chankan previous_actor = self.previous_action['actor'] previous_pai = self.previous_action['pai'] for i in range(self.PLAYER_NUM): if i == previous_actor: continue # add hora if self.players[i].can_hora(): action = { 'type': MjMove.hora.value, 'actor': i, 'target': previous_actor, 'pai': previous_pai } response[i].append(action) return response elif self.previous_action['type'] == MjMove.hora.value: response = self.get_all_none_response() return response else: raise Exception('invalid path')
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 if __name__ == "__main__": pais = [ '1m', '2m', '3m', '4m', '8m', '2p', '3p', '8p', '8p', '9p', '1s', '4s', '5s', '7p' ] pais = [Pai.from_str(p) for p in pais] node = Node(pais) print(pais) print(node.calc_ukeire_num()) start = datetime.datetime.now() print(start) loopnum = 1000 for i in range(loopnum): node.calc_ukeire_num() end = datetime.datetime.now() print(datetime.datetime.now()) print(loopnum, end - start)
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 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]
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
if line["type"] == "reach": reachs[line["actor"]] = True if first_turns[line["actor"]]: double_reachs[line["actor"]] = True # furo update if line["type"] in ["chi", "pon", "daiminkan"]: all_furos[line["actor"]].append({ "type": line["type"], "taken": line["pai"], "consumed": line["consumed"], }) elif line["type"] == "kakan": for f in all_furos[line["actor"]]: if f["type"] == "pon": pon_taken = Pai.from_str(f["taken"]) kakan_pai = Pai.from_str(line["pai"]) if pon_taken.is_same_symbol(kakan_pai): f["type"] = "kakan" f["consumed"].append(f["taken"]) f["taken"] = line["pai"] elif line["type"] == "ankan": all_furos[line["actor"]].append({ "type": line["type"], "consumed": line["consumed"], }) # check hora if line["type"] == "hora": tehai = line["hora_tehais"] taken = line["pai"]