예제 #1
0
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)
예제 #2
0
 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
예제 #3
0
    def before_waiting_step(self, hand: TileSet, ignore_4counts=True):
        self.max_used_tiles = sum(hand.values())

        borrows = self.init_search_by_hand(hand)

        result_iter = self._win_selections_in_tiles(hand, ignore_4counts, self.win_pattern, borrowed_limit(hand),
                                                    borrows, 0, 1)
        return min(v for v, _, _ in result_iter) - 1
예제 #4
0
 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
예제 #5
0
 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)
예제 #6
0
    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)
    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
예제 #9
0
def test_useful_tiles(hand, useful):
    assert TileSet(BruteForceWaiting(NormalTypeWin()).useful_tiles(tile_set_from_string(hand))) \
           == tile_set_from_string(useful)
예제 #10
0
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')
예제 #11
0
def tile_set_from_tenhou(tenhou_ints: Iterable[int]):
    return TileSet(tile_from_tenhou(x) for x in tenhou_ints)
예제 #12
0
def test_pattern_useful_tiles(hand, useful):
    assert TileSet(PatternMatchWaiting(NormalTypeWin()).useful_tiles(tile_set_from_string(hand))) \
           == tile_set_from_string(useful)
예제 #13
0
 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)
예제 #14
0
def need_to_borrow(hand: TileSet, unit: TileSet):
    hand = hand.copy()
    hand.subtract(unit)
    return borrowed_tile_count(hand)
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)