def find_tile_in_hand(self, closed_hand): """ Find and return 136 tile in closed player hand """ if self.player.table.has_aka_dora: tiles_five_of_suits = [4, 13, 22] # special case, to keep aka dora in hand if self.tile_to_discard in tiles_five_of_suits: aka_closed_hand = closed_hand[:] while True: tile = TilesConverter.find_34_tile_in_136_array(self.tile_to_discard, aka_closed_hand) # we have only aka dora in the hand, without simple five if not tile: break # we found aka in the hand, # let's try to search another five tile # to keep aka dora if tile in AKA_DORA_LIST: aka_closed_hand.remove(tile) else: return tile return TilesConverter.find_34_tile_in_136_array(self.tile_to_discard, closed_hand)
def find_tile_in_hand(self, closed_hand): """ Find and return 136 tile in closed player hand """ if self.player.table.has_aka_dora: tiles_five_of_suits = [4, 13, 22] # special case, to keep aka dora in hand if self.tile_to_discard in tiles_five_of_suits: aka_closed_hand = closed_hand[:] while True: tile = TilesConverter.find_34_tile_in_136_array( self.tile_to_discard, aka_closed_hand) # we have only aka dora in the hand, without simple five if not tile: break # we found aka in the hand, # let's try to search another five tile # to keep aka dora if tile in AKA_DORA_LIST: aka_closed_hand.remove(tile) else: return tile return TilesConverter.find_34_tile_in_136_array( self.tile_to_discard, closed_hand)
def find_tile_in_hand(self, closed_hand): """ Find and return 136 tile in closed player hand """ if settings.FIVE_REDS: # special case, to keep aka dora in hand if self.tile_to_discard in [4, 13, 22]: aka_closed_hand = closed_hand[:] while True: tile = TilesConverter.find_34_tile_in_136_array( self.tile_to_discard, aka_closed_hand) # we have only aka dora in the hand if not tile: break # we found aka in the hand, # let's try to search another five tile # to keep aka dora if tile in AKA_DORA_LIST: aka_closed_hand.remove(tile) else: return tile return TilesConverter.find_34_tile_in_136_array( self.tile_to_discard, closed_hand)
def test_find_34_tile_in_136_array(self): result = TilesConverter.find_34_tile_in_136_array(0, [3, 4, 5, 6]) self.assertEqual(result, 3) result = TilesConverter.find_34_tile_in_136_array(33, [3, 4, 134, 135]) self.assertEqual(result, 134) result = TilesConverter.find_34_tile_in_136_array(20, [3, 4, 134, 135]) self.assertEqual(result, None)
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 _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 meldDiscard(self, meld_34, discardtile): tiles_34 = TilesConverter.to_34_array(self.player.tiles + [discardtile]) closed_tiles_34 = TilesConverter.to_34_array(self.player.closed_hand + [discardtile]) open_hand_34 = copy.deepcopy(self.player.open_hand_34_tiles) # remove meld from closed and and add to open hand open_hand_34.append(meld_34) for tile_34 in meld_34: closed_tiles_34[tile_34] -= 1 results = [] for tile in range(0, 34): # Can the tile be discarded from the concealed hand? if not closed_tiles_34[tile]: continue # discard the tile from hand tiles_34[tile] -= 1 # calculate shanten and store shanten = self.shanten.calculate_shanten(tiles_34, open_hand_34) results.append((shanten, tile)) # return tile to hand tiles_34[tile] += 1 (shanten, discard_34) = min(results) discard_136 = TilesConverter.find_34_tile_in_136_array(discard_34, self.player.closed_hand) return shanten, discard_136
def discard_tile(self): h = Hand(TilesConverter.to_one_line_string(self.player.tiles)) tiles = TilesConverter.to_34_array(self.player.tiles) shanten = self.shanten.calculate_shanten(tiles) if shanten == 0: self.player.in_tempai = True if h.test_win(): return Shanten.AGARI_STATE elif self.player.in_tempai: results, st = self.calculate_outs() tile34 = results[0]['discard'] tile_in_hand = TilesConverter.find_34_tile_in_136_array( tile34, self.player.tiles) return tile_in_hand else: hand_data = h.get_data() it = int( self.model.predict_classes(transformCSVHandToCNNMatrix( expandHandToCSV(hand_data)), verbose=0)[0]) t = hand_data[it] tile_in_hand = self.mahjong_tile_to_discard_tile(t) return tile_in_hand
def discard_tile(self): results, shanten = self.calculate_outs() if shanten == 0: self.player.in_tempai = True # we are win! if shanten == Shanten.AGARI_STATE: return Shanten.AGARI_STATE # Disable defence for now # if self.defence.go_to_defence_mode(): # self.player.in_tempai = False # tile_in_hand = self.defence.calculate_safe_tile_against_riichi() # if we wasn't able to find a safe tile, let's discard a random one # if not tile_in_hand: # tile_in_hand = self.player.tiles[random.randrange(len(self.player.tiles) - 1)] # else: # tile34 = results[0]['discard'] # tile_in_hand = TilesConverter.find_34_tile_in_136_array(tile34, self.player.tiles) tile34 = results[0]['discard'] tile_in_hand = TilesConverter.find_34_tile_in_136_array( tile34, self.player.tiles) return tile_in_hand
def _find_meld_tiles(closed_hand, meld_34, discarded_tile): discarded_tile_34 = discarded_tile // 4 meld_34_copy = meld_34[:] closed_hand_copy = closed_hand[:] 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] return tiles
def discard_tile(self, discard_tile): if discard_tile is not None: return discard_tile tiles_34 = TilesConverter.to_34_array(self.player.tiles) closed_tiles_34 = TilesConverter.to_34_array(self.player.closed_hand) results = [] for tile in range(0, 34): # Can the tile be discarded from the concealed hand? if not closed_tiles_34[tile]: continue # discard the tile from hand tiles_34[tile] -= 1 # calculate shanten and store shanten = self.shanten.calculate_shanten( tiles_34, self.player.open_hand_34_tiles) results.append((shanten, tile)) # return tile to hand tiles_34[tile] += 1 (minshanten, discard_34) = min(results) results2 = [] unaccounted = (np.array([4]*34) - closed_tiles_34)\ - TilesConverter.to_34_array(self.table.revealed_tiles) self.shdict = {} for shanten, tile in results: if shanten != minshanten: continue tiles_34[tile] -= 1 h = sum( self.simulate(tiles_34, self.player.open_hand_34_tiles, unaccounted) for _ in range(200)) tiles_34[tile] += 1 results2.append((h, tile)) (h, discard_34) = min(results2) discard_136 = TilesConverter.find_34_tile_in_136_array( discard_34, self.player.closed_hand) if discard_136 is None: logger.debug('Failure') discard_136 = random.randrange(len(self.player.tiles) - 1) discard_136 = self.player.tiles[discard_136] logger.info('Shanten after discard:' + str(shanten)) logger.info('Discard heuristic:' + str(h)) return discard_136
def meldDiscard(self, meld_34, discardtile): tiles_34 = TilesConverter.to_34_array(self.player.tiles + [discardtile]) closed_tiles_34 = TilesConverter.to_34_array(self.player.closed_hand + [discardtile]) open_hand_34 = copy.deepcopy(self.player.open_hand_34_tiles) # remove meld from closed and and add to open hand open_hand_34.append(meld_34) for tile_34 in meld_34: closed_tiles_34[tile_34] -= 1 results = [] for tile in range(0, 34): # Can the tile be discarded from the concealed hand? if not closed_tiles_34[tile]: continue # discard the tile from hand tiles_34[tile] -= 1 # calculate shanten and store shanten = self.shanten.calculate_shanten(tiles_34, open_hand_34) results.append((shanten, tile)) # return tile to hand tiles_34[tile] += 1 (minshanten, discard_34) = min(results) results2 = [] unaccounted = (np.array([4]*34) - closed_tiles_34)\ - TilesConverter.to_34_array(self.table.revealed_tiles) self.shdict = {} for shanten, tile in results: if shanten != minshanten: continue tiles_34[tile] -= 1 h = sum( self.simulate(tiles_34, open_hand_34, unaccounted) for _ in range(200)) tiles_34[tile] += 1 results2.append((h, tile)) (h, discard_34) = min(results2) discard_136 = TilesConverter.find_34_tile_in_136_array( discard_34, self.player.closed_hand) return minshanten, discard_136
def discard_tile(self): gd_player = GreedyPlayer("Me") h = Hand(TilesConverter.to_one_line_string(self.player.tiles)) t = gd_player.select_best_tile(h) tiles = TilesConverter.to_34_array(self.player.tiles) shanten = self.shanten.calculate_shanten(tiles) if shanten == 0: self.player.in_tempai = True types = ['m', 'p', 's', 'z'] if h.test_win(): return Shanten.AGARI_STATE else: tile_in_hand = TilesConverter.find_34_tile_in_136_array( t.get_number() + (t.get_type() >> 4) * 9 - 1, self.player.tiles) return tile_in_hand
def calculate_safe_tile_against_riichi(self): player_tiles = self.table.get_main_player().tiles # tiles that were discarded after riichi or # discarded by player in riichi # for better experience we need to detect the safe tiles for different players safe_tiles = [] for player in self.table.players: safe_tiles += player.safe_tiles if player.in_riichi: safe_tiles += player.discards player_tiles_34 = TilesConverter.to_34_array(player_tiles) safe_tiles_34 = TilesConverter.to_34_array(safe_tiles) safe_tile = None # let's try to find a safe tile in our main player hand for i in range(0, len(safe_tiles_34)): if safe_tiles_34[i] > 0 and player_tiles_34[i] > 0: return TilesConverter.find_34_tile_in_136_array( i, player_tiles) return safe_tile
def discard_tile(self, discard_tile): if discard_tile is not None: return discard_tile tiles_34 = TilesConverter.to_34_array(self.player.tiles) closed_tiles_34 = TilesConverter.to_34_array(self.player.closed_hand) # is_agari = self.agari.is_agari(tiles_34, self.player.open_hand_34_tiles) results = [] for tile in range(0,34): # Can the tile be discarded from the concealed hand? if not closed_tiles_34[tile]: continue # discard the tile from hand tiles_34[tile] -= 1 # calculate shanten and store shanten = self.shanten.calculate_shanten(tiles_34, self.player.open_hand_34_tiles) results.append((shanten, tile)) # return tile to hand tiles_34[tile] += 1 (shanten, discard_34) = min(results) discard_136 = TilesConverter.find_34_tile_in_136_array(discard_34, self.player.closed_hand) if discard_136 is None: logger.debug('Greedy search or tile conversion failed') discard_136 = random.randrange(len(self.player.tiles) - 1) discard_136 = self.player.tiles[discard_136] logger.info('Shanten after discard:' + str(shanten)) return discard_136
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 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 mahjong_tile_to_discard_tile(self, t): return TilesConverter.find_34_tile_in_136_array( t.get_number() + (t.get_type() >> 4) * 9 - 1, self.player.tiles)