poker-ranking-service/poker/rank/hands.py

173 lines
4.6 KiB
Python
Raw Permalink Normal View History

from typing import Optional, Union
from poker.constants import Rank, Value
from poker.models import Card, Hand, RankedHand
from poker.rank.descriptions import DESCRIPTIONS
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]) -> Optional[Value]:
"""Calculate whether hand is a straight.
Arguments:
cards: List of cards in hand.
Returns:
High card if hand is a straight, None otherwise.
"""
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 None
# Check for a ace low straight
if card_values == [Value.TWO, Value.THREE, Value.FOUR, Value.FIVE, Value.ACE]:
return Value.FIVE
# 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 max(cards).value
return None
def royal_flush(hand: Hand) -> dict[str, Union[str, int]]:
"""Calculate whether hand a royal flush."""
if not _is_flush(hand.cards):
return {}
values = [card.value for card in hand.cards]
_is = all(
value in values
for value in [Value.ACE, Value.KING, Value.QUEEN, Value.JACK, Value.TEN]
)
if _is:
return {"suit": hand.cards[0].suit}
return {}
def straight_flush(hand: Hand) -> dict[str, Union[str, int]]:
"""Calculate whether hand a straight flush."""
high = _is_straight(hand.cards)
if high is not None and _is_flush(hand.cards):
return {
"high": high,
"suit": hand.cards[0].suit,
}
return {}
def four_of_a_kind(hand: Hand) -> dict[str, Union[str, int]]:
"""Calculate whether hand has four of a kind."""
if hand.value_counts[0][1] == 4:
return {"value": hand.value_counts[0][0]}
return {}
def full_house(hand: Hand) -> dict[str, Union[str, int]]:
"""Calculate whether hand is a full house."""
if hand.value_counts[0][1] == 3 and hand.value_counts[1][1] == 2:
return {
"trips": hand.value_counts[0][0],
"pair": hand.value_counts[1][0],
}
return {}
def flush(hand: Hand) -> dict[str, Union[str, int]]:
"""Calculate whether hand is a flush."""
high = _is_straight(hand.cards)
if _is_flush(hand.cards) and high is None:
return {"suit": hand.cards[0].suit}
return {}
def straight(hand: Hand) -> dict[str, Union[str, int]]:
"""Calculate whether hand is a flush."""
high = _is_straight(hand.cards)
if high is not None and not _is_flush(hand.cards):
return {"high": high}
return {}
def three_of_a_kind(hand: Hand) -> dict[str, Union[str, int]]:
"""Calculate whether hand is a full house."""
if (
hand.value_counts[0][1] == 3
and hand.value_counts[1][1] == 1
and hand.value_counts[2][1] == 1
):
return {"value": hand.value_counts[0][0]}
return {}
def two_pair(hand: Hand) -> dict[str, Union[str, int]]:
"""Calculate whether hand is a two pair."""
if hand.value_counts[0][1] == 2 and hand.value_counts[1][1] == 2:
values = [hand.value_counts[0][0], hand.value_counts[1][0]]
return {
"high": max(values),
"low": min(values),
}
return {}
def pair(hand: Hand) -> dict[str, Union[str, int]]:
"""Calculate whether hand is a pair."""
if hand.value_counts[0][1] == 2 and hand.value_counts[1][1] == 1:
return {"value": hand.value_counts[0][0]}
return {}
def high_card(hand: Hand) -> dict[str, Union[str, int]]:
"""Get high card."""
return {"value": max(hand.cards).value}
_FUNCTIONS = {
Rank.ROYAL_FLUSH: royal_flush,
Rank.STRAIGHT_FLUSH: straight_flush,
Rank.FOUR_OF_A_KIND: four_of_a_kind,
Rank.FULL_HOUSE: full_house,
Rank.FLUSH: flush,
Rank.STRAIGHT: straight,
Rank.THREE_OF_A_KIND: three_of_a_kind,
Rank.TWO_PAIR: two_pair,
Rank.PAIR: pair,
Rank.HIGH_CARD: high_card,
}
def rank_hand(hand: Hand) -> RankedHand:
"""Get hand rank.
TODO: Use a factory pattern to avoid this huge flow control.
"""
out = None
for rank, func in _FUNCTIONS.items():
result = func(hand)
if result:
return RankedHand(
cards=hand.cards,
rank=rank,
description=DESCRIPTIONS[rank].format(**result),
)
if out is None:
raise ValueError("No rank found.")