aboutsummaryrefslogtreecommitdiff
path: root/poker/rank/hands.py
diff options
context:
space:
mode:
Diffstat (limited to 'poker/rank/hands.py')
-rw-r--r--poker/rank/hands.py110
1 files changed, 110 insertions, 0 deletions
diff --git a/poker/rank/hands.py b/poker/rank/hands.py
new file mode 100644
index 0000000..8764ce9
--- /dev/null
+++ b/poker/rank/hands.py
@@ -0,0 +1,110 @@
+from poker.constants import Rank, Value
+from poker.models import Card, Hand
+
+
+def _is_flush(cards: list[Card]) -> bool:
+ """Calculate whether hand is a flush."""
+ return len(set(card.suit for card in cards)) == 1
+
+
+def _is_straight(cards: list[Card]) -> bool:
+ """Calculate whether hand is a straight."""
+ card_values = sorted([card.value for card in cards])
+
+ # If card values are not unique it is not a straight
+ if len(set(card_values)) != len(card_values):
+ return False
+
+ # Check for a ace low straight
+ if card_values == [Value.TWO, Value.THREE, Value.FOUR, Value.FIVE, Value.ACE]:
+ return True
+
+ # If all card differences are 1 it is a straight
+ diffs = [second - first for first, second in zip(card_values[:-1], card_values[1:])]
+ if set(diffs) == {1}:
+ return True
+
+ return False
+
+
+def is_royal_flush(hand: Hand) -> bool:
+ """Calculate whether hand a royal flush."""
+ if not _is_flush(hand.cards):
+ return False
+
+ values = [card.value for card in hand.cards]
+ return all(
+ value in values
+ for value in [Value.ACE, Value.KING, Value.QUEEN, Value.JACK, Value.TEN]
+ )
+
+
+def is_straight_flush(hand: Hand) -> bool:
+ """Calculate whether hand a straight flush."""
+ return _is_straight(hand.cards) and _is_flush(hand.cards)
+
+
+def is_four_of_a_kind(hand: Hand) -> bool:
+ """Calculate whether hand has four of a kind."""
+ return hand.value_counts[0][1] == 4
+
+
+def is_full_house(hand: Hand) -> bool:
+ """Calculate whether hand is a full house."""
+ return hand.value_counts[0][1] == 3 and hand.value_counts[1][1] == 2
+
+
+def is_flush(hand: Hand) -> bool:
+ """Calculate whether hand is a flush."""
+ return _is_flush(hand.cards) and not _is_straight(hand.cards)
+
+
+def is_straight(hand: Hand) -> bool:
+ """Calculate whether hand is a flush."""
+ return _is_straight(hand.cards) and not _is_flush(hand.cards)
+
+
+def is_three_of_a_kind(hand: Hand) -> bool:
+ """Calculate whether hand is a full house."""
+ return (
+ hand.value_counts[0][1] == 3
+ and hand.value_counts[1][1] == 1
+ and hand.value_counts[2][1] == 1
+ )
+
+
+def is_two_pair(hand: Hand) -> bool:
+ """Calculate whether hand is a two pair."""
+ return hand.value_counts[0][1] == 2 and hand.value_counts[1][1] == 2
+
+
+def is_pair(hand: Hand) -> bool:
+ """Calculate whether hand is a pair."""
+ return hand.value_counts[0][1] == 2 and hand.value_counts[1][1] == 1
+
+
+def get_rank(hand: Hand) -> Rank:
+ """Get hand rank.
+
+ TODO: Use a factory pattern to avoid this huge flow control.
+ """
+ if is_royal_flush(hand):
+ return Rank.ROYAL_FLUSH
+ elif is_straight_flush(hand):
+ return Rank.STRAIGHT_FLUSH
+ elif is_four_of_a_kind(hand):
+ return Rank.FOUR_OF_A_KIND
+ elif is_full_house(hand):
+ return Rank.FULL_HOUSE
+ elif is_flush(hand):
+ return Rank.FLUSH
+ elif is_straight(hand):
+ return Rank.STRAIGHT
+ elif is_three_of_a_kind(hand):
+ return Rank.THREE_OF_A_KIND
+ elif is_two_pair(hand):
+ return Rank.TWO_PAIR
+ elif is_pair(hand):
+ return Rank.PAIR
+ else:
+ return Rank.HIGH_CARD