# https://www.youtube.com/watch?v=8xXslshomOs from typing import Dict, Optional from dataclasses import dataclass @dataclass(frozen=True, order=True) class State: gold_a: int gold_b: int silver_a: int silver_b: int gems_a: int gems_b: int equal_priority: bool # 0 means "a", which is the protagonist winner_cache : Dict[State, int] = {} def resolve_auction(state: State, bet_a: int, bet_b: int) -> Optional[State]: if bet_b > state.gems_b: return None assert bet_a <= state.gems_a equal_priority = state.equal_priority if bet_a > bet_b: bet_winner = 0 elif bet_a < bet_b: bet_winner = 1 else: bet_winner = int(equal_priority) equal_priority = 1 - equal_priority doing_golds = state.gold_a + state.gold_b <3 return State( gold_a=state.gold_a + 1 if doing_golds and bet_winner == 0 else state.gold_a, gold_b=state.gold_b + 1 if doing_golds and bet_winner == 1 else state.gold_b, silver_a=state.silver_a + 1 if not doing_golds and bet_winner == 0 else state.silver_a, silver_b=state.silver_b + 1 if not doing_golds and bet_winner == 1 else state.silver_b, gems_a=state.gems_a - bet_a if bet_winner == 0 else state.gems_a, gems_b=state.gems_b - bet_b if bet_winner == 1 else state.gems_b, equal_priority=equal_priority, ) next_print = 1 def get_winner(state: State) -> int: if state.gold_a >= 3: winner_cache[state] = 0; return 0, (state,) if state.gold_b >= 3: winner_cache[state] = 1; return 1, (state,) if state.silver_a >= 3: winner_cache[state] = 0; return 0, (state,) if state.silver_b >= 3: winner_cache[state] = 1; return 1, (state,) winner = winner_cache.get(state) if winner is not None: return winner, (state,) doing_golds = state.gold_a + state.gold_b <3 child_states = [] path = tuple() # minimax winner_a = 1 for bet_a in range(state.gems_a+1): (winner_b, path) = max([ get_winner(child) for child in [resolve_auction(state, bet_a, bet_b) for bet_b in {0, bet_a, bet_a+1}] if child ]) winner_a = min(winner_a, winner_b) if winner_a == 0: path = (state, bet_a) + path # break winner_cache[state] = winner_a global next_print if len(winner_cache) > next_print: print(len(winner_cache)) next_print *= 2 return winner_a, path initial_state = State( gold_a=1, gold_b=0, silver_a=0, silver_b=0, gems_a=75, gems_b=99, equal_priority=0, ) import pickle cache_file = __file__+".winner_cache.pickle" # print(get_winner(initial_state)) # with open(cache_file, 'wb') as f: # pickle.dump(winner_cache, f) with open(cache_file, 'rb') as f: winner_cache = pickle.load(f) # print(winner_cache) def find_children(state): child_states = [] for bet_a in range(state.gems_a+1): responses = [resolve_auction(state, bet_a, bet_b) for bet_b in {0, bet_a, bet_a+1}] if any(winner_cache.get(res) == 1 for res in responses): responses = [res for res in responses if winner_cache.get(res) == 1] child_states.extend(responses) return [state for state in child_states if state is not None] def find_winning_children(state): return [state for state in find_children(state) if winner_cache.get(state) == 0] def print_bets(state0, state1): for bet_a in range(state0.gems_a+1): for bet_b in {0, bet_a, bet_a+1}: res = resolve_auction(state0, bet_a, bet_b) if res == state1: print(f'bet_a={bet_a} bet_b={bet_b}') def get_winning_path(state: State): get_winning_option_fraction = lambda child: len(find_winning_children(child)) / max(1, len(find_children(child))) print() print(len(find_winning_children(state)), "/", len(find_children(state))) print(state) winner = winner_cache.get(state) child_states = find_winning_children(state) if not child_states: print('fin') return choice = min(child_states, key=get_winning_option_fraction) print_bets(state, choice) # for child in child_states: # print(" " + (">" if child is choice else " ") + " " + repr(child)) get_winning_path(choice) get_winning_path(initial_state)