diff options
author | Paul Harrison <paul@harrison.sh> | 2022-11-20 13:39:08 +0000 |
---|---|---|
committer | Paul Harrison <paul@harrison.sh> | 2022-12-15 16:02:14 +0000 |
commit | b4ba284dcc20563b9b7dde770b7c9f67c6b25e76 (patch) | |
tree | 7131329e9367c5140f65ebc48ec09ae732261fbb /poker | |
parent | d924e39608861362e08c5e3706ff46a7a1af919b (diff) |
feat: Construct ranked hand with description
Refactors rank test functions to retern details required for
description, then return RankedHand from rank_hand function.
Diffstat (limited to 'poker')
-rw-r--r-- | poker/rank/__init__.py | 3 | ||||
-rw-r--r-- | poker/rank/descriptions.py | 14 | ||||
-rw-r--r-- | poker/rank/hands.py | 160 |
3 files changed, 129 insertions, 48 deletions
diff --git a/poker/rank/__init__.py b/poker/rank/__init__.py index e69de29..c8700f9 100644 --- a/poker/rank/__init__.py +++ b/poker/rank/__init__.py @@ -0,0 +1,3 @@ +from poker.rank.hands import rank_hand + +__all__ = ["rank_hand"] diff --git a/poker/rank/descriptions.py b/poker/rank/descriptions.py index e69de29..e7f367a 100644 --- a/poker/rank/descriptions.py +++ b/poker/rank/descriptions.py @@ -0,0 +1,14 @@ +from poker.constants import Rank + +DESCRIPTIONS = { + Rank.ROYAL_FLUSH: "royal flush: {suit}", + Rank.STRAIGHT_FLUSH: "straight flush: {high}-high {suit}", + Rank.FOUR_OF_A_KIND: "four of a kind: {value}", + Rank.FULL_HOUSE: "full house: {trips} over {pair}", + Rank.FLUSH: "flush: {suit}", + Rank.STRAIGHT: "straight: {high}-high", + Rank.THREE_OF_A_KIND: "three of a kind: {value}", + Rank.TWO_PAIR: "two pair: {high} and {low}", + Rank.PAIR: "pair: {value}", + Rank.HIGH_CARD: "high card: {value}", +} diff --git a/poker/rank/hands.py b/poker/rank/hands.py index 8764ce9..6a81af5 100644 --- a/poker/rank/hands.py +++ b/poker/rank/hands.py @@ -1,5 +1,8 @@ +from typing import Optional, Union + from poker.constants import Rank, Value -from poker.models import Card, Hand +from poker.models import Card, Hand, RankedHand +from poker.rank.descriptions import DESCRIPTIONS def _is_flush(cards: list[Card]) -> bool: @@ -7,104 +10,165 @@ def _is_flush(cards: list[Card]) -> bool: return len(set(card.suit for card in cards)) == 1 -def _is_straight(cards: list[Card]) -> bool: - """Calculate whether hand is a straight.""" +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 False + return None # Check for a ace low straight if card_values == [Value.TWO, Value.THREE, Value.FOUR, Value.FIVE, Value.ACE]: - return True + 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 True + return max(cards).value - return False + return None -def is_royal_flush(hand: Hand) -> bool: +def royal_flush(hand: Hand) -> dict[str, Union[str, int]]: """Calculate whether hand a royal flush.""" if not _is_flush(hand.cards): - return False + return {} values = [card.value for card in hand.cards] - return all( + _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 is_straight_flush(hand: Hand) -> bool: +def straight_flush(hand: Hand) -> dict[str, Union[str, int]]: """Calculate whether hand a straight flush.""" - return _is_straight(hand.cards) and _is_flush(hand.cards) + 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 is_four_of_a_kind(hand: Hand) -> bool: + +def four_of_a_kind(hand: Hand) -> dict[str, Union[str, int]]: """Calculate whether hand has four of a kind.""" - return hand.value_counts[0][1] == 4 + if hand.value_counts[0][1] == 4: + return {"value": hand.value_counts[0][0]} + + return {} -def is_full_house(hand: Hand) -> bool: +def full_house(hand: Hand) -> dict[str, Union[str, int]]: """Calculate whether hand is a full house.""" - return hand.value_counts[0][1] == 3 and hand.value_counts[1][1] == 2 + 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 is_flush(hand: Hand) -> bool: + +def flush(hand: Hand) -> dict[str, Union[str, int]]: """Calculate whether hand is a flush.""" - return _is_flush(hand.cards) and not _is_straight(hand.cards) + high = _is_straight(hand.cards) + if _is_flush(hand.cards) and high is None: + return {"suit": hand.cards[0].suit} + + return {} -def is_straight(hand: Hand) -> bool: +def straight(hand: Hand) -> dict[str, Union[str, int]]: """Calculate whether hand is a flush.""" - return _is_straight(hand.cards) and not _is_flush(hand.cards) + high = _is_straight(hand.cards) + if high is not None and not _is_flush(hand.cards): + return {"high": high} + return {} -def is_three_of_a_kind(hand: Hand) -> bool: + +def three_of_a_kind(hand: Hand) -> dict[str, Union[str, int]]: """Calculate whether hand is a full house.""" - return ( + 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 is_two_pair(hand: Hand) -> bool: +def two_pair(hand: Hand) -> dict[str, Union[str, int]]: """Calculate whether hand is a two pair.""" - return hand.value_counts[0][1] == 2 and hand.value_counts[1][1] == 2 + 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 is_pair(hand: Hand) -> bool: + +def pair(hand: Hand) -> dict[str, Union[str, int]]: """Calculate whether hand is a pair.""" - return hand.value_counts[0][1] == 2 and hand.value_counts[1][1] == 1 + 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 get_rank(hand: Hand) -> Rank: +def rank_hand(hand: Hand) -> RankedHand: """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 + out = None + for rank, func in _FUNCTIONS.items(): + result = func(hand) + if result: + out = RankedHand( + cards=hand.cards, + rank=rank, + description=DESCRIPTIONS[rank].format(**result), + ) + + if out is None: + raise ValueError("No rank found.") + + return out |