Source code for maverick.utils.scoring
from typing import Tuple, TYPE_CHECKING
from itertools import combinations
if TYPE_CHECKING: # pragma: no cover
from ..card import Card
from ..enums import HandType
__all__ = ["score_hand", "find_highest_scoring_hand"]
def _check_four_of_a_kind(numbers: list[int]) -> float:
four_val = None
kickers = []
for i in numbers:
if numbers.count(i) == 4:
four_val = i
else:
kickers.append(i)
kickers = sorted(set(kickers), reverse=True)
score = 800 + four_val
score += sum([kickers[i] / (100 ** (i + 1)) for i in range(len(kickers))])
return score
def _check_full_house(numbers: list[int]) -> float:
triple_val = None
pair_val = None
for i in numbers:
if numbers.count(i) == 3:
triple_val = i
elif numbers.count(i) == 2:
pair_val = i
score = 700 + triple_val + pair_val / 100
return score
def _check_three_of_a_kind(numbers: list[int]) -> float:
triple_val = None
kickers = []
for i in numbers:
if numbers.count(i) == 3:
triple_val = i
else:
kickers.append(i)
kickers = sorted(set(kickers), reverse=True)
score = 400 + triple_val
score += sum([kickers[i] / (100 ** (i + 1)) for i in range(len(kickers))])
return score
def _check_two_pair(numbers: list[int]) -> float:
pairs = []
kickers = []
for i in numbers:
if numbers.count(i) == 2:
pairs.append(i)
elif numbers.count(i) == 1:
kickers.append(i)
pairs = sorted(set(pairs), reverse=True)
kickers = sorted(set(kickers), reverse=True)
score = 300 + pairs[0] + pairs[1] / 100
score += sum([kickers[i] / (100 ** (i + 2)) for i in range(len(kickers))])
return score
def _check_pair(numbers: list[int]) -> float:
pair_val = None
kickers = []
for i in numbers:
if numbers.count(i) == 2:
pair_val = i
else:
kickers.append(i)
kickers = sorted(set(kickers), reverse=True)
score = 200 + pair_val
score += sum([kickers[i] / (100 ** (i + 1)) for i in range(len(kickers))])
return score
[docs]
def score_hand(hand: list["Card"]) -> Tuple["HandType", float]:
"""
Classifies and scores a poker hand.
Works with any number of cards (not just 5).
Returns (HandType, float_score) where higher scores = stronger hands.
Hand ranking (base scores):
- High Card: 100+
- Pair: 200+
- Two Pair: 300+
- Three of a Kind: 400+
- Straight: 500+
- Flush: 600+
- Full House: 700+
- Four of a Kind: 800+
- Straight Flush: 900+
- Royal Flush: 1000
Parameters
----------
hand : list[Card]
The list of cards in the hand.
Returns
-------
tuple[HandType, float]
A tuple containing the hand type and its score.
"""
assert len(hand) > 0, "At least one card is required to score a hand."
# Extract suit and rank values
suit_values = [card.suit.value for card in hand]
rank_values = [card.rank.value for card in hand]
# Count repetitions
rnum = [rank_values.count(i) for i in rank_values]
rlet = [suit_values.count(i) for i in suit_values]
# Check for flush (all same suit, and at least 5 cards)
is_flush = max(rlet) >= 5
# Check for straight and find highest card in straight
unique_ranks = sorted(set(rank_values))
is_straight = False
straight_high_card = 0
if len(unique_ranks) >= 5:
# Check if any 5 consecutive ranks exist
for i in range(len(unique_ranks) - 4):
if unique_ranks[i + 4] - unique_ranks[i] == 4:
is_straight = True
straight_high_card = unique_ranks[i + 4]
# Special case: A-2-3-4-5 (wheel) - counts as 5-high, not ace-high
if set([14, 2, 3, 4, 5]).issubset(set(unique_ranks)):
is_straight = True
if straight_high_card == 0: # Only wheel, no higher straight
straight_high_card = 5 # Wheel is 5-high
handtype = HandType.HIGH_CARD
score = 0.0
# Royal Flush: A-K-Q-J-10 all same suit
if (
is_flush
and is_straight
and set([14, 13, 12, 11, 10]).issubset(set(rank_values))
):
handtype = HandType.ROYAL_FLUSH
score = 1000.0
# Straight Flush
elif is_flush and is_straight:
handtype = HandType.STRAIGHT_FLUSH
score = 900 + straight_high_card / 100
# Four of a Kind
elif 4 in rnum:
handtype = HandType.FOUR_OF_A_KIND
score = _check_four_of_a_kind(rank_values)
# Full House
elif 3 in rnum and 2 in rnum:
handtype = HandType.FULL_HOUSE
score = _check_full_house(rank_values)
# Flush
elif is_flush:
handtype = HandType.FLUSH
n = sorted(rank_values, reverse=True)
score = 600 + sum([n[i] / (100 ** (i + 1)) for i in range(len(n))])
# Straight
elif is_straight:
handtype = HandType.STRAIGHT
score = 500 + straight_high_card / 100
# Three of a Kind
elif 3 in rnum:
handtype = HandType.THREE_OF_A_KIND
score = _check_three_of_a_kind(rank_values)
# Two Pair
elif rnum.count(2) >= 4: # At least 2 pairs
handtype = HandType.TWO_PAIR
score = _check_two_pair(rank_values)
# Pair
elif 2 in rnum:
handtype = HandType.PAIR
score = _check_pair(rank_values)
# High Card
else:
handtype = HandType.HIGH_CARD
n = sorted(rank_values, reverse=True)
score = 100 + sum([n[i] / (100 ** (i + 1)) for i in range(len(n))])
return handtype, score
def find_highest_scoring_hand(
private_cards: list["Card"],
community_cards: list["Card"],
n_private: int = 0,
) -> tuple[list["Card"], "HandType", float]:
"""
Find the highest scoring 5-card hand from the given private and community cards
with at least n_private cards from the private cards.
Parameters
----------
private_cards : list[Card]
The player's private cards.
community_cards : list[Card]
The community cards on the table.
n_private : int, optional
The number of private cards that must be included in the hand (default is 0).
A value of 0 means any number of private cards can be used.
Returns
-------
tuple[list[Card], HandType, float]
A tuple containing the best 5-card hand, its hand type, and its score.
"""
all_cards = private_cards + community_cards
# If we have 5 or fewer cards total, return all of them
if len(all_cards) <= 5:
return all_cards, *score_hand(all_cards)
best_hand = None
best_score = -1.0
n_card = min(5, len(all_cards))
# Try all 5-card combinations
for hand in combinations(all_cards, n_card):
# Count how many private cards are in this hand
private_count = sum(1 for card in hand if card in private_cards)
# Skip if doesn't meet minimum private card requirement
if (n_private > 0) and (private_count != n_private):
continue
# Score this hand
_, score = score_hand(list(hand))
# Update best hand if this one is better
if score > best_score:
best_score = score
best_hand = hand
if best_hand is None:
return [], None, 0.0
# Return the best hand found
return list(best_hand), *score_hand(list(best_hand))