def __init__(self, paifu_path_list, n_max=100): self.paifu_path_list = paifu_path_list self.data_size = len(paifu_path_list) * n_max self.shanten_calculator = Shanten() self.n_max = n_max # self.device = 'cuda' if torch.cuda.is_available() else 'cpu' self.device = 'cpu'
def CountShanten(haishi): ''' 牌姿データからシャンテン数を数える ''' man = '' pin = '' sou = '' honors = '' for x in haishi.split(','): if table.pai2[int(x)][1] == 'm': man += table.pai2[int(x)][0] if table.pai2[int(x)][1] == 'p': pin += table.pai2[int(x)][0] if table.pai2[int(x)][1] == 's': sou += table.pai2[int(x)][0] if table.pai2[int(x)][1] == 'z': honors += table.pai2[int(x)][0] shanten = Shanten() tiles = TilesConverter.string_to_34_array( man=man, pin=pin, sou=sou, honors=honors, ) c = shanten.calculate_shanten(tiles) return (c if c > 0 else 0)
def __init__(self): self.shanten = Shanten() self.agari = Agari() self.finished_hand = HandCalculator() self.data_to_save = [] self.csv_exporter = CSVExporter()
def __init__(self, player_seat, opponent): self.playereat = player_seat self.isOpponent = opponent self.hand = np.zeros(34, int) self.discarded = np.zeros(34, int) self.melds = np.zeros((34, 3), int) self.lastDraw = None self.riichi = False self.calshanten = Shanten()
def __init__(self): """ changed the input from filename to tiles_state_and_action data By Jun Lin """ self.shanten_calculator = Shanten() self.hc = HandCalculator() self.sc = ScoresCalculator() self.hand_cache_shanten = {} self.hand_cache_points = {}
def __init__(self, tiles, dora_indicators, revealed_tiles): self.tiles = tiles self.Shanten_calculater = Shanten() self.Hand_Calculator = HandCalculator() self.Tiles = TilesConverter() self.shanten = self.Shanten_calculater.calculate_shanten( self.Tiles.to_34_array(self.tiles)) self.Hand_Config = HandConfig(is_riichi=True) self.dora_indicators = dora_indicators self.revealed_tiles = revealed_tiles
def getTile(s: str) -> dict: #translate s into supported format if len(s) == 0: return {} tiles = TilesConverter.one_line_string_to_136_array(s, True) test = {} # check tiles length added = [] if len(tiles) % 3 == 0: #3n, add a random tile while True: tmp = randint(0, 135) if tmp not in tiles: tiles.append(tmp) added.append(tmp) break if len(tiles) % 3 == 1: #3n+1, add a random tile while True: tmp = randint(0, 135) if tmp not in tiles: tiles.append(tmp) added.append(tmp) break if len(added) > 0: test[-1] = added # now is a normal form tiles.sort() avaliable = [] for i in range(0, 136): if i not in tiles: avaliable.append(i) calculator = Shanten() baseShanten = calculator.calculate_shanten( TilesConverter.to_34_array(tiles)) if baseShanten == -1: return {-2: '已和牌'} #14*122 try tmp = copy.deepcopy(tiles) for t in tiles: tmp.remove(t) one_try = [] for i in avaliable: tmp.append(i) res = calculator.calculate_shanten( TilesConverter.to_34_array(sorted(tmp))) if res < baseShanten: one_try.append(i) tmp.remove(i) t = 4 * (t // 4) if len(one_try) > 0 and t not in test.keys(): test[t] = copy.deepcopy(one_try) tmp.append(t) return test
def test_shanten_for_not_completed_hand(self): shanten = Shanten() tiles = self._string_to_34_array(sou="111345677", pin="1", man="567") self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles), 1) tiles = self._string_to_34_array(sou="111345677", man="567") self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles), 1) tiles = self._string_to_34_array(sou="111345677", man="56") self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles), 0)
def __init__(self, player): super(ImplementationAI, self).__init__(player) self.agari = Agari() self.shanten = Shanten() self.defence = DefenceHandler(player) self.hand_divider = HandDivider() self.finished_hand = HandCalculator() self.previous_shanten = 7 self.current_strategy = None self.waiting = [] self.in_defence = False self.last_discard_option = None
def test_shanten_number_and_chiitoitsu(self): shanten = Shanten() tiles = self._string_to_34_array(sou="114477", pin="114477", man="77") self.assertEqual(shanten.calculate_shanten_for_chiitoitsu_hand(tiles), Shanten.AGARI_STATE) tiles = self._string_to_34_array(sou="114477", pin="114477", man="76") self.assertEqual(shanten.calculate_shanten_for_chiitoitsu_hand(tiles), 0) tiles = self._string_to_34_array(sou="114477", pin="114479", man="76") self.assertEqual(shanten.calculate_shanten_for_chiitoitsu_hand(tiles), 1) tiles = self._string_to_34_array(sou="114477", pin="14479", man="76", honors="1") self.assertEqual(shanten.calculate_shanten_for_chiitoitsu_hand(tiles), 2) tiles = self._string_to_34_array(sou="114477", pin="13479", man="76", honors="1") self.assertEqual(shanten.calculate_shanten_for_chiitoitsu_hand(tiles), 3) tiles = self._string_to_34_array(sou="114467", pin="13479", man="76", honors="1") self.assertEqual(shanten.calculate_shanten_for_chiitoitsu_hand(tiles), 4) tiles = self._string_to_34_array(sou="114367", pin="13479", man="76", honors="1") self.assertEqual(shanten.calculate_shanten_for_chiitoitsu_hand(tiles), 5) tiles = self._string_to_34_array(sou="124367", pin="13479", man="76", honors="1") self.assertEqual(shanten.calculate_shanten_for_chiitoitsu_hand(tiles), 6)
def test_shanten_number_and_open_sets(self): shanten = Shanten() tiles = self._string_to_34_array(sou='44467778', pin='222567') self.assertEqual(shanten.calculate_shanten(tiles), Shanten.AGARI_STATE) melds = [self._string_to_open_34_set(sou='777')] self.assertEqual(shanten.calculate_shanten(tiles, melds), 0) tiles = self._string_to_34_array(sou='23455567', pin='222', man='345') melds = [ self._string_to_open_34_set(man='345'), self._string_to_open_34_set(sou='555'), ] self.assertEqual(shanten.calculate_shanten(tiles, melds), 0)
def shanten(): shanten = Shanten() req = flask.request.get_json() try: tiles = TilesConverter.one_line_string_to_34_array(req["hands"]) except KeyError: return flask.jsonify({"error": "hands required"}), 400 result = shanten.calculate_shanten(tiles) if result == -2: return flask.jsonify({"error": "hands over 14 tiles"}), 400 return flask.jsonify({"shanten": result, "error": None})
def draw_tile(self, tile): """ :param tile: 136 tile format :return: """ self.player.in_tempai = False self.shanten_calculator = Shanten() Tile = TilesConverter() current_shanten = self.shanten_calculator.calculate_shanten( Tile.to_34_array(self.player.tiles)) logger.debug('Current_shanten:{}'.format(current_shanten)) if current_shanten == 0: self.player.in_tempai = True logger.debug('whether_in_tempai:{}'.format(self.player.in_tempai)) self.shanten_calculator = None
def check_can_reach(self, player): if self.open_cnt[player] > 0: return 0 now_reach = self.reach[player][0] for i in range(136): if now_reach[i][0]: return 0 now_closehand = [ i for i in range(136) if self.closehand[player][0][i][0] == 1 ] #import mahjong shanten = Shanten() tiles = TilesConverter.to_34_array(now_closehand) result = shanten.calculate_shanten(tiles) return result <= 0
def tenpai(tiles, sute): shanten = Shanten() if len(tiles) == 34: # [3,1,1,...,3,0,...,0] tiles_34 = tiles else: # man='1112345678999', pin='', sou='', honors='' tiles_34 = TilesConverter.string_to_34_array(tiles) result = [0] * 34 for i in range(34): if tiles_34[i] < 4 and not sute[i]: tiles_34[i] += 1 if shanten.calculate_shanten(tiles_34) == -1: result[i] = 1 tiles_34[i] -= 1 return result
def __init__(self, player): self.player = player self.table = player.table self.chi = Chi(player) self.pon = Pon(player) self.kan = Kan(player) self.riichi = Riichi(player) self.discard = Discard(player) self.grp = GlobalRewardPredictor() self.hand_builder = HandBuilder(player, self) self.shanten_calculator = Shanten() self.hand_cache_shanten = {} self.placement = player.config.PLACEMENT_HANDLER_CLASS(player) self.finished_hand = HandCalculator() self.hand_divider = HandDivider() self.erase_state()
def __init__(self, player): self.player = player self.table = player.table self.kan = Kan(player) self.agari = Agari() self.shanten_calculator = Shanten() self.defence = TileDangerHandler(player) self.riichi = Riichi(player) self.hand_divider = HandDivider() self.finished_hand = HandCalculator() self.hand_builder = HandBuilder(player, self) self.placement = player.config.PLACEMENT_HANDLER_CLASS(player) self.suji = Suji(player) self.kabe = Kabe(player) self.erase_state()
def __init__(self, player): super(ImplementationAI, self).__init__(player) self.agari = Agari() self.shanten_calculator = Shanten() self.defence = DefenceHandler(player) self.riichi = Riichi(player) self.hand_divider = HandDivider() self.finished_hand = HandCalculator() self.hand_builder = HandBuilder(player, self) self.erase_state()
def test_game(ai): pai = TEST_INIT_PAI.copy() random.shuffle(pai) calculator = HandCalculator() config = HandConfig(is_tsumo=True) config.yaku.yakuhai_place = config.yaku.east config.yaku.yakuhai_round = config.yaku.east # 1. 내 초기 패 13개 + 쯔모 1개 hand = pai[:14].copy() tsumo = pai[13] # 2. tsumo 하자 for x in range(1, TEST_STEP + 1): # DEBUG: 정상 작동하는지 중간 패 과정 출력 logging.debug("%02d: %s", x, TilesConverter.to_one_line_string(hand)) # 점수 확인 result = calculator.estimate_hand_value(hand, tsumo) if not result.error: # 화료가 되었다는 뜻이다. 친의 쯔모인 3배 점수를 반환하자 return result.cost['main'] * 3 if x == TEST_STEP: break # 버린다 discard = ai.next_move(hand, tsumo, TEST_STEP - x - 1) hand.remove(discard) # 쯔모한다 tsumo = pai[14 + x] hand.append(tsumo) # 마지막으로 텐파이인지 확인한다 # 샹텐수가 0이면 텐파이이다 hand_34 = TilesConverter.to_34_array(hand) shanten = Shanten() result = shanten.calculate_shanten(hand_34) logging.debug('@: %d', result) if result == 0: return TEST_TEN_SCORE return TEST_NOTEN_SCORE
def test_shanten_number_and_open_sets(self): shanten = Shanten() tiles = self._string_to_34_array(sou="44467778", pin="222567") self.assertEqual(shanten.calculate_shanten(tiles), Shanten.AGARI_STATE) tiles = self._string_to_34_array(sou="44468", pin="222567") self.assertEqual(shanten.calculate_shanten(tiles), 0) tiles = self._string_to_34_array(sou="68", pin="222567") self.assertEqual(shanten.calculate_shanten(tiles), 0) tiles = self._string_to_34_array(sou="68", pin="567") self.assertEqual(shanten.calculate_shanten(tiles), 0) tiles = self._string_to_34_array(sou="68") self.assertEqual(shanten.calculate_shanten(tiles), 0) tiles = self._string_to_34_array(sou="88") self.assertEqual(shanten.calculate_shanten(tiles), Shanten.AGARI_STATE)
class AI(defaultAI): def __init__(self): defaultAI.__init__(self) self.shanten = Shanten() self.log = logging.getLogger('ShantenAI') def next_move(self, hand, tsumo, remain_number): min_shanten = 8 check = [False] * 34 # 핸드 중복 확인 check_shanten = [8] * 14 # 손에 있는 14개 중에 어떤 것이 샹텐이 작은지 확인하는 배열 hand_34 = TilesConverter.to_34_array(hand) for x in range(len(hand)): x4 = hand[x] // 4 if check[x4]: # 같은 타일이면 체크 안해도 됨 continue check[x4] = True hand_34[x4] -= 1 max_shanten = -2 for y in range(34): if y == x4 or hand_34[y] == 4: continue hand_34[y] += 1 result = self.shanten.calculate_shanten(hand_34) if result > max_shanten: max_shanten = result hand_34[y] -= 1 hand_34[x4] += 1 if min_shanten > max_shanten: min_shanten = max_shanten check_shanten[x] = max_shanten check_discard = [] #무엇을 버려야 할지 결정해주는 배열 for x in range(14): if check_shanten[x] == min_shanten: #최소 샨텐에 해당하는 번째의 패이면 check_discard.append(x) self.log.debug("#: %d", min_shanten) return hand[random.choice(check_discard)] def get_name(self): return "shanten_min"
def test_shanten_number_and_chitoitsu(self): shanten = Shanten() tiles = self._string_to_34_array(sou='114477', pin='114477', man='77') self.assertEqual(shanten.calculate_shanten(tiles), Shanten.AGARI_STATE) tiles = self._string_to_34_array(sou='114477', pin='114477', man='76') self.assertEqual(shanten.calculate_shanten(tiles), 0) tiles = self._string_to_34_array(sou='114477', pin='114479', man='76') self.assertEqual(shanten.calculate_shanten(tiles), 1) tiles = self._string_to_34_array(sou='114477', pin='14479', man='76', honors='1') self.assertEqual(shanten.calculate_shanten(tiles), 2)
def test_shanten_number_and_kokushi_musou(self): shanten = Shanten() tiles = self._string_to_34_array(sou='19', pin='19', man='19', honors='12345677') self.assertEqual(shanten.calculate_shanten(tiles), Shanten.AGARI_STATE) tiles = self._string_to_34_array(sou='129', pin='19', man='19', honors='1234567') self.assertEqual(shanten.calculate_shanten(tiles), 0) tiles = self._string_to_34_array(sou='129', pin='129', man='19', honors='123456') self.assertEqual(shanten.calculate_shanten(tiles), 1) tiles = self._string_to_34_array(sou='129', pin='129', man='129', honors='12345') self.assertEqual(shanten.calculate_shanten(tiles), 2)
#################################################################### 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) #################################################################### # Kazoe as a sanbaiman # #################################################################### tiles = TilesConverter.string_to_136_array(man='22244466677788') win_tile = TilesConverter.string_to_136_array(man='7')[0] melds = [Meld(Meld.KAN, TilesConverter.string_to_136_array(man='2222'), False)] dora_indicators = [ TilesConverter.string_to_136_array(man='1')[0],
class ImplementationAI(InterfaceAI): version = '0.0.1' agari = None shanten = None defence = None hand_divider = None finished_hand = None last_discard_option = None previous_shanten = 7 in_defence = False waiting = None current_strategy = None def __init__(self, player): super(ImplementationAI, self).__init__(player) self.agari = Agari() self.shanten = Shanten() self.defence = DefenceHandler(player) self.hand_divider = HandDivider() self.finished_hand = HandCalculator() self.previous_shanten = 7 self.current_strategy = None self.waiting = [] self.in_defence = False self.last_discard_option = None def init_hand(self): """ Let's decide what we will do with our hand (like open for tanyao and etc.) """ self.determine_strategy() def erase_state(self): self.current_strategy = None self.in_defence = False self.last_discard_option = None def draw_tile(self, tile): """ :param tile: 136 tile format :return: """ self.determine_strategy() def discard_tile(self, discard_tile): # we called meld and we had discard tile that we wanted to discard if discard_tile is not None: if not self.last_discard_option: return discard_tile return self.process_discard_option(self.last_discard_option, self.player.closed_hand, True) results, shanten = self.calculate_outs(self.player.tiles, self.player.closed_hand, self.player.open_hand_34_tiles) selected_tile = self.process_discard_options_and_select_tile_to_discard( results, shanten) # bot think that there is a threat on the table # and better to fold # if we can't find safe tiles, let's continue to build our hand if self.defence.should_go_to_defence_mode(selected_tile): if not self.in_defence: logger.info('We decided to fold against other players') self.in_defence = True defence_tile = self.defence.try_to_find_safe_tile_to_discard( results) if defence_tile: return self.process_discard_option(defence_tile, self.player.closed_hand) else: self.in_defence = False return self.process_discard_option(selected_tile, self.player.closed_hand) def process_discard_options_and_select_tile_to_discard( self, results, shanten, had_was_open=False): tiles_34 = TilesConverter.to_34_array(self.player.tiles) # we had to update tiles value there # because it is related with shanten number for result in results: result.tiles_count = self.count_tiles(result.waiting, tiles_34) result.calculate_value(shanten) # current strategy can affect on our discard options # so, don't use strategy specific choices for calling riichi if self.current_strategy: results = self.current_strategy.determine_what_to_discard( self.player.closed_hand, results, shanten, False, None, had_was_open) return self.chose_tile_to_discard(results) def calculate_outs(self, tiles, closed_hand, open_sets_34=None): """ :param tiles: array of tiles in 136 format :param closed_hand: array of tiles in 136 format :param open_sets_34: array of array with tiles in 34 format :return: """ tiles_34 = TilesConverter.to_34_array(tiles) closed_tiles_34 = TilesConverter.to_34_array(closed_hand) is_agari = self.agari.is_agari(tiles_34, self.player.open_hand_34_tiles) results = [] for hand_tile in range(0, 34): if not closed_tiles_34[hand_tile]: continue tiles_34[hand_tile] -= 1 shanten = self.shanten.calculate_shanten(tiles_34, open_sets_34) waiting = [] for j in range(0, 34): if hand_tile == j or tiles_34[j] == 4: continue tiles_34[j] += 1 if self.shanten.calculate_shanten(tiles_34, open_sets_34) == shanten - 1: waiting.append(j) tiles_34[j] -= 1 tiles_34[hand_tile] += 1 if waiting: results.append( DiscardOption(player=self.player, shanten=shanten, tile_to_discard=hand_tile, waiting=waiting, tiles_count=self.count_tiles( waiting, tiles_34))) if is_agari: shanten = Shanten.AGARI_STATE else: shanten = self.shanten.calculate_shanten(tiles_34, open_sets_34) return results, shanten def count_tiles(self, waiting, tiles_34): n = 0 for item in waiting: n += 4 - self.player.total_tiles(item, tiles_34) return n def try_to_call_meld(self, tile, is_kamicha_discard): if not self.current_strategy: return None, None if len(self.player.discards) <= 5 and tile // 4 <= 27: return None, None meld, discard_option = self.current_strategy.try_to_call_meld( tile, is_kamicha_discard) tile_to_discard = None if discard_option: self.last_discard_option = discard_option tile_to_discard = discard_option.tile_to_discard return meld, tile_to_discard def determine_strategy(self): # for already opened hand we don't need to give up on selected strategy if self.player.is_open_hand and self.current_strategy: return False return False old_strategy = self.current_strategy self.current_strategy = None # order is important, the first appropriate strtegy will be used strategies = [] if self.player.table.has_open_tanyao: strategies.append(TanyaoStrategy(BaseStrategy.TANYAO, self.player)) strategies.append(YakuhaiStrategy(BaseStrategy.YAKUHAI, self.player)) strategies.append(HonitsuStrategy(BaseStrategy.HONITSU, self.player)) for strategy in strategies: if strategy.should_activate_strategy(): self.current_strategy = strategy if self.current_strategy: if not old_strategy or self.current_strategy.type != old_strategy.type: message = '{} switched to {} strategy'.format( self.player.name, self.current_strategy) if old_strategy: message += ' from {}'.format(old_strategy) logger.debug(message) logger.debug('With hand: {}'.format( TilesConverter.to_one_line_string(self.player.tiles))) if not self.current_strategy and old_strategy: logger.debug('{} gave up on {}'.format(self.player.name, old_strategy)) return self.current_strategy and True or False def chose_tile_to_discard(self, results: [DiscardOption]) -> DiscardOption: """ Try to find best tile to discard, based on different valuations """ def sorting(x): # - is important for x.tiles_count # in that case we will discard tile that will give for us more tiles # to complete a hand return x.shanten, -x.tiles_count, x.valuation had_to_be_discarded_tiles = [ x for x in results if x.had_to_be_discarded ] if had_to_be_discarded_tiles: had_to_be_discarded_tiles = sorted(had_to_be_discarded_tiles, key=sorting) selected_tile = had_to_be_discarded_tiles[0] else: results = sorted(results, key=sorting) # remove needed tiles from discard options results = [x for x in results if not x.had_to_be_saved] # let's chose most valuable tile first temp_tile = results[0] # and let's find all tiles with same shanten results_with_same_shanten = [ x for x in results if x.shanten == temp_tile.shanten ] possible_options = [temp_tile] for discard_option in results_with_same_shanten: # there is no sense to check already chosen tile if discard_option.tile_to_discard == temp_tile.tile_to_discard: continue # we don't need to select tiles almost dead waits if discard_option.tiles_count <= 2: continue # let's check all other tiles with same shanten # maybe we can find tiles that have almost same tiles count number if temp_tile.tiles_count - 2 < discard_option.tiles_count < temp_tile.tiles_count + 2: possible_options.append(discard_option) # let's sort got tiles by value and let's chose less valuable tile to discard possible_options = sorted(possible_options, key=lambda x: x.valuation) selected_tile = possible_options[0] return selected_tile def process_discard_option(self, discard_option, closed_hand, force_discard=False): self.waiting = discard_option.waiting self.player.ai.previous_shanten = discard_option.shanten self.player.in_tempai = self.player.ai.previous_shanten == 0 # when we called meld we don't need "smart" discard if force_discard: return discard_option.find_tile_in_hand(closed_hand) last_draw_34 = self.player.last_draw and self.player.last_draw // 4 or None if self.player.last_draw not in AKA_DORA_LIST and last_draw_34 == discard_option.tile_to_discard: return self.player.last_draw else: return discard_option.find_tile_in_hand(closed_hand) def estimate_hand_value(self, win_tile, tiles=None, call_riichi=False): """ :param win_tile: 34 tile format :param tiles: :param call_riichi: :return: """ win_tile *= 4 # we don't need to think, that our waiting is aka dora if win_tile in AKA_DORA_LIST: win_tile += 1 if not tiles: tiles = self.player.tiles tiles += [win_tile] config = HandConfig(is_riichi=call_riichi, player_wind=self.player.player_wind, round_wind=self.player.table.round_wind, has_aka_dora=self.player.table.has_aka_dora, has_open_tanyao=self.player.table.has_open_tanyao) result = self.finished_hand.estimate_hand_value( tiles, win_tile, self.player.melds, self.player.table.dora_indicators, config) return result def should_call_riichi(self): print(self.player.discards) # empty waiting can be found in some cases if not self.waiting: return False if self.in_defence: return False #If we tenpai fast enough if len(self.player.discards) <= 8: return True if len(self.player.discards) >= 14: return False # we have a good wait, let's riichi if len(self.waiting) > 1: return True waiting = self.waiting[0] tiles = self.player.closed_hand + [waiting * 4] closed_melds = [x for x in self.player.melds if not x.opened] for meld in closed_melds: tiles.extend(meld.tiles[:3]) tiles_34 = TilesConverter.to_34_array(tiles) results = self.hand_divider.divide_hand(tiles_34) result = results[0] count_of_pairs = len([x for x in result if is_pair(x)]) # with chitoitsu we can call a riichi with pair wait if count_of_pairs == 7: return True for hand_set in result: # better to not call a riichi for a pair wait # it can be easily improved if is_pair(hand_set) and waiting in hand_set: return False return True def should_call_kan(self, tile, open_kan): """ Method will decide should we call a kan, or upgrade pon to kan :param tile: 136 tile format :param open_kan: boolean :return: kan type """ # we don't need to add dora for other players if self.player.ai.in_defence: return None if open_kan: # we don't want to start open our hand from called kan if not self.player.is_open_hand: return None # there is no sense to call open kan when we are not in tempai if not self.player.in_tempai: return None # we have a bad wait, rinshan chance is low if len(self.waiting) < 2: return None tile_34 = tile // 4 tiles_34 = TilesConverter.to_34_array(self.player.tiles) closed_hand_34 = TilesConverter.to_34_array(self.player.closed_hand) pon_melds = [x for x in self.player.open_hand_34_tiles if is_pon(x)] # let's check can we upgrade opened pon to the kan if pon_melds: for meld in pon_melds: # tile is equal to our already opened pon, # so let's call chankan! if tile_34 in meld: return Meld.CHANKAN count_of_needed_tiles = 4 # for open kan 3 tiles is enough to call a kan if open_kan: count_of_needed_tiles = 3 # we have 3 tiles in our hand, # so we can try to call closed meld if closed_hand_34[tile_34] == count_of_needed_tiles: if not open_kan: # to correctly count shanten in the hand # we had do subtract drown tile tiles_34[tile_34] -= 1 melds = self.player.open_hand_34_tiles previous_shanten = self.shanten.calculate_shanten(tiles_34, melds) melds += [[tile_34, tile_34, tile_34]] new_shanten = self.shanten.calculate_shanten(tiles_34, melds) # called kan will not ruin our hand if new_shanten <= previous_shanten: return Meld.KAN return None def should_call_win(self, tile, enemy_seat): return True def enemy_called_riichi(self, enemy_seat): """ After enemy riichi we had to check will we fold or not it is affect open hand decisions :return: """ if self.defence.should_go_to_defence_mode(): self.in_defence = True @property def enemy_players(self): """ Return list of players except our bot """ return self.player.table.players[1:]
import numpy as np from mahjong.shanten import Shanten from tqdm import tqdm from itertools import product from utils import * np.random.seed(0) def check_ryuiso(hand): return np.isin(hand, [19, 20, 21, 23, 25, 32]).all() shanten = Shanten() cases = [] for key in tqdm(product(range(5), repeat=9), total=5 ** 9): if sum(key) == 14: hand = [0] * 18 + list(key) + [0] * 7 # 索子14枚 elif sum(key) == 12: hand = [0] * 18 + list(key) + [0, 0, 0, 0, 0, 2, 0] # 索子12枚 + 發2枚 elif sum(key) == 11: hand = [0] * 18 + list(key) + [0, 0, 0, 0, 0, 3, 0] # 索子11枚 + 發3枚 else: continue if shanten.calculate_shanten(hand) == -1: # 和了形の場合 tiles = flatten_tile34(hand) win_tile = np.random.choice(tiles)
def step(self, action): """ Run one step of the environment. When current episode is ended (any player calls, the game ends with a winner or draw),then reset is called to reset the environment :param action: an action of the agent player :return: observation of the game state, reward of the action, whether the episode is ended, whether the move is validated as real players move """ # Episode is finished if self.finish: return self.toReturn(self.state), 0., True, False # Action is not a possible move if action not in self.possibleActions(self.state): return self.toReturn(self.state), 0., False, False s = self.steps[self.current_step] # Step until player's discarding while s[0] > 0 or (s[0] == 0 and s[1] == 0): self.playermove() if self.current_step >= len(self.steps): return self.toReturn(self.state), 0., True, 0 s = self.steps[self.current_step] # Episode is finished by opponents if self.finish: return self.toReturn(self.state), 0., True, 0 # Draw and discard # s[0] = player_number # s[1] = move type 0 draw, 1 discard # s[2] = card number is_playermove = 0 called = 0 next_s = self.steps[self.current_step + 1] if s[0] == 0 and s[1] == 1: # Player discarding, possible move if action == convert( s[2]): # Move validated as the same as real player, good self.playerdiscard(action) is_playermove = True shanten = Shanten().calculate_shanten( self.state['Players'][0] [0]) # Calculate the distance from winning shanten_prv = Shanten().calculate_shanten( self.state['Previous'][0][0] [0]) # Calculate previous distance if next_s[1] > 1 and next_s[0] == 0: #Discarded get called, bad called = 1 return self.toReturn(self.state), self.cal_reward( shanten, shanten_prv, is_playermove, called), False, is_playermove else: # unexpected move, finish self.playerdiscard(action) self.finish = True shanten = Shanten().calculate_shanten( self.state['Players'][0] [0]) # Calculate the distance from winning shanten_prv = Shanten().calculate_shanten( self.state['Previous'][0][0] [0]) # Calculate previous distance return self.toReturn(self.state), self.cal_reward( shanten, shanten_prv, is_playermove, called), True, is_playermove return self.toReturn(self.state), -10., True, 0
from keras.models import Sequential from keras.layers import Dense from keras.optimizers import Adam from keras import backend as K from keras.utils.training_utils import multi_gpu_model from keras.utils import plot_model from keras import losses from collections import deque import tensorflow as tf from copy import deepcopy from datetime import datetime from time import time Nmai_mahjong = 5 shanten = Shanten() pailist = [] tehailist = [] senpai = [] result = 0 syanten = 0 paisID = np.zeros([136, 3]) class Mahjong(): ripaitehai = [] def pais(self): global paisID for num in range(0, 136): pailist.append(num)
class PaifuDataset(torch.utils.data.Dataset): pai_list = [ "1m", "2m", "3m", "4m", "5m", "r5m", "6m", "7m", "8m", "9m", #萬子 "1p", "2p", "3p", "4p", "5p", "r5p", "6p", "7p", "8p", "9p", #筒子 "1s", "2s", "3s", "4s", "5s", "r5s", "6s", "7s", "8s", "9s", #索子 "東", "南", "西", "北", "白", "發", "中" ] def __init__(self, paifu_path_list, n_max=100): self.paifu_path_list = paifu_path_list self.data_size = len(paifu_path_list) * n_max self.shanten_calculator = Shanten() self.n_max = n_max # self.device = 'cuda' if torch.cuda.is_available() else 'cpu' self.device = 'cpu' def __len__(self): return self.data_size def __getitem__(self, idx): paifu = None file_idx = idx // self.n_max inner_idx = idx % self.n_max with open(self.paifu_path_list[file_idx], 'rb') as f: paifu = pickle.load(f) paifu = paifu[inner_idx] x, y = self.paifu_state_to_xy(paifu) # x = paifu['x'].to(self.device) # y = paifu['y'].to(self.device) return x, y # return paifu['x'], paifu['y'] def paifu_state_to_xy(self, state): # device = 'cuda' if torch.cuda.is_available() else 'cpu' device = 'cpu' # action # Discard: 37, Reach:2 , Chow: 2, Pong: 2, Kong: 2 x = {} positions = self.get_positions(state['action']['who']) # hand hand = state['hands'][state['action']['who']] hand = self.pais2ids(hand) x['hand'] = self.normalize_pai_list(hand, device) # discards : direction x['discards'] = self.normalize_discards(state['discards'], positions, device) # Shanten : direction x['shanten'], x['shanten_diff'] = self.calc_shantens(hand, device) # n_hands_list # x['n_hands_list'] = [len(h) for h in state['hands']] x['n_hands_list'] = [len(state['hands'][p]) for p in positions[1:]] # paifu id x['paifu_id'] = state['paifu_id'] if state['action']['type'] == 'discard': y = state['hands'][state['action']['who']].index( state['action']['tile']) y = [] n_players = 4 for i in range(n_players - 1): enemy_hand = state['hands'][(state['action']['who'] + i + 1) % n_players] enemy_hand = self.pais2ids(enemy_hand) sep_token = 39 y += enemy_hand y += [sep_token] # y += [i] * len(enemy_hand) # y += [4] # pad_token = 1 pad_token = -100 y += [pad_token] * ((13 + 1) * 3 + 1 - len(y)) y = torch.tensor(y, dtype=torch.long, device=device) # melds : direction x['melds'] = [ self.normalize_melds([m for m in state['melds'] if m['who'] == i], device) for i in positions ] # action_meld_tiles if state['action']['type'] in ['chow', 'pong', 'kong']: x['action_meld_tiles'] = self.normalize_action_meld_tiles( state['action']['meld_state']['meld_tiles'], device) else: x['action_meld_tiles'] = torch.zeros(4, dtype=torch.long, device=device) # menzen : direction x['menzen'] = torch.tensor([state['menzen'][i] for i in positions], dtype=torch.long, device=device) # reach_state : direction x['reach_state'] = torch.tensor( [state['reach_state'][i] for i in positions], dtype=torch.long, device=device) # n_reach x['n_reach'] = torch.tensor([min([state['n_reach'], 2])], dtype=torch.long, device=device) # reach_ippatsu : direction x['reach_ippatsu'] = torch.tensor( [state['reach_ippatsu'][i] for i in positions], dtype=torch.long, device=device) # doras x['doras'] = self.normalize_doras(state['doras'], device) # dans : direction x['dans'] = torch.tensor([state['dans'][i] for i in positions], dtype=torch.long, device=device) # rates : direction x['rates'] = self.normalize_rates(state['rates'], positions, device=device) # oya : direction x['oya'] = torch.tensor( [(state['oya'] - state['action']['who'] + 4) % 4], dtype=torch.long, device=device) # scores : direction x['scores'] = self.normalize_scores(state['scores'], positions, device) # n_honba x['n_honba'] = torch.tensor([min([state['n_honba'], 3])], dtype=torch.long, device=device) # n_round x['n_round'] = torch.tensor([state['n_round']], dtype=torch.long, device=device) # sanma_or_yonma x['sanma_or_yonma'] = torch.tensor([state['sanma_or_yonma']], dtype=torch.long, device=device) # han_or_ton x['han_or_ton'] = torch.tensor([state['han_or_ton']], dtype=torch.long, device=device) # aka_ari x['aka_ari'] = torch.tensor([state['aka_ari']], dtype=torch.long, device=device) # kui_ari x['kui_ari'] = torch.tensor([state['kui_ari']], dtype=torch.long, device=device) # who x['who'] = torch.tensor([state['action']['who']], dtype=torch.long, device=device) x['sum_discards'] = torch.tensor( [self.calc_sum_discards(state['discards'])], dtype=torch.long, device=device) return x, y def calc_sum_discards(self, discards): # 0から11 : 0 # 12から23 : 1 # 24から35 : 2 # 36から47 : 3 # 48から59 : 4 # 60以上 : 5 sum_discards = sum([len(d) for d in discards]) if sum_discards >= 60: return 5 return sum_discards // (4 * 3) def normalize_score(self, score): # 0 : 4000以下(満貫は2000-4000だから) # 1 : 4001から8000 # … # 11 : 44000から48000 # 12 : 48001以上 score = (score - 1) // 4000 if score < 0: return 0 if score > 12: return 12 return score def normalize_scores(self, scores, positions, device): return torch.tensor( [self.normalize_score(scores[i]) for i in positions], dtype=torch.long, device=device) def normalize_rate(self, rate): rate = int(rate) // 100 - 14 if rate < 0: return 0 if rate > 9: return 9 return rate def normalize_rates(self, rates, positions, device): # id : rate range # 0 : 1499以下 # 1 : 1500から1600 # … # 9 : 2300以上 return torch.tensor([self.normalize_rate(rates[i]) for i in positions], dtype=torch.long, device=device) def normalize_doras(self, doras, device, max_len=5): doras_tensor = torch.zeros(max_len, dtype=torch.long, device=device) l = len(doras) doras = self.pais2ids(doras) doras_tensor[:l] = torch.tensor(doras, dtype=torch.long, device=device) return doras_tensor def normalize_action_meld_tiles(self, tiles, device, max_len=4): tiles_tensor = torch.zeros(max_len, dtype=torch.long, device=device) l = len(tiles) tiles = self.pais2ids(tiles) tiles_tensor[:l] = torch.tensor(tiles, dtype=torch.long, device=device) return tiles_tensor def normalize_melds(self, melds, device, max_len=20): meld_tensor_list = [self.meld2tensor(meld, device) for meld in melds] meld_tensor = torch.zeros((2, max_len), dtype=torch.long, device=device) if len(meld_tensor_list) < 1: return meld_tensor meld_tensor_cat = torch.cat(meld_tensor_list, dim=1) l = meld_tensor_cat.size()[1] if l > max_len: print('Invalid meld data.') print(meld_tensor_cat) meld_tensor[:, :l] = meld_tensor_cat return meld_tensor def meld2tensor(self, meld, device): if meld['meld_type'] == 2: # Add Kan tiles = [self.pais2ids(meld['meld_tiles'])[0]] else: tiles = self.pais2ids(meld['meld_tiles']) l = len(tiles) tile_ids = torch.tensor(tiles, dtype=torch.long, device=device) token_type = torch.full((l, ), fill_value=meld['meld_type'], dtype=torch.long, device=device) return torch.tensor([*tile_ids, *token_type]).reshape([2, -1]) def normalize_discards(self, discards, positions, device): max_n_discards = 25 l = len(discards) res = torch.zeros((4, max_n_discards), dtype=torch.long, device=device) for i, pos in enumerate(positions): res[i, :len(discards[pos])] = torch.tensor(self.pais2ids( discards[pos]), dtype=torch.long, device=device) return res def calc_shantens(self, hand, device): shantens = [] hand_34_count = self.to_34_count(hand) hand_34 = self.to_34_array(hand) base_shanten, _ = self.calc_shanten(hand_34_count) # for tile in hand_34: # hand_34_count[tile] -= 1 # shanten, _ = self.calc_shanten(hand_34_count) # if shanten > base_shanten: # shantens.append(1) # else: # shantens.append(0) # hand_34_count[tile] += 1 l = len(shantens) x = torch.full((14, ), fill_value=-1, dtype=torch.long, device=device) # x[:l] = torch.tensor(shantens, dtype=torch.long, device=device) base_shanten = min([base_shanten, 6]) + 1 base_shanten = torch.tensor([base_shanten], dtype=torch.long, device=device) return base_shanten, x def to_34_array(self, hand): return [self.to_34(t) for t in hand] def to_34_count(self, hand): res = [0] * 34 for tile in hand: res[self.to_34(tile)] += 1 return res def to_34(self, tile): if tile <= 5: return tile - 1 if tile <= 15: return tile - 2 if tile <= 25: return tile - 3 return tile - 4 def calc_shanten(self, tiles_34, open_sets_34=None): shanten_with_chiitoitsu = self.shanten_calculator.calculate_shanten( tiles_34, open_sets_34, chiitoitsu=True) shanten_without_chiitoitsu = self.shanten_calculator.calculate_shanten( tiles_34, open_sets_34, chiitoitsu=False) return min([shanten_with_chiitoitsu, shanten_without_chiitoitsu ]), shanten_with_chiitoitsu <= shanten_without_chiitoitsu def normalize_pai_list(self, pai_list, device, n=14): l = len(pai_list) x = torch.zeros(n, dtype=torch.long, device=device) x[:l] = torch.tensor(pai_list, dtype=torch.long, device=device) return x def pais2ids(self, pai_list): return [self.pai2id(pai, pai_list) for pai in pai_list] def pai2id(self, pai, pai_list=[]): assert (pai in pai_list) return self.pai_list.index(pai) + 1 def get_positions(self, who, n_players=4): return [(i + who) % n_players for i in range(n_players)]
def index(request): # return HttpResponse("Hello, world. You're at the polls index.") if request.method == 'GET': return JsonResponse({}) # JSON文字列 datas = json.loads(request.body) #全37種 syurui = {'1m':0,'2m':1,'3m':2,'4m':3,'5m':4,'6m':5,'7m':6,'8m':7,'9m':8,'1s':9,'2s':10,'3s':11,'4s':12,'5s':13,'6s':14,'7s':15,'8s':16,'9s':17,'1p':18,'2p':19,'3p':20,'4p':21,'5p':22,'6p':23,'7p':24,'8p':25,'9p':26,'a':27,'b':28,'c':29,'d':30,'e':31,'f':32,'g':33, 'a5m':34, 'a5s':35, 'a5p':36} #風4種 field = {'a':0,'b':1,'c':2,'d':3} #萬子 dic_man = {'1m':1, '2m':2, '3m':3, '4m':4, '5m':5, '6m':6, '7m':7, '8m':8, '9m':9, 'a5m':5} #索子 dic_sou = {'1s':1, '2s':2, '3s':3, '4s':4, '5s':5, '6s':6, '7s':7, '8s':8, '9s':9, 'a5s':5} #筒子 dic_pin = {'1p':1, '2p':2, '3p':3, '4p':4, '5p':5, '6p':6, '7p':7, '8p':8, '9p':9, 'a5p':5} #字牌 dic_honors = {'a':1, 'b':2, 'c':3, 'd':4, 'e':5, 'f':6, 'g':7} dora = datas["dora"]["name"] ground = datas["ground"]["name"] own = datas["own"]["name"] #配牌リスト pai_list = [0 for i in range(37)] for i in datas["haipai"]: pai_list[syurui[i["hai"]]] += i["amount"] input_list = pai_list #シャンテン数 man = '' sou = '' pin = '' honors = '' for i in datas["haipai"]: if 'm' in i["hai"]: for j in range(i["amount"]): man += str(dic_man[i["hai"]]) elif 's' in i["hai"]: for j in range(i["amount"]): sou += str(dic_sou[i["hai"]]) elif 'p' in i["hai"]: for j in range(i["amount"]): pin += str(dic_pin[i["hai"]]) else: for j in range(i["amount"]): honors += str(dic_honors[i["hai"]]) shanten = Shanten() tiles = TilesConverter.string_to_34_array(man=man, pin=pin, sou=sou, honors=honors) ss = shanten.calculate_shanten(tiles) ##萬子,索子,筒子,字牌,中張牌,么九牌,ドラのカウント dd = 0 m = 0 s = 0 p = 0 h = 0 tyu = 0 yao = 0 for i in datas["haipai"]: #ドラ枚数 if i["hai"] == dora or i["hai"] == 'a5m' or i["hai"] == 'a5s' or i["hai"] == 'a5p': dd += i["amount"] #萬子枚数 if 'm' in i["hai"]: m += i["amount"] #索子枚数 elif 's' in i["hai"]: s += i["amount"] #筒子枚数 elif 'p' in i["hai"]: p += i["amount"] #字牌枚数 else: h += i["amount"] #么九牌 if '1' in i["hai"] or '9' in i["hai"] or 'a' in i["hai"] or 'b' in i["hai"] or 'c' in i["hai"] or 'd' in i["hai"] or 'e' in i["hai"] or 'f' in i["hai"] or 'g' in i["hai"]: yao += i["amount"] else: tyu += i["amount"] input_list.append(dd) input_list.append(ss) input_list.append(m) input_list.append(s) input_list.append(p) input_list.append(h) input_list.append(tyu) input_list.append(yao) #ドラリスト化 dora_list = [0 for i in range(34)] dora_list[syurui[dora]] += 1 input_list[len(input_list):len(input_list)] = dora_list #場風リスト化 field1_list = [0 for i in range(4)] field1_list[field[ground]] += 1 input_list[len(input_list):len(input_list)] = field1_list #自風リスト化 field2_list = [0 for i in range(4)] field2_list[field[own]] += 1 input_list[len(input_list):len(input_list)] = field2_list input_data = [] input_data.append(input_list) input_data = np.array(input_data) input_data = input_data.astype('float32') ##訓練済みネットワークを用いた推論 #保存したネットワークの読み込み #訓練済みのネットワークと同様のクラスのインスタンス生成 loaded_net = Net(n_hidden=200) #訓練済みネットワークのパラメータを読み込ませる chainer.serializers.load_npz('./genapp/ml_models/m_data.net', loaded_net) # テストデータで予測値を計算 with chainer.using_config('train', False), chainer.using_config('enable_backprop', False): y = loaded_net(input_data) resignation = np.argmax(y[0,:].array) point = random.randint(1,5) result = { 'res':str(resignation), 'point':str(point) } response = JsonResponse( result ) return response