Spaces:
Sleeping
Sleeping
| import random | |
| from collections import Counter | |
| from dataclasses import dataclass | |
| card_values = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] | |
| ranks = ['2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A'] | |
| rank_value = dict(zip(ranks, card_values)) | |
| value_rank = dict(zip(card_values, ranks)) | |
| suits = ['c', 'd', 'h', 's'] | |
| hand_values = {'hc': 1, | |
| 'pair': 2, | |
| '2pair': 3, | |
| '3ok': 4, | |
| 'straight': 5, | |
| 'flush': 6, | |
| 'boat': 7, | |
| '4ok': 8, | |
| 'straight_flush': 9 | |
| } | |
| HAND_REGISTRY = [] | |
| ##### CLASSES ##### | |
| class Card: | |
| def __init__(self, card_str): | |
| self.rank = str(card_str[0]) | |
| self.suit = card_str[1] | |
| self.name = self.rank + self.suit | |
| self.value = rank_value[self.rank] | |
| def __str__(self): | |
| return self.name | |
| def pretty_name(self): | |
| rank = '10' if self.rank == 'T' else self.rank | |
| suit_map = { | |
| "c": "♣", | |
| "d": "♦", | |
| "h": "♥", | |
| "s": "♠", | |
| } | |
| return f'{rank}{suit_map[self.suit]}' | |
| def __getitem__(self, item): | |
| if item == 'rank': | |
| return self.rank | |
| elif item == 'suit': | |
| return self.suit | |
| elif item == 'name': | |
| return self.name | |
| elif item == 'value': | |
| return self.value | |
| class Hand: | |
| def __init__(self, type, high_value, low_value = 0, kicker=0): | |
| """Type = name of hand (e.g. Pair) | |
| value = value of the hand (i.e. Straight Flush is the most valuable) | |
| high_value = value. either the high card in straight or flush, the set in full house, the top pair in 2pair, etc | |
| low_value = the lower pair in 2 pair or the pair in a full house | |
| kicker = value of the kicker in the hand. Can be null | |
| """ | |
| kicker_rank = value_rank[kicker] if kicker in card_values else 0 | |
| low_rank = value_rank[low_value] if low_value in card_values else 0 | |
| self.type = type | |
| self.hand_value = hand_values[type] | |
| self.kicker = kicker | |
| self.kicker_rank = kicker_rank | |
| self.high_value = high_value | |
| self.high_rank = value_rank[self.high_value] | |
| self.low_value = low_value | |
| self.low_rank = low_rank | |
| def __str__(self): | |
| return f'{self.type}-{self.high_rank}' | |
| def __getitem__(self, item): | |
| if item in ['hand_value', 'high_value']: | |
| return self.high_value | |
| elif item == 'high_rank': | |
| return self.high_rank | |
| elif item == 'kicker': | |
| return self.kicker | |
| elif item == 'kicker_rank': | |
| return self.kicker_rank | |
| elif item == 'low_rank': | |
| return self.low_rank | |
| elif item == 'low_value': | |
| return self.low_value | |
| elif item == 'type': | |
| return self.type | |
| class Deck(list): | |
| def __init__(self, deck): | |
| self.deck = deck | |
| def __getitem__(self, item): | |
| return self.deck[item] | |
| def __iter__(self): | |
| yield from self.deck | |
| def __len__(self): | |
| return len(self.deck) | |
| def deal_card(self): | |
| """Select a random card from the deck. Return the card and the deck with the card removed""" | |
| i = random.randint(0, len(self)-1) | |
| card = self[i] | |
| self.deck.pop(i) | |
| return card, self | |
| def update_deck(self, card): | |
| """Remove card from deck""" | |
| deck_names = [card.name for card in self.deck] | |
| card_name = card.name if isinstance(card, Card) else card | |
| deck_idx = deck_names.index(card_name) | |
| self.deck.pop(deck_idx) | |
| ##### USEFUL FUNCTIONS ##### | |
| def register(func): | |
| """Add a function to the hand register""" | |
| HAND_REGISTRY.append(func) | |
| return func | |
| def make_card(input_list): | |
| """Input_list is either a list of Card objects or string Objects. If Cards, return the cards. | |
| If string, convert to Card and return""" | |
| if len(input_list) == 0: | |
| return input_list | |
| elif isinstance(input_list[0], Card): | |
| return input_list | |
| else: | |
| card_list = [Card(card) for card in input_list] | |
| return card_list | |
| def generate_deck(): | |
| deck = [] | |
| for rank in ranks: | |
| for suit in suits: | |
| card_str = rank + suit | |
| _card = Card(card_str) | |
| deck.append(_card) | |
| deck = Deck(deck) | |
| return deck | |
| ##### POKER ##### | |
| def find_multiple(hand, board, n=2): | |
| """Is there a pair, three of a kind, four of a kind/?""" | |
| hand = make_card(hand) | |
| board = make_card(board) | |
| multiple = False | |
| multiple_hand = None | |
| total_hand = hand + board | |
| values = [card.value for card in total_hand] | |
| c = Counter(values) | |
| for value in set(values): | |
| if c[value] == 2 and n == 2: | |
| multiple = True | |
| hand_type = 'pair' | |
| high_value = value | |
| low_value = max([value for value in values if value != high_value]) | |
| kicker = max([value for value in values if value not in [high_value, low_value]]) | |
| multiple_hand = Hand(hand_type, high_value, low_value=low_value, kicker=kicker) | |
| return multiple_hand | |
| elif c[value] == 3 and n == 3: | |
| multiple = True | |
| hand_type = '3ok' | |
| high_value = value | |
| low_value = max([foo for foo in values if foo != high_value]) | |
| kicker = max([bar for bar in values if bar not in [high_value, low_value]]) | |
| multiple_hand = Hand(hand_type, high_value, low_value=low_value, kicker=kicker) | |
| return multiple_hand | |
| elif c[value] == 4 and n == 4: | |
| multiple = True | |
| hand_type = '4ok' | |
| high_value = value | |
| low_value = max([value for value in values if value != high_value]) | |
| multiple_hand = Hand(hand_type, high_value, low_value=low_value) | |
| return multiple_hand | |
| return multiple | |
| def evaluate_straight(values): | |
| """Evaluates a list of card values to determine whether there are 5 consecutive values""" | |
| straight = False | |
| count = 0 | |
| straight_hand_values = [] | |
| sranks = [bit for bit in reversed(range(2, 15))] | |
| sranks.append(14) | |
| for rank in sranks: | |
| if rank in values: | |
| count += 1 | |
| straight_hand_values.append(rank) | |
| if count == 5: | |
| straight = True | |
| return straight, straight_hand_values | |
| else: | |
| count = 0 | |
| straight_hand_values = [] | |
| return straight, straight_hand_values | |
| def find_straight_flush(hand, board): | |
| """Find a straight flush in a given hand/board combination""" | |
| hand = make_card(hand) | |
| board = make_card(board) | |
| straight_flush = False | |
| flush = find_flush(hand, board) | |
| if flush: | |
| total_hand = hand + board | |
| total_hand = [card for card in total_hand] | |
| hand_suits = [card.suit for card in total_hand] | |
| c = Counter(hand_suits) | |
| flush_suit = c.most_common(1)[0][0] | |
| flush_hand = [card.value for card in total_hand if card.suit == flush_suit] | |
| straight_flush, straight_hand = evaluate_straight(flush_hand) | |
| if straight_flush: | |
| high_value = max(straight_hand) | |
| hand_type = 'straight_flush' | |
| straight_flush_hand = Hand(hand_type,high_value) | |
| return straight_flush_hand | |
| else: | |
| return straight_flush | |
| else: | |
| return straight_flush | |
| def find_quads(hand, board): | |
| quads = find_multiple(hand, board, n=4) | |
| return quads | |
| def find_full_house(hand, board): | |
| """Is there a full house?""" | |
| hand = make_card(hand) | |
| board = make_card(board) | |
| boat = False | |
| boat_hand = None | |
| total_hand = hand + board | |
| values = [card.value for card in total_hand] | |
| c = Counter(values) | |
| for value in set(values): | |
| if c[value] == 3: | |
| high_value = value | |
| c.pop(value) | |
| for value in set(values): | |
| if c[value] > 1: | |
| low_value = value | |
| kicker = max([value for value in values if value != high_value and value != low_value]) | |
| boat_hand = Hand('boat', high_value, low_value=low_value, kicker=kicker) | |
| boat = True | |
| return boat_hand | |
| return boat | |
| def find_flush(hand, board): | |
| """Does any combination of 5 cards in hand or on board amount to 5 of the same suit""" | |
| hand = make_card(hand) | |
| board = make_card(board) | |
| total_hand = hand + board | |
| total_hand_suits = [card.suit for card in total_hand] | |
| flush = False | |
| c = Counter(total_hand_suits) | |
| for suit in total_hand_suits: | |
| if c[suit] >= 5: | |
| flush = True | |
| if flush: | |
| flush_cards = [card for card in total_hand if card.suit == c.most_common(1)[0][0]] | |
| high_value = max([card.value for card in flush_cards]) | |
| flush_hand = Hand('flush', high_value) | |
| return flush_hand | |
| else: | |
| return flush | |
| def find_straight(hand, board): | |
| """Find a straight in a given hand/board combination""" | |
| hand = make_card(hand) | |
| board = make_card(board) | |
| straight = False | |
| straight_hand = None | |
| high_value = 2 | |
| reqd_hand_size = 5 # required hand size gives us some flexibility at the cost of more lines. could be more efficient if we say 'if len(values)<5' | |
| total_hand = hand + board | |
| values = [*set(card.value for card in total_hand)] | |
| slices = len(values) - reqd_hand_size | |
| if slices < 0: | |
| return straight | |
| else: | |
| straight, straight_hand_values = evaluate_straight(values) | |
| if straight: | |
| hand_type = 'straight' | |
| if 14 in straight_hand_values: # all([5,14]) does not work here so using nested ifs. | |
| if 5 in straight_hand_values: | |
| high_value = 5 | |
| else: | |
| high_value = max(straight_hand_values) | |
| straight_hand = Hand(hand_type, high_value) | |
| return straight_hand | |
| else: | |
| return straight | |
| def find_trips(hand, board): | |
| trips = find_multiple(hand, board, n=3) | |
| return trips | |
| def find_two_pair(hand, board): | |
| """Is there two-pair?""" | |
| hand = make_card(hand) | |
| board = make_card(board) | |
| two_pair = False | |
| # two_pair_hand = None | |
| total_hand = hand + board | |
| values = [card.value for card in total_hand] | |
| c = Counter(values) | |
| for value in values: | |
| if c[value] > 1: | |
| pair1 = Hand('pair', value) | |
| c.pop(value) | |
| for value in values: | |
| if c[value] > 1: | |
| pair2 = Hand('pair', value) | |
| kicker = max([value for value in values if value != pair1.high_value and value != pair2.high_value]) | |
| two_pair_hand = Hand('2pair', max(pair1.high_value, pair2.high_value), low_value=min(pair1.high_value, pair2.high_value), kicker=kicker) | |
| two_pair = True | |
| return two_pair_hand | |
| return two_pair | |
| def find_pair(hand, board): | |
| pair = find_multiple(hand, board, n=2) | |
| return pair | |
| def find_high_card(hand, board): | |
| hand = make_card(hand) | |
| board = make_card(board) | |
| total_hand = hand + board | |
| total_hand_values = [card.value for card in total_hand] | |
| total_hand_values.sort() | |
| high_value = total_hand_values[-1] | |
| low_value = total_hand_values[-2] | |
| kicker = total_hand_values[-3] | |
| high_card_hand = Hand('hc', high_value,low_value=low_value, kicker=kicker) | |
| return high_card_hand | |