def _handle_liuju(self, msg): self._round_end_info_to_file(" [R{}T{}] NO ONE WINS, NO MORE TILES...".format( self.game_table.round_number, len(self.game_table.bot.discard34)) ) self.both_log(" NO ONE WINS, NO MORE TILES...\n") self.drawer and self.drawer.liuju() opp_tiles_dict = {} for i in range(1, 4): opp_tiles = TenhouParser.parse_opp_tiles(msg, i) if opp_tiles and len(opp_tiles) > 0: opp_tiles_dict[i] = opp_tiles if len(opp_tiles_dict) > 0: for k, v in opp_tiles_dict.items(): tiles34 = [t // 4 for t in v] self._round_end_info_to_file(" FT-P{}:{}:{}\n".format(k, Tile.t34_to_g(tiles34), tiles34)) p_melds = [meld.tiles for meld in self.game_table.get_player(k).meld136] p_melds += [v] self._log(Tile.t136_to_g(p_melds)) if self.drawer: self.drawer.update_opp(p_melds, k, True) # prd_str = ' WP ' # for i in range(1, 4): # waitings = self.game_table.get_player(i).waiting_prediction # prd_str += "• P{}:{}:{} ".format(i, waitings, Tile.t34_to_g(waitings)) # prd_str += '\n' # self._round_end_info_to_file(prd_str) self.logger_obj.flush_buffer() self._wait_for_a_while() sleep(2) self._send('<NEXTREADY />')
def should_call_kan(self, tile136, from_opponent): """ This method has to be implemented. It decides whether the bot should call a Kan(Quad) meld. A Kan meld is set of four identical tiles. There are three kinds of Quad sets. (1) MINKAN: When the bot has three identical tiles in hand and the opponent discards the fourth tile. (2) KAN: When the bot has all four identical tiles in hand. (3) CHAKAN: When the bot has an open Triplet meld and it draws the fourth tile, the bot can update this Triplet meld to a Kan meld :param tile136: The involved kan tile in 136 form :param from_opponent: Whether the tile was from opponent True/False :return: [Kan type], [to be called tile] if should call Kan else False, False """ tile34 = tile136 // 4 if from_opponent: # (1) Check Minkan should_kan = True # TODO: should be decided by your own strategy if should_kan: # the tiles in hand should be removed from the set of hand tiles after having decided self_tiles = [t for t in self.tiles136 if t // 4 == tile34] for t in self_tiles: self.tiles136.remove(t) # developer could access self.thclient.both_log(msg) to display content in logs msg = " [Bot calls minkan]: {}".format( Tile.t34_to_g([tile136 // 4] * 4)) self.thclient.both_log(msg) # return the result return Meld.KAN, tile136 else: # (2) Check Kan ankan_tile = None if self.hand34.count( tile34) == 4: # bot gots the fourth tile at this turn ankan_tile = tile34 else: own_tile = [ tile for tile in set(self.hand34) if self.hand34.count(tile) == 4 ] if own_tile and len( own_tile ) > 0: # bot had a kan in hand before and did not call kan ankan_tile = own_tile[0] if ankan_tile: should_call_ankan = True # TODO: should be decided by your own strategy if should_call_ankan: msg = " 🤖[Bot calls ankan]: {}".format( Tile.t34_to_g([ankan_tile] * 4)) self.thclient.both_log(msg) return Meld.KAN, self.tile_34_to_136(ankan_tile) # (3) Check Chakan for meld in self.meld136: if meld.tiles[0] // 4 == meld.tiles[1] // 4 == tile34: should_call_chakan = True # TODO: should be decided by your own strategy if should_call_chakan: msg = " 🤖[Bot calls chakan]: {}".format( Tile.t34_to_g([tile136 // 4] * 4)) self.thclient.both_log(msg) return Meld.CHANKAN, tile136 return False, False
def format_hand(self, extra_tile): hand = '{} + {}'.format(Tile.t136_to_g(self.tiles136), Tile.t136_to_g([extra_tile])) if self.is_open_hand: melds = [] for meld in self.meld136: melds.append('{}'.format(meld.tiles_graph)) hand += ' + [{}]'.format(','.join(melds)) return hand
def _handle_opponent_discard(self, msg): opponent_seat = TenhouParser.parse_opponent_seat(msg) if opponent_seat == 0: # if hasattr(self.game_table.bot, "update_part"): # self.game_table.bot.update_part() return -1 tile = TenhouParser.parse_tile(msg) opp_obj = self.game_table.get_player(opponent_seat) discard_tag = ['d', 'e', 'f', 'g'] # lower case alphabet means the player discards whatever he had drawn... was_direct = discard_tag[opponent_seat] in msg opp_obj.add_discard_type(was_direct) if hasattr(self.game_table.bot, "handle_opponent_discard"): self.game_table.bot.handle_opponent_discard(opponent_seat) self.game_table.discard_tile(opponent_seat, tile) self.drawer and self.drawer.discard(tile, opponent_seat) discard_msg = " [Player {}] discards: {} + {}".format(opponent_seat, Tile.t34_to_g(opp_obj.discard34[:-1]), Tile.t136_to_g([tile])) discard_msg += ", melds: {}".format(Tile.t34_to_g(opp_obj.meld34)) if opp_obj.meld34 else "" self.both_log(discard_msg) # check meld call for bot if 't=' in msg: # check kan if 't="3"' in msg or 't="7"' in msg: if self.game_table.bot.should_call_kan(tile, True)[0] == Meld.KAN: self._send('<N type="2" />') kan_msg = ' [Bot] called an open kan: {}'.format(Tile.t34_to_g([tile // 4] * 4)) self.both_log(kan_msg) return -1 # check chow/pon may_call_chi = (msg[1].lower() == 'g') meld, tile_to_discard = self.game_table.bot.try_to_call_meld(tile, may_call_chi) if meld: meld_type = '3' if meld.type == Meld.CHI else '1' self_tiles = [t for t in meld.tiles if t != meld.called_tile] self._send('<N type="{}" hai0="{}" hai1="{}" />'.format(meld_type, self_tiles[0], self_tiles[1])) self.game_table.count_remaining_tiles += 1 return tile else: self._wait_for_a_while() self._send('<N />') self.drawer and self.drawer.set_remain(self.game_table.count_remaining_tiles) return -2
def try_to_call_meld(self, tile136, might_call_chi): """ This method has to be implemented. It decides whether to call a meld or not. :param tile136: the involved opponent's discard in 136 form :param might_call_chi: whether is it possible to call CHI :return: [Meld object], 0 if should call meld else return False, False """ tile34 = tile136 // 4 # (1) Check Pon if self.hand34.count(tile34) >= 2: should_call_pon = True # TODO: should be decided by your own strategy if should_call_pon: self_tiles = [ t136 for t136 in self.tiles136 if t136 // 4 == tile136 // 4 ] msg = " 🤖[Bot calls pon]: {}".format( Tile.t34_to_g([tile136 // 4] * 3)) self.thclient.both_log(msg) return Meld(Meld.PON, self_tiles[0:2] + [tile136], True, tile136), 0 # (2) Check Chi if might_call_chi and tile34 < 27: # There might be multiple possibilities to call Chi chi_candidates = [] if tile34 % 9 > 1 and (tile34 - 2) in self.hand34 and ( tile34 - 1) in self.hand34: chi_candidates.append([tile34 - 2, tile34 - 1]) if 8 > tile34 % 9 > 0 and (tile34 - 1) in self.hand34 and ( tile34 + 1) in self.hand34: chi_candidates.append([tile34 - 1, tile34 + 1]) if 7 > tile34 % 9 and (tile34 + 1) in self.hand34 and ( tile34 + 2) in self.hand34: chi_candidates.append([tile34 + 1, tile34 + 2]) for candidate in chi_candidates: should_chi = True # TODO: should be decided by your own strategy if should_chi: opt1, opt2 = self.tile_34_to_136( candidate[0]), self.tile_34_to_136(candidate[1]) msg = " 😊[Bot calls chow]: {}".format( Tile.t34_to_g(candidate + [tile34])) self.thclient.both_log(msg) return Meld(Meld.CHI, sorted([opt1, opt2, tile136]), True, tile136), 0 return False, False
def str_hand_tiles(self): hand = Tile.t136_to_g(self.tiles136) if self.is_open_hand: melds = [] for meld in self.meld136: melds.append('{}'.format(meld.tiles_graph)) hand += ' + [{}]'.format(','.join(melds)) return hand
def _handle_new_bonus_indicator(self, msg): tile = TenhouParser.parse_bonus_indicator(msg) # save data to objects self.game_table.add_bonus_indicator(tile) # display in logs new_bi_msg = ' New bonus tile indicator revealed: {}'.format(Tile.t136_to_g([tile])) self.both_log(new_bi_msg) # display in GUI self.drawer and self.drawer.add_bonus_indicator(tile) self.drawer and self.drawer.set_remain(self.game_table.count_remaining_tiles)
def _handle_meld_call(self, msg, meld_tile): player_hand = self.game_table.bot.format_hand(meld_tile) if meld_tile else '' meld = TenhouParser.parse_meld(msg) if meld.by_whom != 0: self.game_table.call_meld(meld.by_whom, meld) meld_msg = ' [Player {}] called meld: {}'.format(meld.by_whom, meld) self.both_log(meld_msg) if self.drawer: p_melds = [meld.tiles for meld in self.game_table.get_player(meld.by_whom).meld136] self.drawer.update_opp(p_melds, meld.by_whom) if meld.by_whom == 0: self.game_table.bot.call_meld(meld) if meld.type != Meld.KAN and meld.type != Meld.CHANKAN: discard_tile_136 = self.game_table.bot.to_discard_tile() own_hand_msg = ' [Bot] hand tiles: {}'.format(player_hand) discard_msg = ' [Bot] discards tile {} after called meld'.format( Tile.t136_to_g([discard_tile_136])) self.both_log(own_hand_msg) self.both_log(discard_msg) self._stream_log('') self._send('<D p="{}"/>'.format(discard_tile_136)) self.game_table.discard_tile(0, discard_tile_136) self.game_table.bot.tiles136.remove(discard_tile_136) if self.drawer: self.drawer.discard(discard_tile_136, 0) self.drawer.update_self(self.game_table.bot.tiles136, [meld.tiles for meld in self.game_table.bot.meld136]) else: if self.drawer: self.drawer.update_self(self.game_table.bot.tiles136, [meld.tiles for meld in self.game_table.bot.meld136]) if callable(getattr(self.game_table.bot, 'handle_opponent_discard', None)): for i in range(1, 4): self.game_table.bot.handle_opponent_discard(i)
def __str__(self): bonus_tile_indicator_repr = Tile.t136_to_g(self.bonus_indicator) return 'Round number:{0}, Honba sticks:{1}, Bonus tile indicator:{2}'.format( self.round_number, self.honba_sticks, bonus_tile_indicator_repr)
def tiles_string(self): return Tile.tile136_to_string(self.tiles)
def tiles_graph(self): return Tile.t136_to_g(self.tiles)
def __str__(self): return '{}, {}'.format(self.type, Tile.t136_to_g(self.tiles), self.tiles)
def form_tiles_string(h, m, mk, ak, ft): h_str = Tile.t34_to_g(h) m_str = Tile.t34_to_g(m + mk) + " *" + Tile.t34_to_g(ak) f_str = Tile.t34_to_g(ft) return "Final tiles: {} | {}, Win tile: {}".format( h_str, m_str, f_str)
def _handle_draw_tile(self, msg): drawn_tile_136 = TenhouParser.parse_tile(msg) self.drawer and self.drawer.draw(drawn_tile_136) if not self.game_table.bot.reach_status: # print own hand tiles own_hand_str = ' [Bot] draws a tile: {}'.format(self.game_table.bot.format_hand(drawn_tile_136)) self.game_table.bot.draw_tile(drawn_tile_136) self._stream_log('') self.both_log(own_hand_str) self._wait_for_a_while() # check if bot can call reach can_call_reach, to_discard_136 = self.game_table.bot.can_call_reach() if can_call_reach: self._send('<REACH hai="{}"/>'.format(to_discard_136)) self.game_table.bot.call_reach() self._wait_for_a_while() # check if bot can call a kan set kan_type, kaned_tile136 = self.game_table.bot.should_call_kan(drawn_tile_136, False) if kan_type and self.game_table.count_remaining_tiles > 0: meld_type = 5 if kan_type == Meld.CHANKAN else 4 self._send('<N type="{}" hai="{}"/>'.format(meld_type, kaned_tile136)) return True # bot decides what to discard discard_tile_136 = self.game_table.bot.to_discard_tile() self.game_table.bot.tiles136.remove(discard_tile_136) discard_msg = ' [Bot] discards: {} + {}'.format( Tile.t34_to_g(self.game_table.bot.discard34), Tile.t136_to_g([discard_tile_136]) ) self.both_log(discard_msg) bot_hand_msg = ' [Bot] hand tiles after discarding: {}'.format( self.game_table.bot.str_hand_tiles() ) self.both_log(bot_hand_msg) else: discard_tile_136 = drawn_tile_136 own_hand_str = ' Own hand: {}'.format(self.game_table.bot.format_hand(drawn_tile_136)) self._stream_log('') self.both_log(own_hand_str) if callable(getattr(self.game_table.bot, 'log_opponents_prediction', None)): self.game_table.bot.log_opponents_prediction() if callable(getattr(self.game_table.bot, 'show_riichi_waiting', None)): self.game_table.bot.show_riichi_waiting() discard_msg = ' 🤖[Bot(Richii) discards]: {} + {}'.format( Tile.t34_to_g(self.game_table.bot.discard34), Tile.t136_to_g([discard_tile_136]) ) self.both_log(discard_msg) self._send('<D p="{}"/>'.format(int(discard_tile_136))) self.game_table.discard_tile(0, discard_tile_136) remain_tiles_msg = ' Remaining tiles: {}'.format(self.game_table.count_remaining_tiles) self._stream_log('') self.both_log(remain_tiles_msg) self._flush_buffer() self.drawer and self.drawer.set_remain(self.game_table.count_remaining_tiles) self._stream_log('') self.drawer and self.drawer.discard(discard_tile_136, 0) self.game_table.bot.tiles136 = sorted(self.game_table.bot.tiles136) self.drawer and self.drawer.update_self(self.game_table.bot.tiles136, [meld.tiles for meld in self.game_table.bot.meld136]) return False
def _handle_round_end(self, msg=''): self.logger_obj.flush_buffer() self.logger_obj.add_line('') self.logger_obj.add_line(" {}".format(msg)) # parse message who = int(TenhouParser.get_attribute_value(msg, 'who')) from_whom = int(TenhouParser.get_attribute_value(msg, 'fromWho')) win_tile_136 = int(TenhouParser.get_attribute_value(msg, 'machi')) hand_tiles_136 = TenhouParser.parse_initial_hand(msg) win_score = TenhouParser.parse_win_score(msg) if win_tile_136 in hand_tiles_136: hand_tiles_136.remove(win_tile_136) # save in logs who_str = "{}{}".format( who, "(Riichi)" if self.game_table.get_player(who).reach_status else "" ) from_whom_str = "{}{}".format( from_whom, "(Riichi)" if self.game_table.get_player(from_whom).reach_status else "" ) res_str = " [Round-{} Turn-{} Dealer-{}]\n".format( self.game_table.round_number, len(self.game_table.bot.discard34), self.game_table.dealer_seat ) res_str += " [Win]:{} [By]:{} [Score]:{} [Tile]:{} {} \n".format( who_str, from_whom_str, win_score, win_tile_136 // 4, Tile.t34_to_g(win_tile_136 // 4) ) hand_tiles_34 = [t // 4 for t in hand_tiles_136] if who != 0 else self.game_table.bot.hand34 win_tile_34 = win_tile_136 // 4 meld_tiles_34 = self.game_table.get_player(who).meld34 res_str += " " * 8 + "[Winning Hand] " res_str += "{}+{}+{}".format( Tile.t34_to_g(hand_tiles_34), ''.join([Tile.t34_to_g(m) for m in meld_tiles_34]), Tile.t34_to_g(win_tile_34) ) res_str += " {}+{}+{}\n".format( hand_tiles_34, ''.join(["{}".format(m) for m in meld_tiles_34]), win_tile_34 ) self._round_end_info_to_file(res_str) # display in logs if from_whom == who: win_msg = " Player {} wins by own drawn tile {}".format( who, Tile.t34_to_g(win_tile_136 // 4) ) else: win_msg = " Player {} wins from player {}'s discard {}".format( who, from_whom, Tile.t34_to_g(win_tile_136 // 4) ) self.both_log(win_msg) win_melds = self.game_table.get_player(who).meld34 win_tiles_msg = " {} {}+ {}, {}p".format( Tile.t136_to_g(hand_tiles_136), "+{}".format(Tile.t34_to_g(win_melds)) if len(win_melds) > 0 else '', Tile.t136_to_g(win_tile_136), win_score ) self.both_log(win_tiles_msg) self.both_log('') # display in GUI if self.drawer: from_whom != who and self.drawer.lose(from_whom, win_score) from_whom != who and self.drawer.yong(who, win_score) from_whom == who and self.drawer.zimo(who, win_score) if who != 0: p_melds = [meld.tiles for meld in self.game_table.get_player(who).meld136] p_melds += [hand_tiles_136] p_melds += [[win_tile_136]] self.drawer.update_opp(p_melds, who, True) else: self.drawer.draw(win_tile_136) self._flush_buffer() self._wait_for_a_while() sleep(2) self._send('<NEXTREADY />')