From e668a84e81e3fa6854c99bf198324ad62ac5c79e Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Wed, 10 Apr 2019 16:37:23 -0700 Subject: [PATCH] Add O-Hell python module --- dotfiles/lib/python/ohell.py | 261 +++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 dotfiles/lib/python/ohell.py diff --git a/dotfiles/lib/python/ohell.py b/dotfiles/lib/python/ohell.py new file mode 100644 index 00000000..08fb3043 --- /dev/null +++ b/dotfiles/lib/python/ohell.py @@ -0,0 +1,261 @@ +import collections +import random +import pprint + +do_print = False + +def maybe_print(*args): + if do_print: + print(*args) + + +def falling_factorial(n, k): + current = n + product = 1 + for _ in range(k): + product *= current + current -= 1 + + +def one_hand_with_lead_odds(card, num_players, is_trump=False, revealed_card=6, revealed_in_suit=False): + num_stronger_cards = 14 - card + if is_trump: + if card < revealed_card: + num_stronger_cards -= 1 + elif is_trump is None: + if revealed_in_suit: + if card < revealed_card: + num_stronger_cards -= 1 + else: + num_stronger_cards += 12 + + odds_no_stronger_card_out = 1.0 + + num_cards_remaining = 50 + num_weaker_cards_remaining = num_cards_remaining - num_stronger_cards + + for i in range(num_players-1): + odds_no_stronger_card_out *= num_weaker_cards_remaining/num_cards_remaining + num_weaker_cards_remaining -= 1 + num_cards_remaining -= 1 + + return odds_no_stronger_card_out + + +def expected_value_of_one_bid(*args, **kwargs): + win_prob = one_hand_with_lead_odds(*args, **kwargs) + one_ev = win_prob + ((1 - win_prob) * -1) + zero_ev = -win_prob + + return (one_ev - zero_ev, win_prob, one_ev, zero_ev) + + +def odds_for_number_of_players(num_players, is_trump=False): + for card in range(2, 15): + print(card) + print(expected_value_of_one_bid(card, num_players, is_trump=is_trump)) + + +def random_permutation_of_size(n): + remaining = list(range(n)) + for i in range(n-1, -1, -1): + yield remaining.pop(random.randint(0, i)) + + +def random_permutation(the_list): + for index in random_permutation_of_size(len(the_list)): + yield the_list[index] + + +deck_of_cards = [ + (number, suit) + for number in range(2, 15) + for suit in range(4) +] + + +def compare_cards(card1, card2, trump_suit, led_suit): + card1value, card1suit = card1 + card2value, card2suit = card2 + if card1suit == card2suit and card1suit in (trump_suit, led_suit): + return card1value - card2value + + if card1suit == trump_suit: + return 1 + + if card2suit == trump_suit: + return -1 + + if card1suit == led_suit: + return 1 + + if card2suit == led_suit: + return -1 + + return 0 + + +lost_hands = collections.defaultdict(int) +won_hands = collections.defaultdict(int) + + +performance_by_player_card = collections.defaultdict( + lambda: collections.defaultdict(lambda: collections.defaultdict(lambda: collections.defaultdict(int))) +) + + +def play_hand(player_order, player_to_strategy, player_scores, score_first_only=True): + bids = {} + shuffled_deck = list(random_permutation(deck_of_cards)) + trump_card = shuffled_deck[len(player_order)] + trump_suit = None if trump_card[0] in [11, 12, 13] else trump_card[1] + led_suit = shuffled_deck[0][1] + highest_card = (0, led_suit) + winning_player = player_order[0] + maybe_print("Trump is {}, {} leads".format(trump_suit, player_order[0])) + if (trump_suit is not None and shuffled_deck[0][1] != trump_suit and + shuffled_deck[0][0] <= 8 and shuffled_deck[0][0] > 5): + # import ipdb; ipdb.set_trace() + pass + for player, card in zip(player_order, shuffled_deck): + if compare_cards(card, highest_card, trump_suit, led_suit) > 0: + highest_card = card + winning_player = player + bids[player] = player_to_strategy[player](card, trump_card, bids, len(player_order)) + maybe_print("{} got {} and bid {}".format(player, card, bids[player])) + maybe_print("{} won the hand with {}".format(winning_player, highest_card)) + + for player in player_order: + bid = bids[player] + score = 0 + if winning_player == player: + if bid == 1: + score = 1 + won_hands[player] += 1 + else: + score = -1 + lost_hands[player] += 1 + else: + if bid != 0: + score = -1 + lost_hands[player] += 1 + + player_scores[player] += score + + trump_of_card = None if trump_suit is None else shuffled_deck[0][1] == trump_suit + performance_dict = performance_by_player_card[player][trump_of_card][shuffled_deck[0][0]] + performance_dict[score] += 1 + performance_dict["total"] += score + + if score_first_only: + break + + +def optimal_strategy(card, trump_card, bids, number_of_players): + trump_suit = None if trump_card[0] in [11, 12, 13] else trump_card[1] + card_is_trump = card[1] == trump_card[1] + if trump_suit == None: + card_is_trump = None + + if len(bids) == 0: + odds = expected_value_of_one_bid( + card[0], number_of_players, is_trump=card_is_trump, + revealed_card=trump_card[0], revealed_in_suit=card[1] == trump_card[1] + ) + return 1 if odds[0] > 0 else 0 + + if card_is_trump: + return handle_non_first_bid(card, trump_card, bids, number_of_players) + else: + return 0 + + +def handle_non_first_bid(card, trump_card, bids, number_of_players): + count_greater = 14 - card[0] + count_smaller = card[0] - 2 + + if trump_card[0] < card[0]: + count_smaller -= 1 + else: + count_greater -= 1 + + odds_of_random_trump_smaller = float(count_smaller) / (count_smaller + count_greater) + bid_sum = sum(bids.values()) + + if bid_sum == 0: + return 1 + elif bid_sum == 1 and odds_of_random_trump_smaller > (float(1)/3): + return 1 + elif bid_sum == 2 and count_greater <= 3: + return 1 + elif count_greater <= 1: + return 1 + + return 0 + + +def mom_strat(card, trump_card, bids, number_of_players): + trump_suit = None if trump_card[0] in [11, 12, 13] else trump_card[1] + card_is_trump = card[1] == trump_card[1] + if trump_suit == None: + card_is_trump = None + + if len(bids) > 0: + if card_is_trump: + return handle_non_first_bid(card, trump_card, bids, number_of_players) + return 0 + + if trump_suit == None: + if card[0] >= 5: + return 1 + + if card_is_trump: + return 1 + + if card[0] >= 13: + return 1 + + return 0 + + +player_strategies = { + "optimal": optimal_strategy, + "mom1": mom_strat, + "mom2": mom_strat, + "mom3": mom_strat, +} + + +def simulate_hands(strats, number_of_hands): + player_order = list(player_strategies.keys()) + scores = collections.defaultdict(int) + for _ in range(number_of_hands): + play_hand(player_order, strats, scores) + player_order = player_order[-1:] + player_order[:-1] + + print(scores) + return scores + + +odds_for_number_of_players(4, is_trump=False) +print("score") +scores = simulate_hands(player_strategies, 100000) +highest_score = -10000000 +winner = None +for player, score in scores.items(): + if score > highest_score: + highest_score = score + winner = player +print("{} won with {}".format(winner, highest_score)) +print("won") +print(won_hands) +print("lost") +print(lost_hands) + +# for player, by_trump in performance_by_player_card.items(): +# print(player) +# for trump_type, card_to_score in by_trump.items(): +# print("{}: {}".format(trump_type, sum(card_to_score.values()))) + +pprint.pprint(performance_by_player_card["optimal"][None]) +pprint.pprint(performance_by_player_card["mom1"][None])