def _win_selections_in_tiles(self, hand: TileSet, ignore_4counts, current_state: WinPattern, borrow_limits: TileSet, searching_group: List[List[Tile]], borrowed_stage, waiting_step_pruning): # if borrowed_tile_count(hand) > self.max_used_tiles: # return if ignore_4counts and not all(cnt >= borrow_limits[tile] for tile, cnt in hand.items()): return if current_state.has_win(): borrowed = TileSet(-hand) borrowed_count = sum(borrowed.values()) self.max_used_tiles = borrowed_count - waiting_step_pruning logging.debug("found borrowing %s", borrowed) yield borrowed_count, [], borrowed return # if current_state.need_count() > sum((+hand).values()) + self.max_used_tiles: # return hand = hand.copy() basic_hand_borrowed = borrowed_tile_count(hand) for can_borrowed in range(borrowed_stage, current_state.max_unit_length() + 1): min_used_tiles = current_state.need_units() * can_borrowed searching_round_old = searching_group[can_borrowed] searching_round = searching_round_old.copy() temp_searching_group = searching_group.copy() temp_searching_group[can_borrowed] = searching_round hand_round = hand for tile in searching_round.copy(): for unit, state in current_state.next_states(tile): if basic_hand_borrowed + min_used_tiles <= self.max_used_tiles: logging.debug("test %s in %s at borrow stage %d", unit, hand, can_borrowed) hand_temp = hand_round.copy() hand_temp.subtract(unit) borrowed_new = borrowed_tile_count(hand_temp) if borrowed_new - basic_hand_borrowed == can_borrowed: logging.debug("search %s in %s borrowed %s", unit, hand, TileSet(-hand)) yield from ((cnt, [unit] + patterns, borrowed) for cnt, patterns, borrowed in self._win_selections_in_tiles(hand_temp, ignore_4counts, state, borrow_limits, temp_searching_group, can_borrowed, waiting_step_pruning)) else: logging.debug("%s plan to borrowing out of range %d", hand, self.max_used_tiles - basic_hand_borrowed) return searching_round.remove(tile)
def reasoning_discards(hand, invisible_player_perspective, tile, win): hand_temp = hand - TileSet([tile]) reasoning = HeuristicPatternMatchWaiting(win) waiting_step, useful_tiles = reasoning.waiting_and_useful_tiles(hand_temp) useful_tiles_count = sum( len(set(tile_to_tenhou_range(tile)) & invisible_player_perspective) for tile in useful_tiles) return ReasoningItem(tile, waiting_step, useful_tiles, useful_tiles_count)
def before_waiting_step(self, hand: TileSet, ignore_4counts=True) -> int: if self.win_pattern.match(hand): return -1 for need_tiles in count(1): for tiles in product(TileDistribution.ALL_TILES, repeat=need_tiles): added_hand = hand + TileSet(tiles) if (ignore_4counts and all(v <= 4 for v in added_hand.values())) and self.win_pattern.match(added_hand): return need_tiles - 1
def batch_waiting_and_useful_tiles(self, hand, ignore_4counts=True): waiting_step = self.before_waiting_step(hand) result_iter = self.count_hand_borrow_combinition_iter(hand, ignore_4counts, waiting_step) all_hand_cnt = defaultdict(set) for _, sub_win, borrows in result_iter: hand_win = reduce(operator.add, sub_win, TileSet()) can_drops = hand - hand_win for can_pick_tile in can_drops: all_hand_cnt[can_pick_tile].update(borrows) for tile, hand_useful in all_hand_cnt.items(): yield tile, waiting_step, hand_useful
def _win_selections_in_tiles(self, hand: TileSet, max_used_tiles_count, ignore_4counts, current_state: WinPattern, borrow_limits: TileSet, searching_start: List[Tile]): if sum((-hand).values()) > max_used_tiles_count: return if ignore_4counts and not all(cnt >= borrow_limits[tile] for tile, cnt in hand.items()): return if current_state.has_win(): yield [], TileSet(-hand) if current_state.need_count() > sum((+hand).values()) + max_used_tiles_count: return hand = hand.copy() for tile in searching_start.copy(): for unit, state in current_state.next_states(tile): hand_temp = hand.copy() hand_temp.subtract(unit) yield from (([unit] + tail, remains) for tail, remains in self._win_selections_in_tiles(hand_temp, max_used_tiles_count, ignore_4counts, state, borrow_limits, searching_start.copy())) if hand[tile] >= 0: del hand[tile] searching_start.remove(tile)
def reasoning_discards(hand, invisible_player_perspective, tile, win): hand_temp = hand - TileSet([tile]) reasoning = HeuristicPatternMatchWaiting(win) waiting_step, useful_tiles = reasoning.waiting_and_useful_tiles(hand_temp) return convert_to_reasoning(invisible_player_perspective, tile, useful_tiles, waiting_step)
def discard_reasoning(discard_event, hand_state, invisible_tiles_state, player, player_meld_state): invisible_player_perspective = invisible_tiles_state.value - set(hand_state.value) meld_count = sum(1 for meld in player_meld_state.value if not isinstance(meld, Kita)) hand = TileSet(tile_from_tenhou(index) for index in hand_state.value) logger.info("reasoning {}", hand) win_types = [NormalTypeWin(melds=4 - meld_count)] reasoning_names = ["normal_reasonings", "seven_pair_reasonings"] if meld_count == 0: win_types.append(UniquePairs()) win_reasonings = all_win_type_reasoning(hand, invisible_player_perspective, win_types) merged_win_reasonings = [ reasoning_merge(list(reasoning_same_discard), invisible_player_perspective) for reasoning_same_discard in zip(*win_reasonings) ] merged_win_reasonings.sort(key=reasoning_key) _, expected_reasonings = next(groupby(merged_win_reasonings, key=reasoning_key)) expected_reasonings = list(expected_reasonings) your_choice_tile = tile_from_tenhou(player.discard_tile_index(discard_event)) your_choice_reasoning = reasoning_merge( [reasoning_discards(hand, invisible_player_perspective, your_choice_tile, win) for win in win_types ], invisible_player_perspective ) # find_in_list(merged_win_reasonings, # key=lambda x: x.discard_tile == your_choice_tile) for win_reasoning in win_reasonings: win_reasoning.sort(key=reasoning_key) # TODO add wrong rate for reason display. expect_shanten, expect_counts = reasoning_key(expected_reasonings[0]) your_shanten, your_counts = reasoning_key(your_choice_reasoning) expect_counts = -expect_counts your_counts = -your_counts shanten_sequence_count = len(invisible_player_perspective) ** (your_shanten - expect_shanten) base = expect_counts * shanten_sequence_count # print("base=", base, "expect_counts=", expect_counts, "your_counts=", your_counts) correct_rate = your_counts / base wrong_rate = 1 - correct_rate # print("correct=", correct_rate, "wrong=", wrong_rate) your_choice_reasoning.norm() for item in expected_reasonings: item.norm() for item in merged_win_reasonings: item.norm() hand_str = join_tiles(to_plain_tile(x) for x in hand.tiles()) meld_strs = [ join_tiles(to_plain_tile(tile_from_tenhou(x)) for x in list(meld.self_tiles) + list(meld.borrowed_tiles)) for meld in player_meld_state.value ] round_reasoning = RoundReasoning( hand_str, meld_strs, your_choice_reasoning, expected_reasonings, merged_win_reasonings, wrong_rate, False ) logger.info("reasoned {}", hand) for name, win_reason in zip(reasoning_names, win_reasonings): for item in win_reason: item.norm() setattr(round_reasoning, name, win_reason) return round_reasoning
def test_useful_tiles(hand, useful): assert TileSet(BruteForceWaiting(NormalTypeWin()).useful_tiles(tile_set_from_string(hand))) \ == tile_set_from_string(useful)
def main(): input_hand = tile_set_from_string(input('Input hand:')) while len(input_hand) != 14: print("Input hand should be 14 tiles, e.g. 123m067p9s1234567z. " + "You have input %s legal tiles" % len(input_hand)) input_hand = tile_set_from_string(input('Input hand:')) remain_tiles = TileSet(TileDistribution.ALL_TILES * 4) - input_hand remain_tile_distribution = StaticWall(remain_tiles) possible_discard = {tile: input_hand - TileSet([tile]) for tile in input_hand} remain_draw_count = int(input('Remain times for drawing tiles:')) win_counter = Counter() condition_win_counter = defaultdict(Counter) try_count = input('Experiment times (default 1000):') try_count = 1000 if try_count.strip() == '' else int(try_count) normal_win = NormalTypeWin() seven_pair = UniquePairs() win_patterns = [normal_win, seven_pair] avg_win_counter = Counter() total_win_count = 0 elapsed_time = 0 start = perf_counter() task_start_time = start for i in range(try_count): sample_wall = TileSet(remain_tile_distribution.sample(remain_draw_count)) possible_win_set = set() for tile, hand in possible_discard.items(): total_hand = hand + sample_wall for win_pattern in win_patterns: if win_pattern.match(total_hand): win_counter.update([tile]) total_win_count += 1 possible_win_set.add(tile) break for tile in possible_win_set: avg_win_counter[tile] += 1 / len(possible_win_set) for no_win_tile in set(possible_discard.keys()) - possible_win_set: for tile in possible_win_set: condition_win_counter[no_win_tile][tile] += 1 / len(possible_win_set) interval = perf_counter() - start if interval > 1: start = perf_counter() elapsed_time = start - task_start_time done = i + 1 eta = elapsed_time / done * try_count - elapsed_time print("Experiments %d/%d with %.1fs, ETA %.1fs" % (done, try_count, elapsed_time, eta)) solution_count = len(possible_discard) print("Done in %.1fs!" % (perf_counter() - task_start_time)) solution_tiles = list(possible_discard.keys()) min_rate = 1 / try_count condition_matrix = np.full((solution_count, solution_count), min_rate, dtype=np.double) for condition_index, condition_tile in enumerate(solution_tiles): self_transfer_rate = avg_win_counter[condition_tile] / total_win_count # self_transfer_rate = 0 for transfer_index, transfer_tile in enumerate(solution_tiles): transfer_count = condition_win_counter[condition_tile][transfer_tile] condition_count = win_counter[transfer_tile] if transfer_count > 0 and condition_count > 0: condition_matrix[transfer_index][condition_index] = transfer_count condition_matrix[:, condition_index] *= ((1 - self_transfer_rate) / sum(condition_matrix[:, condition_index])) condition_matrix[condition_index][condition_index] = self_transfer_rate # FIXME: add regularization of Markov possibilities matrix (column sum equals 1). # FIXME: define proper self retain possibilities. condition_matrix = np.matrix(condition_matrix) unbiased_distribution = np.full((solution_count,), 1 / solution_count) rough_pick = condition_matrix * (unbiased_distribution.reshape(1, -1).transpose()) rough_pick = np.array(rough_pick).reshape(-1) eigenvalues, eigenvectors = LA.eig(condition_matrix) nearest_one_index = abs(eigenvalues - 1).argmin() infinite_pick = eigenvectors[:, nearest_one_index] infinite_pick = np.array(infinite_pick).reshape(-1) infinite_pick /= sum(infinite_pick) pick_rank_map = {tile: (rough, infinite) for tile, rough, infinite in zip(solution_tiles, rough_pick, infinite_pick)} # results = sorted(win_counter.items(), key=lambda tup: tup[1], reverse=True) results = sorted(pick_rank_map.items(), key=lambda tup: tup[1][1], reverse=True) print("for input hand <%s>, remain %d draws:" % (input_hand, remain_draw_count)) for tile, (rough, infinite) in results: # rough, infinite = pick_rank_map[tile] win_count = win_counter[tile] print("discard %s: %.2f%% win rate (%d/%d), %.2f%% rough pick rate, %.2f%% accurate pick rate" % (tile, win_count * 100 / try_count, win_count, try_count , rough * 100, infinite * 100)) os.system('pause')
def tile_set_from_tenhou(tenhou_ints: Iterable[int]): return TileSet(tile_from_tenhou(x) for x in tenhou_ints)
def test_pattern_useful_tiles(hand, useful): assert TileSet(PatternMatchWaiting(NormalTypeWin()).useful_tiles(tile_set_from_string(hand))) \ == tile_set_from_string(useful)
def useful_tiles(self, hand: TileSet, ignore_4counts=True): self_waiting = self.before_waiting_step(hand) return set(tile for tile in TileDistribution.ALL_TILES if (ignore_4counts and hand[tile] < 4) and self.before_waiting_step(hand + TileSet([tile])) < self_waiting)
def test_batch_convert(hand_rec): hand, record_map = hand_rec for tile, step, useful in HeuristicPatternMatchWaiting(NormalTypeWin()).batch_waiting_and_useful_tiles(hand): assert record_map[tile] == TileSet(useful)