def test_chose_strategy_and_reset_strategy(self): table = Table() table.has_open_tanyao = True player = table.player tiles = self._string_to_136_array(man='33355788', sou='3479', honors='3') player.init_hand(tiles) self.assertEqual(player.ai.current_strategy.type, BaseStrategy.TANYAO) # we draw a tile that will change our selected strategy tile = self._string_to_136_tile(sou='8') player.draw_tile(tile) self.assertEqual(player.ai.current_strategy, None) tiles = self._string_to_136_array(man='33355788', sou='3479', honors='3') player.init_hand(tiles) self.assertEqual(player.ai.current_strategy.type, BaseStrategy.TANYAO) # for already opened hand we don't need to give up on selected strategy meld = Meld() meld.tiles = [1, 2, 3] player.add_called_meld(meld) tile = self._string_to_136_tile(sou='8') player.draw_tile(tile) self.assertEqual(player.ai.current_strategy.type, BaseStrategy.TANYAO)
def test_call_meld(self): client = Client() client.table.init_round(0, 0, 0, 0, 0, [0, 0, 0, 0]) self.assertEqual(client.table.count_of_remaining_tiles, 70) meld = Meld() meld.who = 3 client.call_meld(meld) self.assertEqual(len(client.table.get_player(3).melds), 1) self.assertEqual(client.table.count_of_remaining_tiles, 71)
def _find_best_meld_to_open(self, possible_melds, new_tiles, closed_hand, discarded_tile): discarded_tile_34 = discarded_tile // 4 final_results = [] for meld_34 in possible_melds: meld_34_copy = meld_34.copy() closed_hand_copy = closed_hand.copy() meld_type = is_chi(meld_34_copy) and Meld.CHI or Meld.PON meld_34_copy.remove(discarded_tile_34) first_tile = TilesConverter.find_34_tile_in_136_array( meld_34_copy[0], closed_hand_copy) closed_hand_copy.remove(first_tile) second_tile = TilesConverter.find_34_tile_in_136_array( meld_34_copy[1], closed_hand_copy) closed_hand_copy.remove(second_tile) tiles = [first_tile, second_tile, discarded_tile] meld = Meld() meld.type = meld_type meld.tiles = sorted(tiles) melds = self.player.melds + [meld] selected_tile = self.player.ai.hand_builder.choose_tile_to_discard( new_tiles, closed_hand_copy, melds, print_log=False) final_results.append({ 'discard_tile': selected_tile, 'meld_print': TilesConverter.to_one_line_string( [meld_34[0] * 4, meld_34[1] * 4, meld_34[2] * 4]), 'meld': meld }) final_results = sorted(final_results, key=lambda x: (x['discard_tile'].shanten, -x['discard_tile']. ukeire, x['discard_tile'].valuation)) DecisionsLogger.debug(log.MELD_PREPARE, 'Options with meld calling', context=final_results) return final_results[0]
def score(args): win_tile = to_tiles(args['winTile'])[0] tiles = to_tiles(args['tiles']) melds = [] for i in args['melds']: if i['type'] == 'chi': melds.append(Meld(Meld.CHI, tiles=to_tiles(i), opened=i['opened'])) elif i['type'] == 'pon': melds.append(Meld(Meld.PON, tiles=to_tiles(i), opened=i['opened'])) elif i['type'] == 'kan': melds.append(Meld(Meld.KAN, tiles=to_tiles(i), opened=i['opened'])) elif i['type'] == 'chankan': melds.append( Meld(Meld.CHANKAN, tiles=to_tiles(i), opened=i['opened'])) elif i['type'] == 'nuki': melds.append(Meld(Meld.NUKI, tiles=to_tiles(i), opened=i['opened'])) dora_ind = [] for i in args['doraIndicators']: dora_ind.append(to_tiles(i)[0]) options = args['options'] config = HandConfig(player_wind=options['playerWind'], round_wind=options['roundWind'], is_tsumo=options['isTsumo'], is_riichi=options['isRiichi'], is_daburu_riichi=options['isDoubleRiichi'], is_ippatsu=options['isIppatsu'], is_rinshan=options['isRinshan'], is_chankan=options['isChankan'], is_haitei=options['isHaitei'], is_houtei=options['isHoutei'], is_nagashi_mangan=options['isNagashiMangan'], is_tenhou=options['isTenhou'], is_renhou=options['isRenhou'], is_chiihou=options['isChiihou']) result = calculator.estimate_hand_value(tiles, win_tile, melds=melds, dora_indicators=dora_ind, config=config) opened = False for i in melds: if i.opened: opened = True return result, opened
def _find_best_meld_to_open(self, possible_melds, new_tiles, closed_hand, discarded_tile): discarded_tile_34 = discarded_tile // 4 final_results = [] for meld_34 in possible_melds: meld_34_copy = meld_34.copy() closed_hand_copy = closed_hand.copy() meld_type = is_chi(meld_34_copy) and Meld.CHI or Meld.PON meld_34_copy.remove(discarded_tile_34) first_tile = TilesConverter.find_34_tile_in_136_array(meld_34_copy[0], closed_hand_copy) closed_hand_copy.remove(first_tile) second_tile = TilesConverter.find_34_tile_in_136_array(meld_34_copy[1], closed_hand_copy) closed_hand_copy.remove(second_tile) tiles = [ first_tile, second_tile, discarded_tile ] meld = Meld() meld.type = meld_type meld.tiles = sorted(tiles) melds = self.player.melds + [meld] selected_tile = self.player.ai.hand_builder.choose_tile_to_discard( new_tiles, closed_hand_copy, melds, print_log=False ) final_results.append({ 'discard_tile': selected_tile, 'meld_print': TilesConverter.to_one_line_string([meld_34[0] * 4, meld_34[1] * 4, meld_34[2] * 4]), 'meld': meld }) final_results = sorted(final_results, key=lambda x: (x['discard_tile'].shanten, -x['discard_tile'].ukeire, x['discard_tile'].valuation)) DecisionsLogger.debug(log.MELD_PREPARE, 'Options with meld calling', context=final_results) return final_results[0]
def try_to_call_meld(self, tile_136, is_kamicha_discard, meld_type): # 1 pon # 2 kan (it is a closed kan and can be send only to the self draw) # 4 chi # there is two return value, meldPrint() and discardOption(), # while the second would not be used by client.py meld_chi, meld_pon = None, None should_chi, should_pon = False, False # print(tile_136) # print(self.player.closed_hand) melds_chi, melds_pon = self.get_possible_meld(tile_136, is_kamicha_discard) if melds_chi and meld_type & 4: should_chi, chi_score, tiles_chi = self.chi.should_call_chi( tile_136, melds_chi) # fix here: tiles_chi is now the first possible meld ---fixed! # tiles_chi = melds_chi[0] meld_chi = Meld(meld_type="chi", tiles=tiles_chi) if meld_chi else None if melds_pon and meld_type & 1: should_pon, pon_score = self.pon.should_call_pon( tile_136, is_kamicha_discard) tiles_pon = melds_pon[0] meld_pon = Meld(meld_type="pon", tiles=tiles_pon) if meld_pon else None if not should_chi and not should_pon: return None, None if should_chi and should_pon: meld = meld_chi if chi_score > pon_score else meld_pon elif should_chi: meld = meld_chi else: meld = meld_pon all_tiles_copy, meld_tiles_copy = self.player.tiles[:], self.player.meld_tiles[:] all_tiles_copy.append(tile_136) meld_tiles_copy.append(meld) closed_hand_copy = [ item for item in all_tiles_copy if item not in meld_tiles_copy ] discard_option = self.discard.discard_tile( all_hands_136=all_tiles_copy, closed_hands_136=closed_hand_copy) return meld, discard_option
def call_meld(self, type, who, from_who, opened, tiles, called_tile): meld = Meld() meld.type = type meld.who = who meld.from_who = from_who meld.opened = opened meld.tiles = tiles meld.called_tile = called_tile return meld
def parse_meld(self, message): data = int(self.get_attribute_content(message, 'm')) meld = Meld() meld.who = int(self.get_attribute_content(message, 'who')) meld.from_who = data & 0x3 if data & 0x4: self.parse_chi(data, meld) elif data & 0x18: self.parse_pon(data, meld) elif data & 0x20: self.parse_nuki(data, meld) else: self.parse_kan(data, meld) return meld
def meld_4_tiles(boxes, opened=True): man, pin, sou, honors = to_136_string(boxes[:4]) if man is None: return None # print('man', man, 'pin', pin, 'sou', sou, 'honors', honors) tiles = tc.string_to_136_array(man=man, pin=pin, sou=sou, honors=honors) # print(tc.to_one_line_string(tiles)) return Meld(meld_type=Meld.KAN, tiles=tiles, opened=opened)
def get_melds(openhand, open_cnt): if open_cnt == 0: return [] melds = [] for i in range(open_cnt): tile = [j for j in range(136) if openhand[i * 2][j][0] == 1] opens = [j for j in range(136) if openhand[i * 2 + 1][j][0] == 1] melds.append(Meld(tiles=tile, opened=(opens != []))) return melds
def parse_meld(self, message): data = int(self.get_attribute_content(message, 'm')) meld = Meld() meld.who = int(self.get_attribute_content(message, 'who')) # 'from_who' is encoded relative the the 'who', so we want # to convert it to be relative to our player meld.from_who = ((data & 0x3) + meld.who) % 4 if data & 0x4: self.parse_chi(data, meld) elif data & 0x18: self.parse_pon(data, meld) elif data & 0x20: self.parse_nuki(data, meld) else: self.parse_kan(data, meld) return meld
def parse_meld(self, message): soup = BeautifulSoup(message, 'html.parser') data = soup.find('n').attrs['m'] data = int(data) meld = Meld() meld.who = int(soup.find('n').attrs['who']) meld.from_who = data & 0x3 if data & 0x4: self.parse_chi(data, meld) elif data & 0x18: self.parse_pon(data, meld) elif data & 0x20: self.parse_nuki(data, meld) else: self.parse_kan(data, meld) return meld
def naku(self, naki_kbn, haistrs): """ なきの場合、対応するMeldオブジェクトを返却する :param naki_kbn: :param haistrs: :return: """ # ポン if naki_kbn == 'N': meld_type = Meld.PON # チー elif naki_kbn == 'C': meld_type = Meld.CHI # カン elif naki_kbn == 'K': meld_type = Meld.KAN man = "" pin = "" sou = "" honors = "" for haistr in haistrs: buff = buff = haistr if buff == '東': honors = self.add_hainum_to_tehai(honors, '1') elif buff == '南': honors = self.add_hainum_to_tehai(honors, '2') elif buff == '西': honors = self.add_hainum_to_tehai(honors, '3') elif buff == '北': honors = self.add_hainum_to_tehai(honors, '4') elif buff == '白': honors = self.add_hainum_to_tehai(honors, '5') elif buff == '発': honors = self.add_hainum_to_tehai(honors, '6') elif buff == '中': honors = self.add_hainum_to_tehai(honors, '7') elif buff[1] == 'm': man = self.add_hainum_to_tehai(man, buff[0]) elif buff[1] == 'p': pin = self.add_hainum_to_tehai(pin, buff[0]) elif buff[1] == 's': sou = self.add_hainum_to_tehai(pin, buff[0]) else: print('[error]不正な文字列が入力されました。') tiles = TilesConverter.string_to_136_array(man=man, pin=pin, sou=sou, honors=honors) return Meld(meld_type=meld_type, tiles=tiles)
def _make_meld(self, meld_type, tiles): meld = Meld() meld.who = 0 meld.type = meld_type meld.tiles = tiles meld.called_tile = tiles[0] return meld
def pong(self, id): tiles = [id] for t in self.hand: if t - t % 4 == id - id % 4: tiles.append(t) if len(tiles) != 3: return False self.melds.append(Meld(meld_type=Meld.PON, tiles=tiles)) self.melds34.append([3 if j == id // 4 else 0 for j in range(34)]) self.hand.remove(tiles[1]) self.hand.remove(tiles[2]) self.tiles34[id // 4] = 0 return True
def kong(self, id, opened=True): id34 = id // 4 if (self.tiles34[id34] == 3 and opened) or self.tiles34[id34] == 4: self.melds.append( Meld(meld_type=Meld.KAN, opened=opened, tiles=list(range(4 * id34, 4 * id34 + 4)))) self.melds34.append([4 if j == id34 else 0 for j in range(34)]) for i in range(len(self.hand)): if self.hand[i] // 4 == id // 4: self.hand.pop(i) self.tiles34[id34] = 0 return True return False
def test_can_call_riichi_and_open_hand(self): table = Table() player = table.player player.in_tempai = True player.in_riichi = False player.scores = 2000 player.melds = [Meld()] player.table.count_of_remaining_tiles = 40 self.assertEqual(player.formal_riichi_conditions(), False) player.melds = [] self.assertEqual(player.formal_riichi_conditions(), True)
def meld_3_tiles(boxes, meld_type=None): man, pin, sou, honors = to_136_string(boxes[:3]) if man is None: return None # print('man', man, 'pin', pin, 'sou', sou, 'honors', honors) tiles = tc.string_to_136_array(man=man, pin=pin, sou=sou, honors=honors) if meld_type is None: if boxes_is_pon(boxes): meld_type = Meld.PON elif boxes_is_chi(boxes): meld_type = Meld.CHI else: print("error: neither pon nor chi({})".format(boxes)) return None # print(tc.to_one_line_string(tiles)) return Meld(meld_type=meld_type, tiles=tiles)
def _make_meld(self, meld_type, is_open=True, man="", pin="", sou="", honors=""): tiles = self._string_to_136_array(man=man, pin=pin, sou=sou, honors=honors) meld = Meld(meld_type=meld_type, tiles=tiles, opened=is_open, called_tile=tiles[0], who=0) return meld
def open_hands_detail_to_melds(self, open_hands_detail): melds = [] for ohd in open_hands_detail: tiles = ohd["tiles"] if ohd["meld_type"] == "Pon": meld_type = "pon" opened = True elif ohd["meld_type"] == "Chi": meld_type = "chi" opened = True elif ohd["meld_type"] == "AnKan": meld_type = "kan" opened = False else: meld_type = "kan" opened = True meld = Meld(meld_type, tiles, opened) melds.append(meld) return melds
def chi(self, id, z): # z=-1:lower tile # z=0 :middle tile # z=1 :higher tile if id >= 27 * 4: # honor tiles return False a, b = (-1, -1) if z == -1: if 9 * 4 - 8 <= id < 9 * 4 or 9 * 8 - 8 <= id < 9 * 8 or 9 * 12 - 8 <= id: return False for t in self.hand: if t - t % 4 == 4 + id - id % 4: a = t if t - t % 4 == 8 + id - id % 4: b = t elif z == 0: if id < 4 or 9 * 4 - 4 <= id < 9 * 4 + 4 or 9 * 8 - 4 <= id < 9 * 8 + 4 or 9 * 12 - 4 <= id: return False for t in self.hand: if t - t % 4 == -4 + id - id % 4: a = t if t - t % 4 == 4 + id - id % 4: b = t elif z == 1: if id < 8 or 9 * 4 <= id < 9 * 4 + 8 or 9 * 8 <= id < 9 * 8 + 8: return False for t in self.hand: if t - t % 4 == -8 + id - id % 4: a = t if t - t % 4 == -4 + id - id % 4: b = t else: return False if a + b < 0: return False self.melds.append(Meld(meld_type=Meld.CHI, tiles=[a, b, id])) self.melds34.append(TilesConverter.to_34_array([a, b, id])) self.hand.remove(a) self.hand.remove(b) self.tiles34[a // 4] -= 1 self.tiles34[b // 4] -= 1 return True
def test_closed_kan_and_riichi(self): table = Table() table.count_of_remaining_tiles = 60 player = table.player player.scores = 25000 kan_tiles = self._string_to_136_array(pin='7777') tiles = self._string_to_136_array(pin='568', sou='1235788') + kan_tiles[:3] player.init_hand(tiles) # +3 to avoid tile duplication of 7 pin tile = kan_tiles[3] player.draw_tile(tile) kan_type = player.should_call_kan(tile, False) self.assertEqual(kan_type, Meld.KAN) meld = Meld() meld.type = Meld.KAN meld.tiles = kan_tiles meld.called_tile = tile meld.who = 0 meld.from_who = 0 meld.opened = False # replacement from the dead wall player.draw_tile(self._string_to_136_tile(pin='4')) table.add_called_meld(meld.who, meld) discard = player.discard_tile() self.assertEqual(self._to_string([discard]), '8p') self.assertEqual(player.can_call_riichi(), True) # with closed kan we can't call riichi player.melds[0].opened = True self.assertEqual(player.can_call_riichi(), False)
def test_call_meld_closed_kan(self): client = Client() client.table.init_round(0, 0, 0, 0, 0, [0, 0, 0, 0]) self.assertEqual(client.table.count_of_remaining_tiles, 70) meld = Meld() client.table.add_called_meld(0, meld) self.assertEqual(len(client.player.melds), 1) self.assertEqual(client.table.count_of_remaining_tiles, 71) client.player.tiles = [0] meld = Meld() meld.type = Meld.KAN # closed kan meld.tiles = [0, 1, 2, 3] meld.called_tile = None meld.opened = False client.table.add_called_meld(0, meld) self.assertEqual(len(client.player.melds), 2) # kan was closed, so -1 self.assertEqual(client.table.count_of_remaining_tiles, 70)
def test_call_meld_kan_from_player(self): client = Client() client.table.init_round(0, 0, 0, 0, 0, [0, 0, 0, 0]) self.assertEqual(client.table.count_of_remaining_tiles, 70) meld = Meld() client.table.add_called_meld(0, meld) self.assertEqual(len(client.player.melds), 1) self.assertEqual(client.table.count_of_remaining_tiles, 71) client.player.tiles = [0] meld = Meld() meld.type = Meld.KAN # closed kan meld.tiles = [0, 1, 2, 3] meld.called_tile = 0 meld.opened = True client.table.add_called_meld(0, meld) self.assertEqual(len(client.player.melds), 2) # kan was called from another player, total number of remaining tiles stays the same self.assertEqual(client.table.count_of_remaining_tiles, 71)
def test_call_meld(self): client = Client() client.table.init_round(0, 0, 0, 0, 0, [0, 0, 0, 0]) self.assertEqual(client.table.count_of_remaining_tiles, 70) meld = Meld() client.table.add_called_meld(0, meld) self.assertEqual(len(client.player.melds), 1) self.assertEqual(client.table.count_of_remaining_tiles, 71) client.player.tiles = [0] meld = Meld() meld.type = Meld.KAN meld.called_tile = 0 client.table.add_called_meld(0, meld) self.assertEqual(len(client.player.melds), 2) # +1 for called meld # -1 for called kan self.assertEqual(client.table.count_of_remaining_tiles, 71)
def try_to_call_meld(self, tile, is_kamicha_discard): """ Determine should we call a meld or not. If yes, it will return Meld object and tile to discard :param tile: 136 format tile :param is_kamicha_discard: boolean :return: Meld and DiscardOption objects """ if self.player.in_riichi: return None, None if self.player.ai.in_defence: return None, None closed_hand = self.player.closed_hand[:] # we can't open hand anymore if len(closed_hand) == 1: return None, None # we can't use this tile for our chosen strategy if not self.is_tile_suitable(tile): return None, None discarded_tile = tile // 4 new_tiles = self.player.tiles[:] + [tile] closed_hand_34 = TilesConverter.to_34_array(closed_hand + [tile]) combinations = [] first_index = 0 second_index = 0 if is_man(discarded_tile): first_index = 0 second_index = 8 elif is_pin(discarded_tile): first_index = 9 second_index = 17 elif is_sou(discarded_tile): first_index = 18 second_index = 26 if second_index == 0: # honor tiles if closed_hand_34[discarded_tile] == 3: combinations = [[[discarded_tile] * 3]] else: # to avoid not necessary calculations # we can check only tiles around +-2 discarded tile first_limit = discarded_tile - 2 if first_limit < first_index: first_limit = first_index second_limit = discarded_tile + 2 if second_limit > second_index: second_limit = second_index combinations = self.player.ai.hand_divider.find_valid_combinations(closed_hand_34, first_limit, second_limit, True) if combinations: combinations = combinations[0] possible_melds = [] for best_meld_34 in combinations: # we can call pon from everyone if is_pon(best_meld_34) and discarded_tile in best_meld_34: if best_meld_34 not in possible_melds: possible_melds.append(best_meld_34) # we can call chi only from left player if is_chi(best_meld_34) and is_kamicha_discard and discarded_tile in best_meld_34: if best_meld_34 not in possible_melds: possible_melds.append(best_meld_34) # we can call melds only with allowed tiles validated_melds = [] for meld in possible_melds: if (self.is_tile_suitable(meld[0] * 4) and self.is_tile_suitable(meld[1] * 4) and self.is_tile_suitable(meld[2] * 4)): validated_melds.append(meld) possible_melds = validated_melds if not possible_melds: return None, None best_meld_34 = self._find_best_meld_to_open(possible_melds, new_tiles) if best_meld_34: # we need to calculate count of shanten with supposed meld # to prevent bad hand openings melds = self.player.open_hand_34_tiles + [best_meld_34] outs_results, shanten = self.player.ai.calculate_outs(new_tiles, closed_hand, melds) # each strategy can use their own value to min shanten number if shanten > self.min_shanten: return None, None # we can't improve hand, so we don't need to open it if not outs_results: return None, None # sometimes we had to call tile, even if it will not improve our hand # otherwise we can call only with improvements of shanten if not self.meld_had_to_be_called(tile) and shanten >= self.player.ai.previous_shanten: return None, None meld_type = is_chi(best_meld_34) and Meld.CHI or Meld.PON best_meld_34.remove(discarded_tile) first_tile = TilesConverter.find_34_tile_in_136_array(best_meld_34[0], closed_hand) closed_hand.remove(first_tile) second_tile = TilesConverter.find_34_tile_in_136_array(best_meld_34[1], closed_hand) closed_hand.remove(second_tile) tiles = [ first_tile, second_tile, tile ] meld = Meld() meld.type = meld_type meld.tiles = sorted(tiles) # we had to be sure that all our discard results exists in the closed hand filtered_results = [] for result in outs_results: if result.find_tile_in_hand(closed_hand): filtered_results.append(result) # we can't discard anything, so let's not open our hand if not filtered_results: return None, None selected_tile = self.player.ai.process_discard_options_and_select_tile_to_discard( filtered_results, shanten, had_was_open=True ) return meld, selected_tile return None, None
def try_to_call_meld(self, tile, is_kamicha_discard): """ When bot can open hand with a set (chi or pon/kan) this method will be called :param tile: 136 format tile :param is_kamicha_discard: boolean :return: Meld and DiscardOption objects or None, None """ # can't call if in riichi if self.player.in_riichi: return None, None closed_hand = self.player.closed_hand[:] # check for appropriate hand size, seems to solve a bug if len(closed_hand) == 1: return None, None # get old shanten value old_tiles_34 = TilesConverter.to_34_array(self.player.tiles) old_shanten = self.shanten.calculate_shanten(old_tiles_34, self.player.open_hand_34_tiles) # setup discarded_tile = tile // 4 new_closed_hand_34 = TilesConverter.to_34_array(closed_hand + [tile]) # We will use hand_divider to find possible melds involving the discarded tile. # Check its suit and number to narrow the search conditions # skipping this will break the default mahjong functions combinations = [] first_index = 0 second_index = 0 if is_man(discarded_tile): first_index = 0 second_index = 8 elif is_pin(discarded_tile): first_index = 9 second_index = 17 elif is_sou(discarded_tile): first_index = 18 second_index = 26 if second_index == 0: # honor tiles if new_closed_hand_34[discarded_tile] == 3: combinations = [[[discarded_tile] * 3]] else: # to avoid not necessary calculations # we can check only tiles around +-2 discarded tile first_limit = discarded_tile - 2 if first_limit < first_index: first_limit = first_index second_limit = discarded_tile + 2 if second_limit > second_index: second_limit = second_index combinations = self.hand_divider.find_valid_combinations(new_closed_hand_34, first_limit, second_limit, True) # Reduce combinations to list of melds if combinations: combinations = combinations[0] # Verify that a meld can be called possible_melds = [] for meld_34 in combinations: # we can call pon from everyone if is_pon(meld_34) and discarded_tile in meld_34: if meld_34 not in possible_melds: possible_melds.append(meld_34) # we can call chi only from left player if is_chi(meld_34) and is_kamicha_discard and discarded_tile in meld_34: if meld_34 not in possible_melds: possible_melds.append(meld_34) # For each possible meld, check if calling it and discarding can improve shanten new_shanten = float('inf') discard_136 = None tiles = None for meld_34 in possible_melds: shanten, disc = self.meldDiscard(meld_34, tile) if shanten < new_shanten: new_shanten, discard_136 = shanten, disc tiles = meld_34 # If shanten can be improved by calling meld, call it if new_shanten < old_shanten: meld = Meld() meld.type = is_chi(tiles) and Meld.CHI or Meld.PON # convert meld tiles back to 136 format for Meld type return # find them in a copy of the closed hand and remove tiles.remove(discarded_tile) first_tile = TilesConverter.find_34_tile_in_136_array(tiles[0], closed_hand) closed_hand.remove(first_tile) second_tile = TilesConverter.find_34_tile_in_136_array(tiles[1], closed_hand) closed_hand.remove(second_tile) tiles_136 = [ first_tile, second_tile, tile ] discard_136 = TilesConverter.find_34_tile_in_136_array(discard_136 // 4, closed_hand) meld.tiles = sorted(tiles_136) return meld, discard_136 return None, None
def yaku(self, player, is_tsumo, agari_hai=None, chankan=False): # 手牌 tile_strs = [""] * 4 win_tile_strs = [""] * 4 for hai in player.tehai.hais: tile_strs[hai.kind] += "r" if hai.red else str(hai.num) for furo in player.tehai.furos: for hai in furo.hais: tile_strs[hai.kind] += "r" if hai.red else str(hai.num) if furo.kind == FuroKind.ANKAN or furo.kind == FuroKind.MINKAN or furo.kind == FuroKind.KAKAN: tile_strs[furo.hais[0].kind] = tile_strs[ furo.hais[0].kind][:-1] if is_tsumo: hai = player.tehai.tsumo_hai tile_strs[hai.kind] += "r" if hai.red else str(hai.num) win_tile_strs[hai.kind] += "r" if hai.red else str(hai.num) else: tile_strs[agari_hai.kind] += "r" if agari_hai.red else str( agari_hai.num) win_tile_strs[agari_hai.kind] += "r" if agari_hai.red else str( agari_hai.num) tiles = TilesConverter.string_to_136_array(tile_strs[0], tile_strs[1], tile_strs[2], tile_strs[3], True) win_tile = TilesConverter.string_to_136_array(win_tile_strs[0], win_tile_strs[1], win_tile_strs[2], win_tile_strs[3], True)[0] # 副露 FURO_MELD = { FuroKind.PON: Meld.PON, FuroKind.CHI: Meld.CHI, FuroKind.ANKAN: Meld.KAN, FuroKind.MINKAN: Meld.KAN, FuroKind.KAKAN: Meld.CHANKAN } melds = [] for furo in player.tehai.furos: furo_strs = [""] * 4 for hai in furo.hais: furo_strs[hai.kind] += "r" if hai.red else str(hai.num) meld_tiles = TilesConverter.string_to_136_array( furo_strs[0], furo_strs[1], furo_strs[2], furo_strs[3], True) melds.append( Meld(FURO_MELD[furo.kind], meld_tiles, furo.kind != FuroKind.ANKAN)) # ドラ dora_indicators = [] for hai in self.dora_hais: dora_strs = [""] * 4 dora_strs[hai.kind] += "r" if hai.red else str(hai.num) dora_tile = TilesConverter.string_to_136_array( dora_strs[0], dora_strs[1], dora_strs[2], dora_strs[3], True)[0] dora_indicators.append(dora_tile) if player.richi: for hai in self.uradora_hais: dora_strs = [""] * 4 dora_strs[hai.kind] += "r" if hai.red else str(hai.num) dora_tile = TilesConverter.string_to_136_array( dora_strs[0], dora_strs[1], dora_strs[2], dora_strs[3], True)[0] dora_indicators.append(dora_tile) # 設定 KAZE_WIND = [EAST, SOUTH, WEST, NORTH] config = HandConfig( is_tsumo=is_tsumo, is_riichi=player.richi, is_ippatsu=player.ippatsu, is_rinshan=player.rinshan, is_chankan=chankan, is_haitei=agari_hai is None and self.yama.remain == 0, is_houtei=agari_hai is not None and self.yama.remain == 0, is_daburu_riichi=len(player.kawa.hais) > 0 and player.kawa.hais[0].richi, is_tenhou=agari_hai is None and self.jikaze(player) == 0 and len(player.kawa.hais) == 0, is_renhou=agari_hai is not None and len(player.kawa.hais) == 0, is_chiihou=agari_hai is None and self.jikaze(player) != 0 and len(player.kawa.hais) == 0, player_wind=KAZE_WIND[self.jikaze(player)], round_wind=KAZE_WIND[self.bakaze], options=OptionalRules(has_aka_dora=True)) return self.calculator.estimate_hand_value(tiles, win_tile, melds, dora_indicators, config)
def maj_cal(data): """{ "tehai":{ "man":"111", "pin":"222", "sou":"333", "honors":"11166" }, "agari":{ "man":"1", "pin":"", "sou":"", "honors":"" }, "wind":{ "round":"1", "player":"1", }, "dora": {"man":"", "pin":"1", "sou":"", "honors":"" }, "kan":[ {"man":"", "pin":"", "sou":"", "honors":"", "open":"" } ], "pon":[ {"man":"", "pin":"", "sou":"", "honors":"" } ], "chi":[ {"man":"", "pin":"", "sou":"", "honors":"" } ], "option":[] }""" print(data) has_aka_dora = False for d_i, d in data.items(): if d_i != "option" and has_aka_dora == False: if type(d) == type({}): for i, v in d.items(): if i != "open" and "r" in v: has_aka_dora = True break elif type(d) == type([]): for d2 in d: for i, v in d2.items(): # print("v",v) if i != "open" and "r" in v: has_aka_dora = True break handconfig = HandConfig( options=OptionalRules(has_open_tanyao=True, has_aka_dora=has_aka_dora)) melds = [] dora_indicators = [] yaku_opened = False if "wind" in data.keys(): handconfig.round_wind = WINDS[int(data["wind"]["round"]) - 1] handconfig.player_wind = WINDS[int(data["wind"]["player"]) - 1] handconfig.is_dealer = handconfig.player_wind == WINDS[0] if "kan" in data.keys() and len(data["kan"]) > 0: for kan in data["kan"]: opened = (kan["open"] in {"1", 1}) melds.append( Meld(meld_type=Meld.KAN, tiles=to_136_array(obj=kan), opened=opened)) if opened: yaku_opened = opened if "pon" in data.keys() and len(data["pon"]) > 0: for pon in data["pon"]: melds.append(Meld(meld_type=Meld.PON, tiles=to_136_array(obj=pon))) yaku_opened = True if "chi" in data.keys() and len(data["chi"]) > 0: for chi in data["chi"]: #配列内をソート for chi_key in chi.keys(): if chi[chi_key] != "": r_flag = False if "r" in chi[chi_key]: r_flag = True chi[chi_key] = chi[chi_key].replace("r", "5") tmp = chi[chi_key] tmp = sorted([tmp[0], tmp[1], tmp[2]]) chi[chi_key] = "".join(tmp) if r_flag: chi[chi_key] = chi[chi_key].replace("5", "r") print(chi[chi_key]) melds.append(Meld(meld_type=Meld.CHI, tiles=to_136_array(obj=chi))) yaku_opened = True if "dora" in data.keys(): dora_indicators = to_136_array(obj=data["dora"]) if "option" in data.keys(): for i in data["option"]: if i in ["tsumo", "ツモ", "つも"]: handconfig.is_tsumo = True elif i in ["riichi", "リーチ", "りーち"]: handconfig.is_riichi = True elif i in ["rinshan", "嶺上", "りんしゃん", "リンシャン"]: handconfig.is_rinshan = True elif i in ["ippatsu", "一発", "いっぱつ", "イッパツ"]: handconfig.is_ippatsu = True elif i in ["chankan", "槍槓", "ちゃんかん", "チャンカン"]: handconfig.is_chankan = True elif i in ["haitei", "海底", "はいてい", "ハイテイ"]: handconfig.is_haitei = True elif i in ["houtei", "河底", "ほうてい", "ホウテイ"]: handconfig.is_houtei = True elif i in ["daburu_riichi", "だぶりー", "ダブリー", "だぶるりーち", "ダブルリーチ"]: handconfig.is_daburu_riichi = True elif i in ["nagashi_mangan", "流しマンガン", "ナガシマンガン", "ながしまんがん"]: handconfig.is_nagashi_mangan = True elif i in ["tenhou", "テンホウ", "てんほう"]: handconfig.is_tenhou = True elif i in ["renhou", "レンホウ", "れんほう"]: handconfig.is_renhou = True elif i in ["chiihou", "ちいほう", "チイホウ"]: handconfig.is_chiihou = True import json #print(json.dumps(data["tehai"], indent=4)) #print(json.dumps(data["agari"], indent=4)) tiles = to_136_array(data["tehai"], has_aka_dora=has_aka_dora) win_tile = to_136_array(data["agari"], has_aka_dora=has_aka_dora)[0] if len(dora_indicators) == []: dora_indicators = None if len(melds) == 0: melds = None result = calculator.estimate_hand_value(tiles, win_tile, melds=melds, dora_indicators=dora_indicators, config=handconfig) return print_hand_result(result, opened=yaku_opened)
#################################################################### # Tanyao hand by tsumo # #################################################################### result = calculator.estimate_hand_value(tiles, win_tile, config=HandConfig(is_tsumo=True)) print_hand_result(result) #################################################################### # Add open set to hand # #################################################################### melds = [ Meld(meld_type=Meld.PON, tiles=TilesConverter.string_to_136_array(man='444')) ] result = calculator.estimate_hand_value( tiles, win_tile, melds=melds, config=HandConfig(has_open_tanyao=True)) print_hand_result(result) #################################################################### # Shanten calculation # #################################################################### shanten = Shanten() tiles = TilesConverter.string_to_34_array(man='13569', pin='123459', sou='443') result = shanten.calculate_shanten(tiles) print(result)
def win(): req = flask.request.get_json() calculator = HandCalculator() try: tiles = TilesConverter.one_line_string_to_136_array(req["hands"], has_aka_dora=True) except KeyError: return flask.jsonify({"error": "hands required"}), 400 except ValueError: return flask.jsonify({"error": "invalid hands"}), 400 try: win_tile = TilesConverter.one_line_string_to_136_array( req["win_tile"], has_aka_dora=True)[0] except KeyError: return flask.jsonify({"error": "win_tile required"}), 400 except ValueError: return flask.jsonify({"error": "invalid win_tile"}), 400 melds = [] try: melds = req["melds"] except KeyError: pass meld_tiles = [] for meld in melds: try: m = meld["meld"] except KeyError: return flask.jsonify({"error": "meld required"}), 400 try: if m == "chi": meld_tiles.append( Meld( Meld.CHI, TilesConverter.one_line_string_to_136_array( meld["tiles"]))) elif m == "pon": meld_tiles.append( Meld( Meld.PON, TilesConverter.one_line_string_to_136_array( meld["tiles"]))) elif m == "minkan": meld_tiles.append( Meld( Meld.KAN, TilesConverter.one_line_string_to_136_array( meld["tiles"]), True)) elif m == "ankan": meld_tiles.append( Meld( Meld.KAN, TilesConverter.one_line_string_to_136_array( meld["tiles"]), False)) elif m == "kakan": meld_tiles.append( Meld( Meld.CHANKAN, TilesConverter.one_line_string_to_136_array( meld["tiles"]))) else: return flask.jsonify({"error": "invalid meld"}), 400 except KeyError: return flask.jsonify({"error": "meld tiles required"}), 400 except ValueError: return flask.jsonify({"error": "invalid meld tiles"}), 400 try: tsumo = req["tsumo"] except KeyError: tsumo = False try: riichi = req["riichi"] except KeyError: riichi = False try: ippatsu = req["ippatsu"] except KeyError: ippatsu = False try: rinshan = req["rinshan"] except KeyError: rinshan = False try: chankan = req["chankan"] except KeyError: chankan = False try: haitei = req["haitei"] except KeyError: haitei = False try: houtei = req["houtei"] except KeyError: houtei = False try: double_riichi = req["double_riichi"] except KeyError: double_riichi = False try: tenhou = req["tenhou"] except KeyError: tenhou = False try: chiihou = req["chiihou"] except KeyError: chiihou = False try: wind_player = req["wind_player"] if wind_player == "east": wind_player = EAST elif wind_player == "south": wind_player = SOUTH elif wind_player == "west": wind_player = WEST elif wind_player == "north": wind_player = NORTH else: return flask.jsonify({"error": "invalid wind_player"}), 400 except KeyError: return flask.jsonify({"error": "wind_player required"}), 400 try: wind_round = req["wind_round"] if wind_round == "east": wind_round = EAST elif wind_round == "south": wind_round = SOUTH elif wind_round == "west": wind_round = WEST elif wind_round == "north": wind_round = NORTH else: return flask.jsonify({"error": "invalid wind_round"}), 400 except KeyError: return flask.jsonify({"error": "wind_round required"}), 400 try: dora_indicators = TilesConverter.one_line_string_to_136_array( req["dora_indicators"], has_aka_dora=True) except KeyError: return flask.jsonify({"error": "dora_indicators required"}), 400 except ValueError: return flask.jsonify({"error": "invalid dora_indicators"}), 400 option = OptionalRules(has_open_tanyao=True, has_aka_dora=True, has_double_yakuman=False) config = HandConfig(is_tsumo=tsumo, is_riichi=riichi, is_ippatsu=ippatsu, is_rinshan=rinshan, is_chankan=chankan, is_haitei=haitei, is_houtei=houtei, is_daburu_riichi=double_riichi, is_tenhou=tenhou, is_chiihou=chiihou, player_wind=wind_player, round_wind=wind_round, options=option) result = calculator.estimate_hand_value(tiles, win_tile, dora_indicators=dora_indicators, config=config) if result.error: return flask.jsonify({"error": result.error}), 400 yaku = map(lambda y: y.japanese, result.yaku) return flask.jsonify({ "cost": result.cost, "han": result.han, "fu": result.fu, "yaku": list(yaku), "error": None })