Skip to content

Instantly share code, notes, and snippets.

@bennuttall
Last active August 14, 2023 19:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bennuttall/6e6e0b8b6aed4d8e4b4ddc3a41ab98c9 to your computer and use it in GitHub Desktop.
Save bennuttall/6e6e0b8b6aed4d8e4b4ddc3a41ab98c9 to your computer and use it in GitHub Desktop.
Poker hands solution
High Card: 1,302,540 (50.1177%)
Pair: 1,098,240 (42.2569%)
Two Pair: 123,552 (4.7539%)
Three of a Kind: 54,912 (2.1128%)
Straight: 10,200 (0.3925%)
Flush: 5,108 (0.1965%)
Full House: 3,744 (0.1441%)
Four of a Kind: 624 (0.0240%)
Straight Flush: 36 (0.0014%)
Royal Flush: 4 (0.0002%)
from itertools import product
import random
from itertools import combinations
from collections import defaultdict, Counter
from functools import cached_property, total_ordering
VALUES = "23456789TJQKA"
SUITS = "HDSC"
HAND_RANKS = (
"High Card",
"Pair",
"Two Pair",
"Three of a Kind",
"Straight",
"Flush",
"Full House",
"Four of a Kind",
"Straight Flush",
"Royal Flush",
)
class Card:
"""
Represents a single playing card. Initialise with a two-character string in
the form <value><suit> e.g. AS for the ace of spades:
>>> card = Card("AS")
"""
def __init__(self, value_suit):
value, suit = value_suit
if value not in VALUES:
raise ValueError
self.value = value
if suit not in SUITS:
raise ValueError
self.suit = suit
def __repr__(self):
return f"<Card object {self}>"
def __str__(self):
return f"{self.value}{self.suit}"
def __eq__(self, other):
return self.value == other.value
def __gt__(self, other):
return self.value_index > other.value_index
@property
def value_index(self):
return VALUES.index(self.value)
@total_ordering
class PokerHand:
"""
Represents a hand of five cards. Initialise with five 2-character strings
each separated by a space, e.g:
>>> hand = PokerHand.from_str("3D JC 8S 4H 2C")
Evaluate the hand rank by accessing the rank property:
>>> hand.rank
'High Card'
"""
def __init__(self, *cards: list[Card]):
card_set = {repr(card) for card in cards}
duplicate_cards = len(card_set) < len(cards)
if duplicate_cards:
raise ValueError
self.cards = sorted(cards)
@classmethod
def from_str(cls, s: str):
return cls(*[Card(sv) for sv in s.split(' ')])
def __repr__(self):
return f"<PokerHand object ({self})>"
def __str__(self):
card_strings = (str(card) for card in self)
return " ".join(card_strings)
def __iter__(self):
return iter(self.cards)
def __eq__(self, other: "PokerHand"):
if self.rank_index == other.rank_index:
return self.hand_comparison == other.hand_comparison
return False
def __gt__(self, other: "PokerHand"):
if self.rank_index == other.rank_index:
return self.hand_comparison > other.hand_comparison
return self.rank_index > other.rank_index
@cached_property
def rank(self):
suits_set = {card.suit for card in self}
values = [card.value for card in self]
values_set = {card.value for card in self}
value_counts = {values.count(v) for v in values}
values_str = ''.join(values)
flush = len(suits_set) == 1
straight = values_str in VALUES or values_str == '2345A'
royal = values_str == 'TJQKA'
if flush:
if royal:
return "Royal Flush"
if straight:
return "Straight Flush"
return "Flush"
if straight:
return "Straight"
if len(values_set) == 2:
if 4 in value_counts:
return "Four of a Kind"
return "Full House"
if len(values_set) == 3:
if 3 in value_counts:
return "Three of a Kind"
return "Two Pair"
if len(values_set) == 4:
return "Pair"
return "High Card"
@cached_property
def rank_index(self):
return HAND_RANKS.index(self.rank)
@cached_property
def hand_comparison(self):
sort_by_value = lambda v: -VALUES.index(v)
values = Counter([card.value for card in self.cards])
if self.rank == "High Card":
return sorted(self.cards)
if self.rank == "Pair":
pair_card = values.most_common()[0][0]
other_cards = sorted([v[0] for v in values.most_common()[1:]], key=sort_by_value)
return [VALUES.index(pair_card)] + [VALUES.index(v) for v in other_cards]
if self.rank == "Two Pair":
pairs = sorted([v[0] for v in values.most_common()[:2]], key=sort_by_value)
other_card = values.most_common()[-1][0]
return [VALUES.index(p) for p in pairs] + [other_card]
if self.rank == "Three of a Kind":
three = values.most_common()[0][0]
return VALUES.index(three)
if self.rank == "Straight":
return sorted(self.cards)
if self.rank == "Flush":
return sorted(self.cards)
if self.rank == "Full House":
three = values.most_common()[0][0]
return VALUES.index(three)
if self.rank == "Four of a Kind":
four = values.most_common()[0][0]
return VALUES.index(four)
if self.rank == "Straight Flush":
return sorted(self.cards)
if self.rank == "Royal Flush":
return True
def from_two_cards(card_1: Card, card_2: Card):
deck = [
Card(f"{v}{s}")
for v, s in product(VALUES, SUITS)
if f"{v}{s}" not in {str(card_1), str(card_2)}
]
ranks = defaultdict(int)
for card_3, card_4, card_5 in combinations(deck, 3):
hand = PokerHand(card_1, card_2, card_3, card_4, card_5)
ranks[hand.rank] += 1
total = sum(ranks.values())
for rank in HAND_RANKS:
print(f"{rank}: {100 * ranks[rank] / total:.0f}%")
def best_hand(*cards: list[Card]):
hands = sorted(PokerHand(*five_cards) for five_cards in combinations(cards, 5))
def from_seven_cards(card_1: Card, card_2: Card, card_3: Card, card_4: Card, card_5: Card, card_6: Card, card_7: Card):
deck = [
Card(f"{v}{s}")
for v, s in product(VALUES, SUITS)
if f"{v}{s}" not in {str(card_1), str(card_2)}
]
ranks = defaultdict(int)
for card_6, card_7 in combinations(deck, 3):
hand = PokerHand(card_1, card_2, card_3, card_4, card_5)
ranks[hand.rank] += 1
total = sum(ranks.values())
for rank in HAND_RANKS:
print(f"{rank}: {100 * ranks[rank] / total:.0f}%")
deck = [Card(f"{v}{s}") for v, s in product(VALUES, SUITS)]
print("Made deck", len(deck))
all_hands = sorted(PokerHand(*five_cards) for five_cards in combinations(deck, 5))
print("Made all hands", len(all_hands))
hands = sorted(PokerHand(*five_cards) for five_cards in combinations(deck, 5))
print("Sorted hands")
all_ranks = {}
score = 0
last_hand = None
for hand in hands:
if last_hand is None or hand > last_hand:
score += 1
print(score, hand.rank, hand.hand_comparison)
last_hand = hand
all_ranks[str(hand)] = score
print("Scored hands", len(all_ranks))
# random.shuffle(deck)
# card_1 = deck.pop()
# card_2 = deck.pop()
# print(card_1, card_2)
# from_two_cards(card_1, card_2)
from poker import PokerHand
def test_compare_pair():
hand_1 = PokerHand.from_str("AS AH 4D 6S 8H") # Pair of Aces
hand_2 = PokerHand.from_str("KH KD 4S 6H 8S") # Pair of Kings
hand_3 = PokerHand.from_str("JC JD 6S 8H TH") # Pair of Jacks
hand_4 = PokerHand.from_str("7D 7H 2S 3C 4D") # Pair of Sevens / 2 3 4
hand_5 = PokerHand.from_str("7S 7C 3H 4H 5S") # Pair of Sevens / 3 4 5
for hand in [hand_1, hand_2, hand_3, hand_4, hand_5]:
assert hand.rank == "Pair"
assert hand_1 > hand_2
assert hand_2 > hand_3
assert hand_4 < hand_3
assert hand_4 < hand_5
def test_compare_two_pair():
hand_1 = PokerHand.from_str("AS AH 4D 4S 8H") # Two Pair: Aces and Fours
hand_2 = PokerHand.from_str("KH KD 4S 4H 8S") # Two Pair: Kings and Fours
hand_3 = PokerHand.from_str("JC JD 6S 6H TH") # Two Pair: Jacks and Sixes
hand_4 = PokerHand.from_str("7D 7H 3S 3C 5D") # Two Pair: Sevens and Threes
hand_5 = PokerHand.from_str("6S 6C 9H 9C 2S") # Two Pair: Sixes and Nines
hand_6 = PokerHand.from_str("AS AC 2D 2H 9S") # Two Pair: Aces and Twos / 9
hand_7 = PokerHand.from_str("AD AH 2C 2S TS") # Two Pair: Aces and Twos / T
for hand in [hand_1, hand_2, hand_3, hand_4, hand_5, hand_6, hand_7]:
assert hand.rank == "Two Pair"
assert hand_1 > hand_2
assert hand_2 > hand_3
assert hand_3 > hand_4
assert hand_4 < hand_5
assert hand_5 < hand_6
assert hand_6 < hand_7
def test_compare_three_of_a_kind():
hand_1 = PokerHand.from_str("AS AH AD 4S 8H") # Three of a Kind: Aces
hand_2 = PokerHand.from_str("KH KD KS 6H 8S") # Three of a Kind: Kings
hand_3 = PokerHand.from_str("JC JD JH 8S TH") # Three of a Kind: Jacks
hand_4 = PokerHand.from_str("7D 7H 7S 3C 5D") # Three of a Kind: Sevens
hand_5 = PokerHand.from_str("5S 5C 5H 9H 2S") # Three of a Kind: Fives
hand_6 = PokerHand.from_str("9S 9C 9D 2H 3D") # Three of a Kind: Nines / 2 3
for hand in [hand_1, hand_2, hand_3, hand_4, hand_5, hand_6]:
assert hand.rank == "Three of a Kind"
assert hand_1 > hand_2
assert hand_2 > hand_3
assert hand_3 > hand_4
assert hand_4 > hand_5
assert hand_5 < hand_6
def test_compare_straight():
hand_1 = PokerHand.from_str("2S 3H 4D 5S 6H") # Straight: 2 to 6
hand_2 = PokerHand.from_str("KH QD JS TH 9S") # Straight: 9 to K
hand_3 = PokerHand.from_str("7C 8D 9H TS JH") # Straight: 7 to Jack
hand_4 = PokerHand.from_str("6D 7H 8S 9H TC") # Straight: 6 to 10
hand_5 = PokerHand.from_str("3S 4C 5H 6H 7S") # Straight: 3 to 7
hand_6 = PokerHand.from_str("AS KS QD JD TH") # Straight: 10 to Ace
for hand in [hand_1, hand_2, hand_3, hand_4, hand_5, hand_6]:
assert hand.rank == "Straight"
assert hand_1 < hand_2
assert hand_2 > hand_3
assert hand_3 > hand_4
assert hand_4 > hand_5
assert hand_5 < hand_6
def test_compare_flush():
hand_1 = PokerHand.from_str("2S 4S 6S 8S TS") # Flush: 2 to T
hand_2 = PokerHand.from_str("KH QH JH 9H 7H") # Flush: 7 to K
hand_3 = PokerHand.from_str("3D 5D 7D 9D JD") # Flush: 3 to J
hand_4 = PokerHand.from_str("6C 8C TC JC AC") # Flush: 6 to A
hand_5 = PokerHand.from_str("3H 5H 7H 9H KH") # Flush: 3 to K
hand_6 = PokerHand.from_str("7D 8D 9D JD QD") # Flush: 7 to Q
for hand in [hand_1, hand_2, hand_3, hand_4, hand_5, hand_6]:
assert hand.rank == "Flush"
assert hand_1 < hand_2
assert hand_2 > hand_3
assert hand_3 < hand_4
assert hand_4 > hand_5
assert hand_5 < hand_6
def test_compare_full_house():
hand_1 = PokerHand.from_str("AS AH AD 8S 8H") # Full House: Aces over Eights
hand_2 = PokerHand.from_str("KH KD KS 6H 6S") # Full House: Kings over Sixes
hand_3 = PokerHand.from_str("JC JD JH 8S 8H") # Full House: Jacks over Eights
hand_4 = PokerHand.from_str("7D 7H 7S 3C 3H") # Full House: Sevens over Threes
hand_5 = PokerHand.from_str("6S 6C 6H 9C 9S") # Full House: Sixes over Nines
hand_6 = PokerHand.from_str("TS TH TC AH AD") # Full House: Tens over Aces
for hand in [hand_1, hand_2, hand_3, hand_4, hand_5, hand_6]:
assert hand.rank == "Full House"
assert hand_1 > hand_2
assert hand_2 > hand_3
assert hand_3 > hand_4
assert hand_4 > hand_5
assert hand_5 < hand_6
def test_compare_four_of_a_kind():
hand_1 = PokerHand.from_str("AS AH AD AC 8H") # Four of a Kind: Aces
hand_2 = PokerHand.from_str("KH KD KS KC 8S") # Four of a Kind: Kings
hand_3 = PokerHand.from_str("JC JD JH JS 8H") # Four of a Kind: Jacks
hand_4 = PokerHand.from_str("7D 7H 7S 7C 5H") # Four of a Kind: Sevens
hand_5 = PokerHand.from_str("6S 6C 6H 6D 2S") # Four of a Kind: Sixes
hand_6 = PokerHand.from_str("TS TH TC TD 9H") # Four of a Kind: Tens
for hand in [hand_1, hand_2, hand_3, hand_4, hand_5, hand_6]:
assert hand.rank == "Four of a Kind"
assert hand_1 > hand_2
assert hand_2 > hand_3
assert hand_3 > hand_4
assert hand_4 > hand_5
assert hand_5 < hand_6
def test_compare_straight_flush():
hand_1 = PokerHand.from_str("2S 3S 4S 5S 6S") # Straight Flush: 2 to 6, Spades
hand_2 = PokerHand.from_str("KH QH JH TH 9H") # Straight Flush: 9 to K, Hearts
hand_3 = PokerHand.from_str("AC 2C 3C 4C 5C") # Straight Flush: A to 5, Clubs
hand_4 = PokerHand.from_str("6D 7D 8D 9D TD") # Straight Flush: 6 to 10, Diamonds
hand_5 = PokerHand.from_str("3S 4S 5S 6S 7S") # Straight Flush: 3 to 7, Spades
hand_6 = PokerHand.from_str("KS QS JS TS 9S") # Straight Flush: 10 to A, Spades
for hand in [hand_1, hand_2, hand_3, hand_4, hand_5, hand_6]:
assert hand.rank == "Straight Flush"
assert hand_1 < hand_2
assert hand_2 > hand_3
assert hand_3 < hand_4
assert hand_4 > hand_5
assert hand_5 < hand_6
def test_compare_royal_flush():
hand_1 = PokerHand.from_str("TS JS QS KS AS") # Royal Flush: Spades
hand_2 = PokerHand.from_str("AH KH QH JH TH") # Royal Flush: Hearts
hand_3 = PokerHand.from_str("TC JC QC KC AC") # Royal Flush: Clubs
hand_4 = PokerHand.from_str("TD JD QD KD AD") # Royal Flush: Diamonds
for hand in [hand_1, hand_2, hand_3, hand_4]:
assert hand.rank == "Royal Flush"
assert hand_1 == hand_2
assert hand_2 == hand_3
assert hand_3 == hand_4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment