149 lines
4.3 KiB
Python
149 lines
4.3 KiB
Python
# 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)
|