task4
This commit is contained in:
2
main.py
2
main.py
@@ -10,7 +10,7 @@ def main():
|
|||||||
|
|
||||||
cards_str = sys.argv[1]
|
cards_str = sys.argv[1]
|
||||||
|
|
||||||
hand_ranking = HandEvaluator.evaluateFromInput(cards_str)
|
hand_ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
print(hand_ranking)
|
print(hand_ranking)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
110
poker/card.py
110
poker/card.py
@@ -1,6 +1,6 @@
|
|||||||
from functools import total_ordering
|
from functools import total_ordering
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Tuple, Optional
|
from typing import List, Tuple, Optional, Union, Any
|
||||||
|
|
||||||
|
|
||||||
class Suit(Enum):
|
class Suit(Enum):
|
||||||
@@ -51,29 +51,101 @@ class Rank(Enum):
|
|||||||
return hash(self.numeric_value)
|
return hash(self.numeric_value)
|
||||||
|
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class ShortDeckRank(Enum):
|
||||||
|
"""
|
||||||
|
shortdeck不包含2,3,4,5点数
|
||||||
|
"""
|
||||||
|
SIX = (6, '6')
|
||||||
|
SEVEN = (7, '7')
|
||||||
|
EIGHT = (8, '8')
|
||||||
|
NINE = (9, '9')
|
||||||
|
TEN = (10, 'T')
|
||||||
|
JACK = (11, 'J')
|
||||||
|
QUEEN = (12, 'Q')
|
||||||
|
KING = (13, 'K')
|
||||||
|
ACE = (14, 'A')
|
||||||
|
|
||||||
|
def __new__(cls, value, symbol):
|
||||||
|
obj = object.__new__(cls)
|
||||||
|
obj._value_ = value
|
||||||
|
obj.numeric_value = value
|
||||||
|
obj.symbol = symbol
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.symbol
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, ShortDeckRank):
|
||||||
|
return self.numeric_value == other.numeric_value
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if isinstance(other, ShortDeckRank):
|
||||||
|
return self.numeric_value < other.numeric_value
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.numeric_value)
|
||||||
|
|
||||||
|
|
||||||
@total_ordering
|
@total_ordering
|
||||||
class Card:
|
class Card:
|
||||||
def __init__(self, rank: Rank, suit: Suit):
|
"""
|
||||||
|
通用卡片类,支持标准扑克和短牌扑克
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, rank: Union[Rank, 'ShortDeckRank'], suit: Suit):
|
||||||
self.rank = rank
|
self.rank = rank
|
||||||
self.suit = suit
|
self.suit = suit
|
||||||
|
|
||||||
|
def get_rank(self) -> Union[Rank, 'ShortDeckRank']:
|
||||||
|
return self.rank
|
||||||
|
|
||||||
|
def get_suit(self) -> Suit:
|
||||||
|
return self.suit
|
||||||
|
|
||||||
|
def is_short_deck(self) -> bool:
|
||||||
|
return isinstance(self.rank, ShortDeckRank)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.rank}{self.suit}"
|
return f"{self.rank}{self.suit}"
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if isinstance(other, Card):
|
if isinstance(other, Card):
|
||||||
return self.rank == other.rank and self.suit == other.suit
|
self_value = getattr(self.rank, 'numeric_value', self.rank.value)
|
||||||
|
other_value = getattr(other.rank, 'numeric_value', other.rank.value)
|
||||||
|
return self_value == other_value and self.suit == other.suit
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
if isinstance(other, Card):
|
if isinstance(other, Card):
|
||||||
if self.rank != other.rank:
|
self_value = getattr(self.rank, 'numeric_value', self.rank.value)
|
||||||
return self.rank < other.rank
|
other_value = getattr(other.rank, 'numeric_value', other.rank.value)
|
||||||
|
|
||||||
|
if self_value != other_value:
|
||||||
|
return self_value < other_value
|
||||||
return self.suit.value < other.suit.value
|
return self.suit.value < other.suit.value
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _find_rank(rank_char, is_short_deck):
|
||||||
|
if is_short_deck:
|
||||||
|
# 在ShortDeckRank中查找
|
||||||
|
for r in ShortDeckRank:
|
||||||
|
if r.symbol == rank_char:
|
||||||
|
return r
|
||||||
|
else:
|
||||||
|
# 在Rank中查找
|
||||||
|
for r in Rank:
|
||||||
|
if r.symbol == rank_char:
|
||||||
|
return r
|
||||||
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def createCard(cls, card_str) -> 'Card':
|
def create_card(cls, card_str, is_short_deck=False) -> 'Card':
|
||||||
|
"""创建卡片,支持标准扑克和短牌扑克"""
|
||||||
if len(card_str) != 2:
|
if len(card_str) != 2:
|
||||||
raise ValueError(f"Invalid card string: {card_str}")
|
raise ValueError(f"Invalid card string: {card_str}")
|
||||||
|
|
||||||
@@ -81,14 +153,10 @@ class Card:
|
|||||||
suit_char = card_str[1].lower()
|
suit_char = card_str[1].lower()
|
||||||
|
|
||||||
# 查找rank
|
# 查找rank
|
||||||
rank = None
|
rank = cls._find_rank(rank_char, is_short_deck)
|
||||||
for r in Rank:
|
|
||||||
if r.symbol == rank_char:
|
|
||||||
rank = r
|
|
||||||
break
|
|
||||||
|
|
||||||
if rank is None:
|
if rank is None:
|
||||||
raise ValueError(f"Invalid rank: {rank_char}")
|
rank_type = "short deck" if is_short_deck else "standard"
|
||||||
|
raise ValueError(f"Invalid rank for {rank_type}: {rank_char}")
|
||||||
|
|
||||||
# 查找suit
|
# 查找suit
|
||||||
suit = None
|
suit = None
|
||||||
@@ -103,9 +171,10 @@ class Card:
|
|||||||
return cls(rank, suit)
|
return cls(rank, suit)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parseCards(cls, cards_str) -> List['Card']:
|
def parse_cards(cls, cards_str, is_short_deck=False) -> List['Card']:
|
||||||
"""
|
"""
|
||||||
从字符串拆解多张牌, "AsKs AhAdAc6s7s"
|
从字符串拆解多张牌,支持标准扑克和短牌扑克
|
||||||
|
例如: "AsKs AhAdAc6s7s"
|
||||||
"""
|
"""
|
||||||
cards_str = cards_str.strip()
|
cards_str = cards_str.strip()
|
||||||
if not cards_str:
|
if not cards_str:
|
||||||
@@ -125,4 +194,13 @@ class Card:
|
|||||||
i += 2
|
i += 2
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Invalid card format at position {i}")
|
raise ValueError(f"Invalid card format at position {i}")
|
||||||
return [cls.createCard(card_str) for card_str in card_strings]
|
|
||||||
|
return [cls.create_card(card_str, is_short_deck) for card_str in card_strings]
|
||||||
|
@classmethod
|
||||||
|
def parseLongCards(cls, cards_str) -> List['Card']:
|
||||||
|
return cls.parse_cards(cards_str, is_short_deck=False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_short_deck_cards(cls, cards_str) -> List['Card']:
|
||||||
|
return cls.parse_cards(cards_str, is_short_deck=True)
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ from .hand_ranking import HandRanking, HandType
|
|||||||
|
|
||||||
class HandEvaluator:
|
class HandEvaluator:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def evaluateHand(cards) -> HandRanking:
|
def evaluate_hand(cards) -> HandRanking:
|
||||||
"""
|
"""
|
||||||
从7张牌中找出最好的5张牌组合
|
从7张牌中找出最好的5张牌组合
|
||||||
"""
|
"""
|
||||||
@@ -19,7 +19,7 @@ class HandEvaluator:
|
|||||||
|
|
||||||
# 所有可能的5张牌组合
|
# 所有可能的5张牌组合
|
||||||
for five_cards in combinations(cards, 5):
|
for five_cards in combinations(cards, 5):
|
||||||
ranking = HandEvaluator.evaluate5Cards(list(five_cards))
|
ranking = HandEvaluator.evaluate_5_cards(list(five_cards))
|
||||||
|
|
||||||
if best_ranking is None or ranking > best_ranking:
|
if best_ranking is None or ranking > best_ranking:
|
||||||
best_ranking = ranking
|
best_ranking = ranking
|
||||||
@@ -28,11 +28,8 @@ class HandEvaluator:
|
|||||||
best_ranking.cards = best_cards
|
best_ranking.cards = best_cards
|
||||||
return best_ranking
|
return best_ranking
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def evaluate5Cards(cards) -> HandRanking:
|
def _analyze_cards(cls, cards: List[Card]) -> tuple:
|
||||||
if len(cards) != 5:
|
|
||||||
raise ValueError(f"Expected 5 cards, got {len(cards)}")
|
|
||||||
|
|
||||||
# 按点数排序(降序)
|
# 按点数排序(降序)
|
||||||
sorted_cards = sorted(cards, key=lambda c: c.rank.numeric_value, reverse=True)
|
sorted_cards = sorted(cards, key=lambda c: c.rank.numeric_value, reverse=True)
|
||||||
ranks = [card.rank for card in sorted_cards]
|
ranks = [card.rank for card in sorted_cards]
|
||||||
@@ -45,8 +42,16 @@ class HandEvaluator:
|
|||||||
# 同花
|
# 同花
|
||||||
is_flush = len(set(suits)) == 1
|
is_flush = len(set(suits)) == 1
|
||||||
|
|
||||||
# 顺子
|
is_straight, straight_high = cls._is_straight(ranks)
|
||||||
is_straight, straight_high = HandEvaluator._isStraight(ranks)
|
|
||||||
|
return sorted_cards, ranks, suits, rank_counts, count_values, is_flush, is_straight, straight_high
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def evaluate_5_cards(cards) -> HandRanking:
|
||||||
|
if len(cards) != 5:
|
||||||
|
raise ValueError(f"Expected 5 cards, got {len(cards)}")
|
||||||
|
|
||||||
|
sorted_cards, ranks, suits, rank_counts, count_values, is_flush, is_straight, straight_high = HandEvaluator._analyze_cards(cards)
|
||||||
|
|
||||||
# 根据牌型返回相应的HandRanking
|
# 根据牌型返回相应的HandRanking
|
||||||
if is_straight and is_flush:
|
if is_straight and is_flush:
|
||||||
@@ -90,7 +95,7 @@ class HandEvaluator:
|
|||||||
return HandRanking(HandType.HIGH_CARD, ranks, sorted_cards)
|
return HandRanking(HandType.HIGH_CARD, ranks, sorted_cards)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _isStraight(ranks) -> Tuple[bool, Rank]:
|
def _is_straight(ranks) -> Tuple[bool, Rank]:
|
||||||
# 排序点数值
|
# 排序点数值
|
||||||
values = sorted([rank.numeric_value for rank in ranks], reverse=True)
|
values = sorted([rank.numeric_value for rank in ranks], reverse=True)
|
||||||
|
|
||||||
@@ -117,6 +122,6 @@ class HandEvaluator:
|
|||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def evaluateFromInput(cards_str) -> HandRanking:
|
def evaluate_from_input(cards_str) -> HandRanking:
|
||||||
cards = Card.parseCards(cards_str)
|
cards = Card.parse_cards(cards_str)
|
||||||
return HandEvaluator.evaluateHand(cards)
|
return HandEvaluator.evaluate_hand(cards)
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import total_ordering
|
from functools import total_ordering
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple, Union, TypeVar, Any
|
||||||
from .card import Card, Rank
|
from .card import Card, Rank, ShortDeckRank
|
||||||
|
|
||||||
|
HandTypeVar = TypeVar('HandTypeVar', bound=Enum)
|
||||||
|
|
||||||
|
|
||||||
@total_ordering
|
@total_ordering
|
||||||
@@ -42,32 +44,36 @@ class HandType(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class HandRanking:
|
class HandRanking:
|
||||||
def __init__(self, hand_type: HandType, key_ranks: List[Rank], cards: List[Card]):
|
"""通用手牌排名类,支持标准扑克和短牌扑克的手牌类型"""
|
||||||
|
|
||||||
|
def __init__(self, hand_type: Any, key_ranks: List[Union[Rank, ShortDeckRank]], cards: List[Card]):
|
||||||
self.hand_type = hand_type
|
self.hand_type = hand_type
|
||||||
self.key_ranks = key_ranks # 用于比较的关键点数
|
self.key_ranks = key_ranks # 用于比较的关键点数
|
||||||
self.cards = cards # 组成这个ranking的5张牌
|
self.cards = cards # 组成这个ranking的5张牌
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.hand_type == HandType.FOUR_OF_A_KIND:
|
hand_type_name = self.hand_type.name if hasattr(self.hand_type, 'name') else str(self.hand_type)
|
||||||
|
|
||||||
|
if hand_type_name == "FOUR_OF_A_KIND":
|
||||||
return f"Quad({self.key_ranks[0].symbol})"
|
return f"Quad({self.key_ranks[0].symbol})"
|
||||||
elif self.hand_type == HandType.FULL_HOUSE:
|
elif hand_type_name == "FULL_HOUSE":
|
||||||
return f"Full House({self.key_ranks[0].symbol} over {self.key_ranks[1].symbol})"
|
return f"Full House({self.key_ranks[0].symbol} over {self.key_ranks[1].symbol})"
|
||||||
elif self.hand_type == HandType.FLUSH:
|
elif hand_type_name == "FLUSH":
|
||||||
return f"Flush({self.key_ranks[0].symbol} high)"
|
return f"Flush({self.key_ranks[0].symbol} high)"
|
||||||
elif self.hand_type == HandType.STRAIGHT:
|
elif hand_type_name == "STRAIGHT":
|
||||||
return f"Straight({self.key_ranks[0].symbol} high)"
|
return f"Straight({self.key_ranks[0].symbol} high)"
|
||||||
elif self.hand_type == HandType.STRAIGHT_FLUSH:
|
elif hand_type_name == "STRAIGHT_FLUSH":
|
||||||
if self.key_ranks[0] == Rank.ACE:
|
if hasattr(self.key_ranks[0], 'symbol') and self.key_ranks[0].symbol == 'A':
|
||||||
return "Royal Flush"
|
return "Royal Flush"
|
||||||
else:
|
else:
|
||||||
return f"Straight Flush({self.key_ranks[0].symbol} high)"
|
return f"Straight Flush({self.key_ranks[0].symbol} high)"
|
||||||
elif self.hand_type == HandType.ROYAL_FLUSH:
|
elif hand_type_name == "ROYAL_FLUSH":
|
||||||
return "Royal Flush"
|
return "Royal Flush"
|
||||||
elif self.hand_type == HandType.THREE_OF_A_KIND:
|
elif hand_type_name == "THREE_OF_A_KIND":
|
||||||
return f"Three of a Kind({self.key_ranks[0].symbol})"
|
return f"Three of a Kind({self.key_ranks[0].symbol})"
|
||||||
elif self.hand_type == HandType.TWO_PAIR:
|
elif hand_type_name == "TWO_PAIR":
|
||||||
return f"Two Pair({self.key_ranks[0].symbol} and {self.key_ranks[1].symbol})"
|
return f"Two Pair({self.key_ranks[0].symbol} and {self.key_ranks[1].symbol})"
|
||||||
elif self.hand_type == HandType.ONE_PAIR:
|
elif hand_type_name == "ONE_PAIR":
|
||||||
return f"Pair({self.key_ranks[0].symbol})"
|
return f"Pair({self.key_ranks[0].symbol})"
|
||||||
else: # HIGH_CARD
|
else: # HIGH_CARD
|
||||||
return f"High Card({self.key_ranks[0].symbol})"
|
return f"High Card({self.key_ranks[0].symbol})"
|
||||||
@@ -75,14 +81,20 @@ class HandRanking:
|
|||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, HandRanking):
|
if not isinstance(other, HandRanking):
|
||||||
return False
|
return False
|
||||||
return (self.hand_type == other.hand_type and self.key_ranks == other.key_ranks)
|
self_strength = getattr(self.hand_type, 'strength', self.hand_type.value[0] if hasattr(self.hand_type.value, '__getitem__') else self.hand_type.value)
|
||||||
|
other_strength = getattr(other.hand_type, 'strength', other.hand_type.value[0] if hasattr(other.hand_type.value, '__getitem__') else other.hand_type.value)
|
||||||
|
return (self_strength == other_strength and self.key_ranks == other.key_ranks)
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
if not isinstance(other, HandRanking):
|
if not isinstance(other, HandRanking):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
if self.hand_type != other.hand_type:
|
|
||||||
return self.hand_type < other.hand_type
|
self_strength = getattr(self.hand_type, 'strength', self.hand_type.value[0] if hasattr(self.hand_type.value, '__getitem__') else self.hand_type.value)
|
||||||
# 手牌类型相同比较点数
|
other_strength = getattr(other.hand_type, 'strength', other.hand_type.value[0] if hasattr(other.hand_type.value, '__getitem__') else other.hand_type.value)
|
||||||
|
|
||||||
|
if self_strength != other_strength:
|
||||||
|
return self_strength < other_strength
|
||||||
|
|
||||||
for self_rank, other_rank in zip(self.key_ranks, other.key_ranks):
|
for self_rank, other_rank in zip(self.key_ranks, other.key_ranks):
|
||||||
if self_rank != other_rank:
|
if self_rank != other_rank:
|
||||||
return self_rank < other_rank
|
return self_rank < other_rank
|
||||||
|
|||||||
@@ -11,3 +11,9 @@ dependencies = [
|
|||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
python_files = ["test_*.py"]
|
python_files = ["test_*.py"]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"coverage>=7.10.7",
|
||||||
|
"pytest-cov>=7.0.0",
|
||||||
|
]
|
||||||
|
|||||||
12
run_tests.py
12
run_tests.py
@@ -19,7 +19,7 @@ def test_basic_card_functionality():
|
|||||||
assert str(card) == "As", f"Expected 'As', got '{str(card)}'"
|
assert str(card) == "As", f"Expected 'As', got '{str(card)}'"
|
||||||
|
|
||||||
# Test card parsing
|
# Test card parsing
|
||||||
parsed_card = Card.createCard("Kh")
|
parsed_card = Card.create_card("Kh")
|
||||||
assert parsed_card.rank == Rank.KING
|
assert parsed_card.rank == Rank.KING
|
||||||
assert parsed_card.suit == Suit.HEARTS
|
assert parsed_card.suit == Suit.HEARTS
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ def test_hand_parsing():
|
|||||||
"""Test hand parsing from string"""
|
"""Test hand parsing from string"""
|
||||||
print("Testing hand parsing...")
|
print("Testing hand parsing...")
|
||||||
|
|
||||||
cards = Card.parseCards("AsKs AhAdAc6s7s")
|
cards = Card.parse_cards("AsKs AhAdAc6s7s")
|
||||||
assert len(cards) == 7, f"Expected 7 cards, got {len(cards)}"
|
assert len(cards) == 7, f"Expected 7 cards, got {len(cards)}"
|
||||||
assert str(cards[0]) == "As"
|
assert str(cards[0]) == "As"
|
||||||
assert str(cards[6]) == "7s"
|
assert str(cards[6]) == "7s"
|
||||||
@@ -61,7 +61,7 @@ def test_hand_evaluation_examples():
|
|||||||
]
|
]
|
||||||
|
|
||||||
for cards_str, expected_str, expected_type in test_cases:
|
for cards_str, expected_str, expected_type in test_cases:
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
actual_str = str(ranking)
|
actual_str = str(ranking)
|
||||||
|
|
||||||
assert ranking.hand_type == expected_type, f"Wrong hand type for {cards_str}: expected {expected_type}, got {ranking.hand_type}"
|
assert ranking.hand_type == expected_type, f"Wrong hand type for {cards_str}: expected {expected_type}, got {ranking.hand_type}"
|
||||||
@@ -75,7 +75,7 @@ def test_wheel_straight():
|
|||||||
print("Testing wheel straight...")
|
print("Testing wheel straight...")
|
||||||
|
|
||||||
cards_str = "As2h3d4c5h7s8s"
|
cards_str = "As2h3d4c5h7s8s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.STRAIGHT
|
assert ranking.hand_type == HandType.STRAIGHT
|
||||||
assert ranking.key_ranks[0] == Rank.FIVE, "In wheel straight, 5 should be the high card"
|
assert ranking.key_ranks[0] == Rank.FIVE, "In wheel straight, 5 should be the high card"
|
||||||
@@ -90,14 +90,14 @@ def test_error_handling():
|
|||||||
|
|
||||||
# Test invalid card string
|
# Test invalid card string
|
||||||
try:
|
try:
|
||||||
Card.createCard("Xx")
|
Card.create_card("Xx")
|
||||||
assert False, "Should have raised ValueError for invalid card"
|
assert False, "Should have raised ValueError for invalid card"
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass # Expected
|
pass # Expected
|
||||||
|
|
||||||
# Test wrong number of cards
|
# Test wrong number of cards
|
||||||
try:
|
try:
|
||||||
HandEvaluator.evaluateFromInput("AsKh")
|
HandEvaluator.evaluate_from_input("AsKh")
|
||||||
assert False, "Should have raised ValueError for wrong number of cards"
|
assert False, "Should have raised ValueError for wrong number of cards"
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass # Expected
|
pass # Expected
|
||||||
|
|||||||
10
shortdeck/__init__.py
Normal file
10
shortdeck/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from poker.card import ShortDeckRank
|
||||||
|
from .hand_evaluator import ShortDeckHandEvaluator
|
||||||
|
from .hand_ranking import ShortDeckHandType, ShortDeckHandRanking
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'ShortDeckRank',
|
||||||
|
'ShortDeckHandEvaluator',
|
||||||
|
'ShortDeckHandType',
|
||||||
|
'ShortDeckHandRanking'
|
||||||
|
]
|
||||||
103
shortdeck/hand_evaluator.py
Normal file
103
shortdeck/hand_evaluator.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
from collections import Counter
|
||||||
|
from typing import List, Optional
|
||||||
|
from poker.hand_evaluator import HandEvaluator
|
||||||
|
from poker.card import Suit, Card, ShortDeckRank
|
||||||
|
from .hand_ranking import ShortDeckHandType, ShortDeckHandRanking
|
||||||
|
from itertools import combinations
|
||||||
|
|
||||||
|
|
||||||
|
class ShortDeckHandEvaluator(HandEvaluator):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def evaluate_hand(cls, cards: List[Card]) -> ShortDeckHandRanking:
|
||||||
|
if len(cards) < 5:
|
||||||
|
raise ValueError("Need at least 5 cards to evaluate a hand")
|
||||||
|
|
||||||
|
best_ranking = None
|
||||||
|
for combo in combinations(cards, 5):
|
||||||
|
ranking = cls.evaluate_5_cards(list(combo))
|
||||||
|
if best_ranking is None or ranking > best_ranking:
|
||||||
|
best_ranking = ranking
|
||||||
|
|
||||||
|
return best_ranking
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def evaluate_5_cards(cls, cards: List[Card]) -> ShortDeckHandRanking:
|
||||||
|
if len(cards) != 5:
|
||||||
|
raise ValueError("Must provide exactly 5 cards")
|
||||||
|
|
||||||
|
from poker.hand_evaluator import HandEvaluator
|
||||||
|
sorted_cards, ranks, suits, rank_counts, count_values, is_flush, _, _ = HandEvaluator._analyze_cards(cards)
|
||||||
|
|
||||||
|
# 重写顺子判断
|
||||||
|
is_straight, straight_high = cls.is_straight(ranks)
|
||||||
|
|
||||||
|
if is_straight and is_flush:
|
||||||
|
if straight_high == ShortDeckRank.ACE:
|
||||||
|
return ShortDeckHandRanking(ShortDeckHandType.ROYAL_FLUSH, [straight_high], sorted_cards)
|
||||||
|
else:
|
||||||
|
return ShortDeckHandRanking(ShortDeckHandType.STRAIGHT_FLUSH, [straight_high], sorted_cards)
|
||||||
|
|
||||||
|
if count_values == [4, 1]: # 四条
|
||||||
|
quad_rank = [rank for rank, count in rank_counts.items() if count == 4][0]
|
||||||
|
kicker = [rank for rank, count in rank_counts.items() if count == 1][0]
|
||||||
|
return ShortDeckHandRanking(ShortDeckHandType.FOUR_OF_A_KIND, [quad_rank, kicker], sorted_cards)
|
||||||
|
|
||||||
|
# 关键差异:Flush > Full House
|
||||||
|
if is_flush:
|
||||||
|
return ShortDeckHandRanking(ShortDeckHandType.FLUSH, ranks, sorted_cards)
|
||||||
|
|
||||||
|
if count_values == [3, 2]: # 满堂红
|
||||||
|
trip_rank = [rank for rank, count in rank_counts.items() if count == 3][0]
|
||||||
|
pair_rank = [rank for rank, count in rank_counts.items() if count == 2][0]
|
||||||
|
return ShortDeckHandRanking(ShortDeckHandType.FULL_HOUSE, [trip_rank, pair_rank], sorted_cards)
|
||||||
|
|
||||||
|
if is_straight:
|
||||||
|
return ShortDeckHandRanking(ShortDeckHandType.STRAIGHT, [straight_high], sorted_cards)
|
||||||
|
|
||||||
|
if count_values == [3, 1, 1]: # 三条
|
||||||
|
trip_rank = [rank for rank, count in rank_counts.items() if count == 3][0]
|
||||||
|
kickers = sorted([rank for rank, count in rank_counts.items() if count == 1],
|
||||||
|
key=lambda x: x.numeric_value, reverse=True)
|
||||||
|
return ShortDeckHandRanking(ShortDeckHandType.THREE_OF_A_KIND, [trip_rank] + kickers, sorted_cards)
|
||||||
|
|
||||||
|
if count_values == [2, 2, 1]: # 两对
|
||||||
|
pairs = sorted([rank for rank, count in rank_counts.items() if count == 2],
|
||||||
|
key=lambda x: x.numeric_value, reverse=True)
|
||||||
|
kicker = [rank for rank, count in rank_counts.items() if count == 1][0]
|
||||||
|
return ShortDeckHandRanking(ShortDeckHandType.TWO_PAIR, pairs + [kicker], sorted_cards)
|
||||||
|
|
||||||
|
if count_values == [2, 1, 1, 1]: # 一对
|
||||||
|
pair_rank = [rank for rank, count in rank_counts.items() if count == 2][0]
|
||||||
|
kickers = sorted([rank for rank, count in rank_counts.items() if count == 1],
|
||||||
|
key=lambda x: x.numeric_value, reverse=True)
|
||||||
|
return ShortDeckHandRanking(ShortDeckHandType.ONE_PAIR, [pair_rank] + kickers, sorted_cards)
|
||||||
|
|
||||||
|
return ShortDeckHandRanking(ShortDeckHandType.HIGH_CARD, ranks, sorted_cards)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_straight(cls, ranks: List[ShortDeckRank]) -> tuple[bool, Optional[ShortDeckRank]]:
|
||||||
|
unique_ranks = sorted(set(rank.numeric_value for rank in ranks))
|
||||||
|
|
||||||
|
if len(unique_ranks) < 5:
|
||||||
|
return False, None
|
||||||
|
ace_low_straight = [6, 7, 8, 9, 14] # A(14), 6, 7, 8, 9
|
||||||
|
if all(val in unique_ranks for val in ace_low_straight):
|
||||||
|
return True, ShortDeckRank.NINE
|
||||||
|
|
||||||
|
for i in range(len(unique_ranks) - 4):
|
||||||
|
if unique_ranks[i+4] - unique_ranks[i] == 4:
|
||||||
|
high_value = unique_ranks[i+4]
|
||||||
|
high_rank = None
|
||||||
|
for rank in ShortDeckRank:
|
||||||
|
if rank.numeric_value == high_value:
|
||||||
|
high_rank = rank
|
||||||
|
break
|
||||||
|
return True, high_rank
|
||||||
|
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def evaluate_from_input(cls, input_str) -> ShortDeckHandRanking:
|
||||||
|
cards = Card.parse_short_deck_cards(input_str)
|
||||||
|
return cls.evaluate_hand(cards)
|
||||||
52
shortdeck/hand_ranking.py
Normal file
52
shortdeck/hand_ranking.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from functools import total_ordering
|
||||||
|
from enum import Enum
|
||||||
|
from typing import List
|
||||||
|
from poker.hand_ranking import HandType, HandRanking
|
||||||
|
from poker.card import ShortDeckRank, Card
|
||||||
|
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class ShortDeckHandType(Enum):
|
||||||
|
"""
|
||||||
|
标准牌型扑克与短牌型扑克的区别:
|
||||||
|
- 同花(Flush) > 满堂红(Full House)
|
||||||
|
- 其他牌型等级不变
|
||||||
|
"""
|
||||||
|
HIGH_CARD = (1, "High Card")
|
||||||
|
ONE_PAIR = (2, "One Pair")
|
||||||
|
TWO_PAIR = (3, "Two Pair")
|
||||||
|
THREE_OF_A_KIND = (4, "Three of a Kind")
|
||||||
|
STRAIGHT = (5, "Straight")
|
||||||
|
FULL_HOUSE = (6, "Full House")
|
||||||
|
FLUSH = (7, "Flush")
|
||||||
|
FOUR_OF_A_KIND = (8, "Four of a Kind")
|
||||||
|
STRAIGHT_FLUSH = (9, "Straight Flush")
|
||||||
|
ROYAL_FLUSH = (10, "Royal Flush")
|
||||||
|
|
||||||
|
def __new__(cls, strength, type_name):
|
||||||
|
obj = object.__new__(cls)
|
||||||
|
obj._value_ = strength
|
||||||
|
obj.strength = strength
|
||||||
|
obj.type_name = type_name
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.type_name
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, ShortDeckHandType):
|
||||||
|
return self.strength == other.strength
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if isinstance(other, ShortDeckHandType):
|
||||||
|
return self.strength < other.strength
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
|
||||||
|
class ShortDeckHandRanking(HandRanking):
|
||||||
|
def __init__(self, hand_type: ShortDeckHandType, key_ranks: List[ShortDeckRank], cards: List[Card]):
|
||||||
|
super().__init__(hand_type, key_ranks, cards)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return super().__str__()
|
||||||
28
shortdeck_main.py
Normal file
28
shortdeck_main.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Short Deck Poker Hand Evaluation Script
|
||||||
|
|
||||||
|
Usage: python shortdeck_main.py "6sKs AhAdAc6s7s"
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from shortdeck import ShortDeckHandEvaluator
|
||||||
|
from poker.hand_ranking import HandType
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("输入: python shortdeck_main.py \"AsKs QhQdQc6s7s\"")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
hand_input = sys.argv[1]
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_from_input(hand_input)
|
||||||
|
print(str(result))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
60
task4_readme.md
Normal file
60
task4_readme.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Task 4
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
从Task1标准扑克扩展到短牌扑克
|
||||||
|
|
||||||
|
## 支持的手牌类型
|
||||||
|
|
||||||
|
1. **皇家同花顺** (Royal Flush) - A♠K♠Q♠J♠T♠
|
||||||
|
2. **同花顺** (Straight Flush) - 连续的五张同花色牌
|
||||||
|
3. **四条** (Four of a Kind) - 四张相同点数的牌
|
||||||
|
4. **同花** (Flush) - 五张同花色的牌
|
||||||
|
5. **满堂红** (Full House) - 三条加一对
|
||||||
|
6. **顺子** (Straight) - 五张连续点数的牌(包括A-2-3-4-5轮子)
|
||||||
|
7. **三条** (Three of a Kind) - 三张相同点数的牌
|
||||||
|
8. **两对** (Two Pair) - 两个对子
|
||||||
|
9. **一对** (One Pair) - 一个对子
|
||||||
|
10. **高牌** (High Card) - 没有组合的单张高牌
|
||||||
|
|
||||||
|
### 环境要求
|
||||||
|
|
||||||
|
**牌组特性**:
|
||||||
|
- 移除2、3、4、5,保留6-A(36张牌)
|
||||||
|
- 支持A-6-7-8-9特殊顺子
|
||||||
|
|
||||||
|
**手牌排名调整**:
|
||||||
|
```
|
||||||
|
短牌vs标准扑克的关键差异:
|
||||||
|
- Flush (同花) > Full House (满堂红)
|
||||||
|
- 其他排名保持一致
|
||||||
|
```
|
||||||
|
|
||||||
|
**可能的顺子组合**:
|
||||||
|
1. A-6-7-8-9
|
||||||
|
2. 6-7-8-9-T
|
||||||
|
3. 7-8-9-T-J
|
||||||
|
4. 8-9-T-J-Q
|
||||||
|
5. 9-T-J-Q-K
|
||||||
|
6. T-J-Q-K-A
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
|
||||||
|
**单元测试**:
|
||||||
|
- Card类:创建、解析、验证、比较
|
||||||
|
- HandEvaluator:各种手牌类型识别
|
||||||
|
- HandRanking:排名比较和字符串表示
|
||||||
|
|
||||||
|
**关键验证用例**:
|
||||||
|
```bash
|
||||||
|
# 标准扑克 - Royal Flush
|
||||||
|
python main.py "AsKs QsJsTsAdAc"
|
||||||
|
# 输出: Royal Flush
|
||||||
|
|
||||||
|
# 短牌扑克 - 关键规则验证
|
||||||
|
python shortdeck_main.py "AsKs QsJs9s" # Flush
|
||||||
|
python shortdeck_main.py "AsAh AcKsKh" # Full House
|
||||||
|
|
||||||
|
# 短牌扑克
|
||||||
|
python shortdeck_main.py "As9h 8d7c6s9dKs"
|
||||||
|
```
|
||||||
3
tests/shortdeck/__init__.py
Normal file
3
tests/shortdeck/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Test module for Short Deck Poker implementation
|
||||||
|
"""
|
||||||
203
tests/shortdeck/test_card.py
Normal file
203
tests/shortdeck/test_card.py
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
"""
|
||||||
|
Tests for Short Deck Card implementation
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from poker.card import Card, ShortDeckRank, Suit # 使用统一的Card类
|
||||||
|
|
||||||
|
|
||||||
|
class TestShortDeckRank:
|
||||||
|
"""Test cases for ShortDeckRank enum"""
|
||||||
|
|
||||||
|
def test_rank_values(self):
|
||||||
|
"""Test that short deck ranks have correct values"""
|
||||||
|
assert ShortDeckRank.SIX.numeric_value == 6
|
||||||
|
assert ShortDeckRank.SEVEN.numeric_value == 7
|
||||||
|
assert ShortDeckRank.EIGHT.numeric_value == 8
|
||||||
|
assert ShortDeckRank.NINE.numeric_value == 9
|
||||||
|
assert ShortDeckRank.TEN.numeric_value == 10
|
||||||
|
assert ShortDeckRank.JACK.numeric_value == 11
|
||||||
|
assert ShortDeckRank.QUEEN.numeric_value == 12
|
||||||
|
assert ShortDeckRank.KING.numeric_value == 13
|
||||||
|
assert ShortDeckRank.ACE.numeric_value == 14
|
||||||
|
|
||||||
|
def test_rank_symbols(self):
|
||||||
|
"""Test that short deck ranks have correct symbols"""
|
||||||
|
assert ShortDeckRank.SIX.symbol == '6'
|
||||||
|
assert ShortDeckRank.SEVEN.symbol == '7'
|
||||||
|
assert ShortDeckRank.EIGHT.symbol == '8'
|
||||||
|
assert ShortDeckRank.NINE.symbol == '9'
|
||||||
|
assert ShortDeckRank.TEN.symbol == 'T'
|
||||||
|
assert ShortDeckRank.JACK.symbol == 'J'
|
||||||
|
assert ShortDeckRank.QUEEN.symbol == 'Q'
|
||||||
|
assert ShortDeckRank.KING.symbol == 'K'
|
||||||
|
assert ShortDeckRank.ACE.symbol == 'A'
|
||||||
|
|
||||||
|
def test_rank_string_representation(self):
|
||||||
|
"""Test string representation of ranks"""
|
||||||
|
assert str(ShortDeckRank.SIX) == '6'
|
||||||
|
assert str(ShortDeckRank.ACE) == 'A'
|
||||||
|
|
||||||
|
def test_rank_comparison(self):
|
||||||
|
"""Test rank comparison"""
|
||||||
|
assert ShortDeckRank.SIX < ShortDeckRank.SEVEN
|
||||||
|
assert ShortDeckRank.KING < ShortDeckRank.ACE
|
||||||
|
assert ShortDeckRank.ACE > ShortDeckRank.KING
|
||||||
|
assert ShortDeckRank.SIX <= ShortDeckRank.SIX
|
||||||
|
assert ShortDeckRank.ACE >= ShortDeckRank.ACE
|
||||||
|
|
||||||
|
def test_rank_equality(self):
|
||||||
|
"""Test rank equality"""
|
||||||
|
assert ShortDeckRank.SIX == ShortDeckRank.SIX
|
||||||
|
assert ShortDeckRank.ACE != ShortDeckRank.KING
|
||||||
|
|
||||||
|
def test_rank_hash(self):
|
||||||
|
"""Test rank hash functionality"""
|
||||||
|
rank_set = {ShortDeckRank.SIX, ShortDeckRank.SIX, ShortDeckRank.SEVEN}
|
||||||
|
assert len(rank_set) == 2 # No duplicate SIX
|
||||||
|
|
||||||
|
def test_no_low_ranks(self):
|
||||||
|
"""Test that ranks 2,3,4,5 are not present in short deck"""
|
||||||
|
rank_values = [rank.numeric_value for rank in ShortDeckRank]
|
||||||
|
assert 2 not in rank_values
|
||||||
|
assert 3 not in rank_values
|
||||||
|
assert 4 not in rank_values
|
||||||
|
assert 5 not in rank_values
|
||||||
|
assert len(list(ShortDeckRank)) == 9 # Only 9 ranks
|
||||||
|
|
||||||
|
|
||||||
|
class TestSuit:
|
||||||
|
"""Test cases for Suit enum (same as regular poker)"""
|
||||||
|
|
||||||
|
def test_suit_values(self):
|
||||||
|
"""Test suit values"""
|
||||||
|
assert Suit.SPADES.value == 's'
|
||||||
|
assert Suit.HEARTS.value == 'h'
|
||||||
|
assert Suit.DIAMONDS.value == 'd'
|
||||||
|
assert Suit.CLUBS.value == 'c'
|
||||||
|
|
||||||
|
def test_suit_string_representation(self):
|
||||||
|
"""Test string representation of suits"""
|
||||||
|
assert str(Suit.SPADES) == 's'
|
||||||
|
assert str(Suit.HEARTS) == 'h'
|
||||||
|
|
||||||
|
|
||||||
|
class TestCard:
|
||||||
|
"""Test cases for Card class"""
|
||||||
|
|
||||||
|
def test_card_creation(self):
|
||||||
|
"""Test card creation"""
|
||||||
|
card = Card(ShortDeckRank.ACE, Suit.SPADES)
|
||||||
|
assert card.rank == ShortDeckRank.ACE
|
||||||
|
assert card.suit == Suit.SPADES
|
||||||
|
|
||||||
|
def test_card_string_representation(self):
|
||||||
|
"""Test card string representation"""
|
||||||
|
card = Card(ShortDeckRank.ACE, Suit.SPADES)
|
||||||
|
assert str(card) == "As"
|
||||||
|
|
||||||
|
card2 = Card(ShortDeckRank.KING, Suit.HEARTS)
|
||||||
|
assert str(card2) == "Kh"
|
||||||
|
|
||||||
|
def test_card_comparison(self):
|
||||||
|
"""Test card comparison"""
|
||||||
|
ace_spades = Card(ShortDeckRank.ACE, Suit.SPADES)
|
||||||
|
king_spades = Card(ShortDeckRank.KING, Suit.SPADES)
|
||||||
|
ace_hearts = Card(ShortDeckRank.ACE, Suit.HEARTS)
|
||||||
|
|
||||||
|
assert king_spades < ace_spades
|
||||||
|
assert ace_hearts < ace_spades # Same rank, suit comparison
|
||||||
|
assert ace_spades > king_spades
|
||||||
|
|
||||||
|
def test_card_equality(self):
|
||||||
|
"""Test card equality"""
|
||||||
|
card1 = Card(ShortDeckRank.ACE, Suit.SPADES)
|
||||||
|
card2 = Card(ShortDeckRank.ACE, Suit.SPADES)
|
||||||
|
card3 = Card(ShortDeckRank.KING, Suit.SPADES)
|
||||||
|
|
||||||
|
assert card1 == card2
|
||||||
|
assert card1 != card3
|
||||||
|
|
||||||
|
def test_create_card_valid(self):
|
||||||
|
"""Test creating cards from valid strings"""
|
||||||
|
# 使用短牌模式创建卡片
|
||||||
|
card = Card.create_card("As", is_short_deck=True)
|
||||||
|
assert card.rank == ShortDeckRank.ACE
|
||||||
|
assert card.suit == Suit.SPADES
|
||||||
|
|
||||||
|
card2 = Card.create_card("6h", is_short_deck=True)
|
||||||
|
assert card2.rank == ShortDeckRank.SIX
|
||||||
|
assert card2.suit == Suit.HEARTS
|
||||||
|
|
||||||
|
def test_create_card_invalid_length(self):
|
||||||
|
"""Test creating card with invalid string length"""
|
||||||
|
with pytest.raises(ValueError, match="Invalid card string"):
|
||||||
|
Card.create_card("A")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Invalid card string"):
|
||||||
|
Card.create_card("Ash")
|
||||||
|
|
||||||
|
def test_create_card_invalid_rank(self):
|
||||||
|
"""Test creating card with invalid rank (should reject 2,3,4,5)"""
|
||||||
|
with pytest.raises(ValueError, match="Invalid rank"):
|
||||||
|
Card.create_card("2s", is_short_deck=True)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Invalid rank"):
|
||||||
|
Card.create_card("5h", is_short_deck=True)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Invalid rank"):
|
||||||
|
Card.create_card("Xs", is_short_deck=True)
|
||||||
|
|
||||||
|
def test_create_card_invalid_suit(self):
|
||||||
|
"""Test creating card with invalid suit"""
|
||||||
|
with pytest.raises(ValueError, match="Invalid suit"):
|
||||||
|
Card.create_card("Ax")
|
||||||
|
|
||||||
|
def test_parse_cards_valid(self):
|
||||||
|
"""Test parsing multiple valid cards"""
|
||||||
|
cards = Card.parse_cards("AsKs AhAdAc6s7s")
|
||||||
|
assert len(cards) == 7
|
||||||
|
assert str(cards[0]) == "As"
|
||||||
|
assert str(cards[1]) == "Ks"
|
||||||
|
assert str(cards[6]) == "7s"
|
||||||
|
|
||||||
|
def test_parse_cards_empty(self):
|
||||||
|
"""Test parsing empty string"""
|
||||||
|
cards = Card.parse_cards("")
|
||||||
|
assert len(cards) == 0
|
||||||
|
|
||||||
|
cards = Card.parse_cards(" ")
|
||||||
|
assert len(cards) == 0
|
||||||
|
|
||||||
|
def test_parse_cards_invalid_format(self):
|
||||||
|
"""Test parsing cards with invalid format"""
|
||||||
|
with pytest.raises(ValueError, match="Invalid card format"):
|
||||||
|
Card.parse_cards("AsK") # Odd length
|
||||||
|
|
||||||
|
def test_parse_cards_with_invalid_cards(self):
|
||||||
|
"""Test parsing string containing invalid cards"""
|
||||||
|
with pytest.raises(ValueError, match="Invalid rank"):
|
||||||
|
Card.parse_cards("As 2s Kh", is_short_deck=True) # Contains '2s'
|
||||||
|
|
||||||
|
def test_get_all_cards(self):
|
||||||
|
"""Test getting all 36 cards in short deck"""
|
||||||
|
all_cards = []
|
||||||
|
for rank in ShortDeckRank:
|
||||||
|
for suit in Suit:
|
||||||
|
all_cards.append(Card(rank, suit))
|
||||||
|
|
||||||
|
assert len(all_cards) == 36 # 9 ranks * 4 suits
|
||||||
|
|
||||||
|
# Check that all combinations are present
|
||||||
|
ranks_found = set()
|
||||||
|
suits_found = set()
|
||||||
|
for card in all_cards:
|
||||||
|
ranks_found.add(card.rank)
|
||||||
|
suits_found.add(card.suit)
|
||||||
|
|
||||||
|
assert len(ranks_found) == 9 # All 9 short deck ranks
|
||||||
|
assert len(suits_found) == 4 # All 4 suits
|
||||||
|
|
||||||
|
# Verify no low cards (2,3,4,5) are present
|
||||||
|
for card in all_cards:
|
||||||
|
assert card.rank.numeric_value >= 6
|
||||||
326
tests/shortdeck/test_hand_evaluator.py
Normal file
326
tests/shortdeck/test_hand_evaluator.py
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
"""
|
||||||
|
Tests for Short Deck Hand Evaluator
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from poker.card import Card, ShortDeckRank, Suit # 使用统一的Card类
|
||||||
|
from shortdeck.hand_evaluator import ShortDeckHandEvaluator
|
||||||
|
from shortdeck.hand_ranking import ShortDeckHandType, ShortDeckHandRanking
|
||||||
|
|
||||||
|
|
||||||
|
class TestShortDeckHandEvaluator:
|
||||||
|
"""Test cases for ShortDeckHandEvaluator class"""
|
||||||
|
|
||||||
|
def test_evaluate_hand_insufficient_cards(self):
|
||||||
|
"""Test evaluation with less than 5 cards"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.KING, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.QUEEN, Suit.DIAMONDS),
|
||||||
|
Card(ShortDeckRank.JACK, Suit.CLUBS)
|
||||||
|
]
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Need at least 5 cards"):
|
||||||
|
ShortDeckHandEvaluator.evaluate_hand(cards)
|
||||||
|
|
||||||
|
def test_evaluate_hand_exactly_five_cards(self):
|
||||||
|
"""Test evaluation with exactly 5 cards"""
|
||||||
|
# Royal flush
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.KING, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.QUEEN, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.JACK, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.TEN, Suit.SPADES)
|
||||||
|
]
|
||||||
|
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_hand(cards)
|
||||||
|
assert result.hand_type == ShortDeckHandType.ROYAL_FLUSH
|
||||||
|
|
||||||
|
def test_evaluate_hand_seven_cards(self):
|
||||||
|
"""Test evaluation with 7 cards - should find best 5"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.DIAMONDS),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.CLUBS), # Four Aces
|
||||||
|
Card(ShortDeckRank.KING, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.SIX, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.SEVEN, Suit.DIAMONDS)
|
||||||
|
]
|
||||||
|
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_hand(cards)
|
||||||
|
assert result.hand_type == ShortDeckHandType.FOUR_OF_A_KIND
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.ACE
|
||||||
|
|
||||||
|
def test_evaluate_five_cards_wrong_count(self):
|
||||||
|
"""Test evaluate5Cards with wrong number of cards"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.KING, Suit.HEARTS)
|
||||||
|
]
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Must provide exactly 5 cards"):
|
||||||
|
ShortDeckHandEvaluator.evaluate_5_cards(cards)
|
||||||
|
|
||||||
|
def test_royal_flush(self):
|
||||||
|
"""Test royal flush detection"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.KING, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.QUEEN, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.JACK, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.TEN, Suit.SPADES)
|
||||||
|
]
|
||||||
|
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_5_cards(cards)
|
||||||
|
assert result.hand_type == ShortDeckHandType.ROYAL_FLUSH
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.ACE
|
||||||
|
|
||||||
|
def test_straight_flush(self):
|
||||||
|
"""Test straight flush detection"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.KING, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.QUEEN, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.JACK, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.TEN, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.NINE, Suit.HEARTS)
|
||||||
|
]
|
||||||
|
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_5_cards(cards)
|
||||||
|
assert result.hand_type == ShortDeckHandType.STRAIGHT_FLUSH
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.KING
|
||||||
|
|
||||||
|
def test_four_of_a_kind(self):
|
||||||
|
"""Test four of a kind detection"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.DIAMONDS),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.CLUBS),
|
||||||
|
Card(ShortDeckRank.KING, Suit.SPADES)
|
||||||
|
]
|
||||||
|
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_5_cards(cards)
|
||||||
|
assert result.hand_type == ShortDeckHandType.FOUR_OF_A_KIND
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.ACE
|
||||||
|
assert result.key_ranks[1] == ShortDeckRank.KING # Kicker
|
||||||
|
|
||||||
|
def test_flush_beats_full_house(self):
|
||||||
|
"""Test that flush beats full house in short deck"""
|
||||||
|
# Flush
|
||||||
|
flush_cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.KING, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.QUEEN, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.JACK, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.NINE, Suit.SPADES)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Full house
|
||||||
|
full_house_cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.DIAMONDS),
|
||||||
|
Card(ShortDeckRank.KING, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.KING, Suit.HEARTS)
|
||||||
|
]
|
||||||
|
|
||||||
|
flush_result = ShortDeckHandEvaluator.evaluate_5_cards(flush_cards)
|
||||||
|
full_house_result = ShortDeckHandEvaluator.evaluate_5_cards(full_house_cards)
|
||||||
|
|
||||||
|
assert flush_result.hand_type == ShortDeckHandType.FLUSH
|
||||||
|
assert full_house_result.hand_type == ShortDeckHandType.FULL_HOUSE
|
||||||
|
assert flush_result > full_house_result # Flush beats Full House in Short Deck!
|
||||||
|
|
||||||
|
def test_full_house(self):
|
||||||
|
"""Test full house detection"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.DIAMONDS),
|
||||||
|
Card(ShortDeckRank.KING, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.KING, Suit.HEARTS)
|
||||||
|
]
|
||||||
|
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_5_cards(cards)
|
||||||
|
assert result.hand_type == ShortDeckHandType.FULL_HOUSE
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.ACE # Trips
|
||||||
|
assert result.key_ranks[1] == ShortDeckRank.KING # Pair
|
||||||
|
|
||||||
|
def test_flush(self):
|
||||||
|
"""Test flush detection"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.KING, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.QUEEN, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.JACK, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.NINE, Suit.SPADES) # Not a straight
|
||||||
|
]
|
||||||
|
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_5_cards(cards)
|
||||||
|
assert result.hand_type == ShortDeckHandType.FLUSH
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.ACE
|
||||||
|
|
||||||
|
def test_straight_regular(self):
|
||||||
|
"""Test regular straight detection"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.KING, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.QUEEN, Suit.DIAMONDS),
|
||||||
|
Card(ShortDeckRank.JACK, Suit.CLUBS),
|
||||||
|
Card(ShortDeckRank.TEN, Suit.SPADES)
|
||||||
|
]
|
||||||
|
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_5_cards(cards)
|
||||||
|
assert result.hand_type == ShortDeckHandType.STRAIGHT
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.ACE
|
||||||
|
|
||||||
|
def test_straight_low_wheel(self):
|
||||||
|
"""Test short deck wheel straight: A-6-7-8-9"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.NINE, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.EIGHT, Suit.DIAMONDS),
|
||||||
|
Card(ShortDeckRank.SEVEN, Suit.CLUBS),
|
||||||
|
Card(ShortDeckRank.SIX, Suit.SPADES)
|
||||||
|
]
|
||||||
|
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_5_cards(cards)
|
||||||
|
assert result.hand_type == ShortDeckHandType.STRAIGHT
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.NINE # 9-high straight in short deck wheel
|
||||||
|
|
||||||
|
def test_three_of_a_kind(self):
|
||||||
|
"""Test three of a kind detection"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.DIAMONDS),
|
||||||
|
Card(ShortDeckRank.KING, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.QUEEN, Suit.HEARTS)
|
||||||
|
]
|
||||||
|
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_5_cards(cards)
|
||||||
|
assert result.hand_type == ShortDeckHandType.THREE_OF_A_KIND
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.ACE
|
||||||
|
assert ShortDeckRank.KING in result.key_ranks
|
||||||
|
assert ShortDeckRank.QUEEN in result.key_ranks
|
||||||
|
|
||||||
|
def test_two_pair(self):
|
||||||
|
"""Test two pair detection"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.KING, Suit.DIAMONDS),
|
||||||
|
Card(ShortDeckRank.KING, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.QUEEN, Suit.HEARTS)
|
||||||
|
]
|
||||||
|
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_5_cards(cards)
|
||||||
|
assert result.hand_type == ShortDeckHandType.TWO_PAIR
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.ACE # Higher pair first
|
||||||
|
assert result.key_ranks[1] == ShortDeckRank.KING # Lower pair second
|
||||||
|
assert result.key_ranks[2] == ShortDeckRank.QUEEN # Kicker
|
||||||
|
|
||||||
|
def test_one_pair(self):
|
||||||
|
"""Test one pair detection"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.KING, Suit.DIAMONDS),
|
||||||
|
Card(ShortDeckRank.QUEEN, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.JACK, Suit.HEARTS)
|
||||||
|
]
|
||||||
|
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_5_cards(cards)
|
||||||
|
assert result.hand_type == ShortDeckHandType.ONE_PAIR
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.ACE
|
||||||
|
assert ShortDeckRank.KING in result.key_ranks
|
||||||
|
assert ShortDeckRank.QUEEN in result.key_ranks
|
||||||
|
assert ShortDeckRank.JACK in result.key_ranks
|
||||||
|
|
||||||
|
def test_high_card(self):
|
||||||
|
"""Test high card detection"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.KING, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.QUEEN, Suit.DIAMONDS),
|
||||||
|
Card(ShortDeckRank.JACK, Suit.CLUBS),
|
||||||
|
Card(ShortDeckRank.NINE, Suit.HEARTS) # Not a straight (missing 10)
|
||||||
|
]
|
||||||
|
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_5_cards(cards)
|
||||||
|
assert result.hand_type == ShortDeckHandType.HIGH_CARD
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.ACE
|
||||||
|
|
||||||
|
def test_is_straight_regular_straights(self):
|
||||||
|
"""Test isStraight with regular straights"""
|
||||||
|
# T-J-Q-K-A
|
||||||
|
ranks1 = [ShortDeckRank.ACE, ShortDeckRank.KING, ShortDeckRank.QUEEN, ShortDeckRank.JACK, ShortDeckRank.TEN]
|
||||||
|
is_straight1, high1 = ShortDeckHandEvaluator.is_straight(ranks1)
|
||||||
|
assert is_straight1 == True
|
||||||
|
assert high1 == ShortDeckRank.ACE
|
||||||
|
|
||||||
|
# 6-7-8-9-T
|
||||||
|
ranks2 = [ShortDeckRank.TEN, ShortDeckRank.NINE, ShortDeckRank.EIGHT, ShortDeckRank.SEVEN, ShortDeckRank.SIX]
|
||||||
|
is_straight2, high2 = ShortDeckHandEvaluator.is_straight(ranks2)
|
||||||
|
assert is_straight2 == True
|
||||||
|
assert high2 == ShortDeckRank.TEN
|
||||||
|
|
||||||
|
def test_is_straight_wheel(self):
|
||||||
|
"""Test isStraight with short deck wheel: A-6-7-8-9"""
|
||||||
|
ranks = [ShortDeckRank.ACE, ShortDeckRank.NINE, ShortDeckRank.EIGHT, ShortDeckRank.SEVEN, ShortDeckRank.SIX]
|
||||||
|
is_straight, high = ShortDeckHandEvaluator.is_straight(ranks)
|
||||||
|
assert is_straight == True
|
||||||
|
assert high == ShortDeckRank.NINE # 9-high straight in short deck wheel
|
||||||
|
|
||||||
|
def test_is_straight_not_straight(self):
|
||||||
|
"""Test isStraight with non-straight"""
|
||||||
|
ranks = [ShortDeckRank.ACE, ShortDeckRank.KING, ShortDeckRank.QUEEN, ShortDeckRank.JACK, ShortDeckRank.NINE]
|
||||||
|
is_straight, high = ShortDeckHandEvaluator.is_straight(ranks)
|
||||||
|
assert is_straight == False
|
||||||
|
assert high is None
|
||||||
|
|
||||||
|
def test_is_straight_insufficient_cards(self):
|
||||||
|
"""Test isStraight with less than 5 unique ranks"""
|
||||||
|
ranks = [ShortDeckRank.ACE, ShortDeckRank.ACE, ShortDeckRank.KING, ShortDeckRank.KING] # Only 2 unique
|
||||||
|
is_straight, high = ShortDeckHandEvaluator.is_straight(ranks)
|
||||||
|
assert is_straight == False
|
||||||
|
assert high is None
|
||||||
|
|
||||||
|
def test_evaluate_from_input_valid(self):
|
||||||
|
"""Test evaluateFromInput with valid input"""
|
||||||
|
# Four of a kind
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_from_input("AsAhAdAc Ks")
|
||||||
|
assert result.hand_type == ShortDeckHandType.FOUR_OF_A_KIND
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.ACE
|
||||||
|
|
||||||
|
def test_evaluate_from_input_seven_cards(self):
|
||||||
|
"""Test evaluateFromInput with 7 cards"""
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_from_input("AsAhAdAc KsQsJs")
|
||||||
|
assert result.hand_type == ShortDeckHandType.FOUR_OF_A_KIND
|
||||||
|
assert result.key_ranks[0] == ShortDeckRank.ACE
|
||||||
|
|
||||||
|
def test_evaluate_from_input_invalid_cards(self):
|
||||||
|
"""Test evaluateFromInput with invalid cards (should reject 2,3,4,5)"""
|
||||||
|
with pytest.raises(ValueError, match="Invalid rank for short deck"):
|
||||||
|
ShortDeckHandEvaluator.evaluate_from_input("2s3s4s5s6s")
|
||||||
|
|
||||||
|
def test_all_possible_straights(self):
|
||||||
|
"""Test all possible straights in short deck"""
|
||||||
|
# All possible short deck straights:
|
||||||
|
# A-6-7-8-9, 6-7-8-9-T, 7-8-9-T-J, 8-9-T-J-Q, 9-T-J-Q-K, T-J-Q-K-A
|
||||||
|
|
||||||
|
straight_combinations = [
|
||||||
|
([ShortDeckRank.ACE, ShortDeckRank.NINE, ShortDeckRank.EIGHT, ShortDeckRank.SEVEN, ShortDeckRank.SIX], ShortDeckRank.NINE),
|
||||||
|
([ShortDeckRank.TEN, ShortDeckRank.NINE, ShortDeckRank.EIGHT, ShortDeckRank.SEVEN, ShortDeckRank.SIX], ShortDeckRank.TEN),
|
||||||
|
([ShortDeckRank.JACK, ShortDeckRank.TEN, ShortDeckRank.NINE, ShortDeckRank.EIGHT, ShortDeckRank.SEVEN], ShortDeckRank.JACK),
|
||||||
|
([ShortDeckRank.QUEEN, ShortDeckRank.JACK, ShortDeckRank.TEN, ShortDeckRank.NINE, ShortDeckRank.EIGHT], ShortDeckRank.QUEEN),
|
||||||
|
([ShortDeckRank.KING, ShortDeckRank.QUEEN, ShortDeckRank.JACK, ShortDeckRank.TEN, ShortDeckRank.NINE], ShortDeckRank.KING),
|
||||||
|
([ShortDeckRank.ACE, ShortDeckRank.KING, ShortDeckRank.QUEEN, ShortDeckRank.JACK, ShortDeckRank.TEN], ShortDeckRank.ACE),
|
||||||
|
]
|
||||||
|
|
||||||
|
for ranks, expected_high in straight_combinations:
|
||||||
|
is_straight, high = ShortDeckHandEvaluator.is_straight(ranks)
|
||||||
|
assert is_straight == True, f"Failed to detect straight: {[r.symbol for r in ranks]}"
|
||||||
|
assert high == expected_high, f"Wrong high card for {[r.symbol for r in ranks]}: expected {expected_high.symbol}, got {high.symbol if high else None}"
|
||||||
177
tests/shortdeck/test_hand_ranking.py
Normal file
177
tests/shortdeck/test_hand_ranking.py
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
"""
|
||||||
|
Tests for Short Deck HandRanking and HandType classes
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from poker.card import Card, ShortDeckRank, Suit # 使用统一的Card类
|
||||||
|
from shortdeck.hand_ranking import ShortDeckHandRanking, ShortDeckHandType
|
||||||
|
|
||||||
|
|
||||||
|
class TestShortDeckHandType:
|
||||||
|
"""Test cases for ShortDeckHandType enum"""
|
||||||
|
|
||||||
|
def test_hand_type_values(self):
|
||||||
|
"""Test hand type strength values - Flush beats Full House in Short Deck"""
|
||||||
|
assert ShortDeckHandType.HIGH_CARD.strength == 1
|
||||||
|
assert ShortDeckHandType.ONE_PAIR.strength == 2
|
||||||
|
assert ShortDeckHandType.TWO_PAIR.strength == 3
|
||||||
|
assert ShortDeckHandType.THREE_OF_A_KIND.strength == 4
|
||||||
|
assert ShortDeckHandType.STRAIGHT.strength == 5
|
||||||
|
assert ShortDeckHandType.FULL_HOUSE.strength == 6
|
||||||
|
assert ShortDeckHandType.FLUSH.strength == 7 # Higher than Full House!
|
||||||
|
assert ShortDeckHandType.FOUR_OF_A_KIND.strength == 8
|
||||||
|
assert ShortDeckHandType.STRAIGHT_FLUSH.strength == 9
|
||||||
|
assert ShortDeckHandType.ROYAL_FLUSH.strength == 10
|
||||||
|
|
||||||
|
def test_hand_type_names(self):
|
||||||
|
"""Test hand type names"""
|
||||||
|
assert ShortDeckHandType.HIGH_CARD.type_name == "High Card"
|
||||||
|
assert ShortDeckHandType.FOUR_OF_A_KIND.type_name == "Four of a Kind"
|
||||||
|
assert ShortDeckHandType.ROYAL_FLUSH.type_name == "Royal Flush"
|
||||||
|
assert ShortDeckHandType.FLUSH.type_name == "Flush"
|
||||||
|
assert ShortDeckHandType.FULL_HOUSE.type_name == "Full House"
|
||||||
|
|
||||||
|
def test_hand_type_string_representation(self):
|
||||||
|
"""Test string representation of hand types"""
|
||||||
|
assert str(ShortDeckHandType.HIGH_CARD) == "High Card"
|
||||||
|
assert str(ShortDeckHandType.ROYAL_FLUSH) == "Royal Flush"
|
||||||
|
|
||||||
|
def test_hand_type_comparison(self):
|
||||||
|
"""Test hand type comparison - KEY: Flush > Full House in Short Deck"""
|
||||||
|
assert ShortDeckHandType.HIGH_CARD < ShortDeckHandType.ONE_PAIR
|
||||||
|
assert ShortDeckHandType.FLUSH > ShortDeckHandType.FULL_HOUSE # Short Deck rule!
|
||||||
|
assert ShortDeckHandType.ROYAL_FLUSH >= ShortDeckHandType.STRAIGHT_FLUSH
|
||||||
|
assert ShortDeckHandType.TWO_PAIR <= ShortDeckHandType.THREE_OF_A_KIND
|
||||||
|
assert ShortDeckHandType.STRAIGHT < ShortDeckHandType.FULL_HOUSE
|
||||||
|
assert ShortDeckHandType.FULL_HOUSE < ShortDeckHandType.FLUSH
|
||||||
|
|
||||||
|
def test_hand_type_equality(self):
|
||||||
|
"""Test hand type equality"""
|
||||||
|
assert ShortDeckHandType.FLUSH == ShortDeckHandType.FLUSH
|
||||||
|
assert ShortDeckHandType.FLUSH != ShortDeckHandType.FULL_HOUSE
|
||||||
|
|
||||||
|
|
||||||
|
class TestShortDeckHandRanking:
|
||||||
|
"""Test cases for ShortDeckHandRanking class"""
|
||||||
|
|
||||||
|
def test_hand_ranking_creation(self):
|
||||||
|
"""Test hand ranking creation"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.SPADES),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.DIAMONDS),
|
||||||
|
Card(ShortDeckRank.ACE, Suit.CLUBS),
|
||||||
|
Card(ShortDeckRank.KING, Suit.SPADES)
|
||||||
|
]
|
||||||
|
ranking = ShortDeckHandRanking(ShortDeckHandType.FOUR_OF_A_KIND, [ShortDeckRank.ACE, ShortDeckRank.KING], cards)
|
||||||
|
|
||||||
|
assert ranking.hand_type == ShortDeckHandType.FOUR_OF_A_KIND
|
||||||
|
assert ranking.key_ranks == [ShortDeckRank.ACE, ShortDeckRank.KING]
|
||||||
|
assert ranking.cards == cards
|
||||||
|
|
||||||
|
def test_quad_string_representation(self):
|
||||||
|
"""Test string representation for four of a kind"""
|
||||||
|
cards = [Card(ShortDeckRank.ACE, Suit.SPADES)] * 5
|
||||||
|
ranking = ShortDeckHandRanking(ShortDeckHandType.FOUR_OF_A_KIND, [ShortDeckRank.ACE, ShortDeckRank.KING], cards)
|
||||||
|
assert str(ranking) == "Quad(A)"
|
||||||
|
|
||||||
|
def test_full_house_string_representation(self):
|
||||||
|
"""Test string representation for full house"""
|
||||||
|
cards = [Card(ShortDeckRank.ACE, Suit.SPADES)] * 5
|
||||||
|
ranking = ShortDeckHandRanking(ShortDeckHandType.FULL_HOUSE, [ShortDeckRank.ACE, ShortDeckRank.KING], cards)
|
||||||
|
assert str(ranking) == "Full House(A over K)"
|
||||||
|
|
||||||
|
def test_flush_string_representation(self):
|
||||||
|
"""Test string representation for flush"""
|
||||||
|
cards = [Card(ShortDeckRank.ACE, Suit.SPADES)] * 5
|
||||||
|
ranking = ShortDeckHandRanking(ShortDeckHandType.FLUSH, [ShortDeckRank.ACE], cards)
|
||||||
|
assert str(ranking) == "Flush(A high)"
|
||||||
|
|
||||||
|
def test_straight_string_representation(self):
|
||||||
|
"""Test string representation for straight"""
|
||||||
|
cards = [Card(ShortDeckRank.ACE, Suit.SPADES)] * 5
|
||||||
|
ranking = ShortDeckHandRanking(ShortDeckHandType.STRAIGHT, [ShortDeckRank.ACE], cards)
|
||||||
|
assert str(ranking) == "Straight(A high)"
|
||||||
|
|
||||||
|
def test_straight_flush_string_representation(self):
|
||||||
|
"""Test string representation for straight flush"""
|
||||||
|
cards = [Card(ShortDeckRank.KING, Suit.SPADES)] * 5
|
||||||
|
ranking = ShortDeckHandRanking(ShortDeckHandType.STRAIGHT_FLUSH, [ShortDeckRank.KING], cards)
|
||||||
|
assert str(ranking) == "Straight Flush(K high)"
|
||||||
|
|
||||||
|
def test_royal_flush_string_representation(self):
|
||||||
|
"""Test string representation for royal flush"""
|
||||||
|
cards = [Card(ShortDeckRank.ACE, Suit.SPADES)] * 5
|
||||||
|
|
||||||
|
# Direct royal flush
|
||||||
|
ranking1 = ShortDeckHandRanking(ShortDeckHandType.ROYAL_FLUSH, [ShortDeckRank.ACE], cards)
|
||||||
|
assert str(ranking1) == "Royal Flush"
|
||||||
|
|
||||||
|
# Straight flush with Ace high should also show as Royal Flush
|
||||||
|
ranking2 = ShortDeckHandRanking(ShortDeckHandType.STRAIGHT_FLUSH, [ShortDeckRank.ACE], cards)
|
||||||
|
assert str(ranking2) == "Royal Flush"
|
||||||
|
|
||||||
|
def test_three_of_a_kind_string_representation(self):
|
||||||
|
"""Test string representation for three of a kind"""
|
||||||
|
cards = [Card(ShortDeckRank.ACE, Suit.SPADES)] * 5
|
||||||
|
ranking = ShortDeckHandRanking(ShortDeckHandType.THREE_OF_A_KIND, [ShortDeckRank.ACE], cards)
|
||||||
|
assert str(ranking) == "Three of a Kind(A)"
|
||||||
|
|
||||||
|
def test_two_pair_string_representation(self):
|
||||||
|
"""Test string representation for two pair"""
|
||||||
|
cards = [Card(ShortDeckRank.ACE, Suit.SPADES)] * 5
|
||||||
|
ranking = ShortDeckHandRanking(ShortDeckHandType.TWO_PAIR, [ShortDeckRank.ACE, ShortDeckRank.KING], cards)
|
||||||
|
assert str(ranking) == "Two Pair(A and K)"
|
||||||
|
|
||||||
|
def test_one_pair_string_representation(self):
|
||||||
|
"""Test string representation for one pair"""
|
||||||
|
cards = [Card(ShortDeckRank.ACE, Suit.SPADES)] * 5
|
||||||
|
ranking = ShortDeckHandRanking(ShortDeckHandType.ONE_PAIR, [ShortDeckRank.ACE], cards)
|
||||||
|
assert str(ranking) == "Pair(A)"
|
||||||
|
|
||||||
|
def test_high_card_string_representation(self):
|
||||||
|
"""Test string representation for high card"""
|
||||||
|
cards = [Card(ShortDeckRank.ACE, Suit.SPADES)] * 5
|
||||||
|
ranking = ShortDeckHandRanking(ShortDeckHandType.HIGH_CARD, [ShortDeckRank.ACE], cards)
|
||||||
|
assert str(ranking) == "High Card(A)"
|
||||||
|
|
||||||
|
def test_hand_ranking_comparison(self):
|
||||||
|
"""Test hand ranking comparison"""
|
||||||
|
# Create some test cards
|
||||||
|
cards = [Card(ShortDeckRank.ACE, Suit.SPADES)] * 5
|
||||||
|
|
||||||
|
# Create different rankings
|
||||||
|
quad_aces = ShortDeckHandRanking(ShortDeckHandType.FOUR_OF_A_KIND, [ShortDeckRank.ACE], cards)
|
||||||
|
flush_ace = ShortDeckHandRanking(ShortDeckHandType.FLUSH, [ShortDeckRank.ACE], cards)
|
||||||
|
full_house = ShortDeckHandRanking(ShortDeckHandType.FULL_HOUSE, [ShortDeckRank.ACE, ShortDeckRank.KING], cards)
|
||||||
|
straight_ace = ShortDeckHandRanking(ShortDeckHandType.STRAIGHT, [ShortDeckRank.ACE], cards)
|
||||||
|
|
||||||
|
# Test Short Deck specific comparison: Flush > Full House
|
||||||
|
assert flush_ace > full_house
|
||||||
|
assert full_house < flush_ace
|
||||||
|
|
||||||
|
# Test other comparisons
|
||||||
|
assert quad_aces > flush_ace
|
||||||
|
assert straight_ace < full_house
|
||||||
|
|
||||||
|
def test_hand_ranking_equality(self):
|
||||||
|
"""Test hand ranking equality"""
|
||||||
|
cards1 = [Card(ShortDeckRank.ACE, Suit.SPADES)] * 5
|
||||||
|
cards2 = [Card(ShortDeckRank.KING, Suit.HEARTS)] * 5
|
||||||
|
|
||||||
|
ranking1 = ShortDeckHandRanking(ShortDeckHandType.FLUSH, [ShortDeckRank.ACE], cards1)
|
||||||
|
ranking2 = ShortDeckHandRanking(ShortDeckHandType.FLUSH, [ShortDeckRank.ACE], cards2)
|
||||||
|
ranking3 = ShortDeckHandRanking(ShortDeckHandType.FLUSH, [ShortDeckRank.KING], cards2)
|
||||||
|
|
||||||
|
assert ranking1 == ranking2 # Same hand type and key ranks
|
||||||
|
assert ranking1 != ranking3 # Different key ranks
|
||||||
|
|
||||||
|
def test_same_type_different_ranks(self):
|
||||||
|
"""Test comparison of same hand type with different key ranks"""
|
||||||
|
cards = [Card(ShortDeckRank.ACE, Suit.SPADES)] * 5
|
||||||
|
|
||||||
|
ace_high = ShortDeckHandRanking(ShortDeckHandType.HIGH_CARD, [ShortDeckRank.ACE], cards)
|
||||||
|
king_high = ShortDeckHandRanking(ShortDeckHandType.HIGH_CARD, [ShortDeckRank.KING], cards)
|
||||||
|
|
||||||
|
assert ace_high > king_high
|
||||||
|
assert king_high < ace_high
|
||||||
225
tests/shortdeck/test_integration.py
Normal file
225
tests/shortdeck/test_integration.py
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
"""
|
||||||
|
Integration tests for Short Deck Poker implementation
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from poker.card import Card, ShortDeckRank, Suit # 使用统一的Card类
|
||||||
|
from shortdeck import ShortDeckHandEvaluator, ShortDeckHandType, ShortDeckHandRanking
|
||||||
|
|
||||||
|
|
||||||
|
class TestShortDeckIntegration:
|
||||||
|
"""Integration tests for the complete short deck poker system"""
|
||||||
|
|
||||||
|
def test_shortdeck_example_cases(self):
|
||||||
|
"""Test various short deck example hands"""
|
||||||
|
|
||||||
|
# Test cases with expected results
|
||||||
|
test_cases = [
|
||||||
|
# Royal Flush
|
||||||
|
("AsKsQsJsTs", ShortDeckHandType.ROYAL_FLUSH, "Royal Flush"),
|
||||||
|
|
||||||
|
# Straight Flush (not royal)
|
||||||
|
("KsQsJsTs9s", ShortDeckHandType.STRAIGHT_FLUSH, "Straight Flush(K high)"),
|
||||||
|
|
||||||
|
# Four of a Kind
|
||||||
|
("AsAhAdAcKs", ShortDeckHandType.FOUR_OF_A_KIND, "Quad(A)"),
|
||||||
|
|
||||||
|
# Flush (beats Full House in Short Deck!)
|
||||||
|
("AsKsQsJs9s", ShortDeckHandType.FLUSH, "Flush(A high)"),
|
||||||
|
|
||||||
|
# Full House (weaker than Flush in Short Deck)
|
||||||
|
("AsAhAdKsKh", ShortDeckHandType.FULL_HOUSE, "Full House(A over K)"),
|
||||||
|
|
||||||
|
# Straight - regular
|
||||||
|
("AsKsQhJdTs", ShortDeckHandType.STRAIGHT, "Straight(A high)"),
|
||||||
|
|
||||||
|
# Straight - short deck wheel (A-6-7-8-9)
|
||||||
|
("As9h8d7c6s", ShortDeckHandType.STRAIGHT, "Straight(9 high)"),
|
||||||
|
|
||||||
|
# Three of a Kind
|
||||||
|
("AsAhAdKsQs", ShortDeckHandType.THREE_OF_A_KIND, "Three of a Kind(A)"),
|
||||||
|
|
||||||
|
# Two Pair
|
||||||
|
("AsAhKdKsQs", ShortDeckHandType.TWO_PAIR, "Two Pair(A and K)"),
|
||||||
|
|
||||||
|
# One Pair
|
||||||
|
("AsAhKdQsJs", ShortDeckHandType.ONE_PAIR, "Pair(A)"),
|
||||||
|
|
||||||
|
# High Card
|
||||||
|
("AsKhQdJs9s", ShortDeckHandType.HIGH_CARD, "High Card(A)")
|
||||||
|
]
|
||||||
|
|
||||||
|
for hand_str, expected_type, expected_str in test_cases:
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_from_input(hand_str)
|
||||||
|
assert result.hand_type == expected_type, f"Failed for {hand_str}: expected {expected_type}, got {result.hand_type}"
|
||||||
|
assert str(result) == expected_str, f"Failed string for {hand_str}: expected '{expected_str}', got '{str(result)}'"
|
||||||
|
|
||||||
|
def test_shortdeck_seven_card_hands(self):
|
||||||
|
"""Test evaluation with 7 cards (like Texas Hold'em)"""
|
||||||
|
|
||||||
|
# Should find the best 5-card hand from 7 cards
|
||||||
|
test_cases = [
|
||||||
|
# Four of a kind + extra cards
|
||||||
|
("AsAhAdAcKs Qs 6h", ShortDeckHandType.FOUR_OF_A_KIND, "Quad(A)"),
|
||||||
|
|
||||||
|
# Full house from 7 cards
|
||||||
|
("AsAhAdKsKh Qc 7d", ShortDeckHandType.FULL_HOUSE, "Full House(A over K)"),
|
||||||
|
|
||||||
|
# Flush with extra cards
|
||||||
|
("AsKsQsJsTs 6h 7c", ShortDeckHandType.ROYAL_FLUSH, "Royal Flush"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for hand_str, expected_type, expected_str in test_cases:
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_from_input(hand_str)
|
||||||
|
assert result.hand_type == expected_type
|
||||||
|
assert str(result) == expected_str
|
||||||
|
|
||||||
|
def test_shortdeck_flush_vs_full_house(self):
|
||||||
|
"""Test the key Short Deck rule: Flush beats Full House"""
|
||||||
|
|
||||||
|
# Create flush
|
||||||
|
flush_hand = "AsKsQsJs9s" # A-high flush
|
||||||
|
flush_result = ShortDeckHandEvaluator.evaluate_from_input(flush_hand)
|
||||||
|
|
||||||
|
# Create full house
|
||||||
|
full_house_hand = "AsAhAdKsKh" # Aces full of Kings
|
||||||
|
full_house_result = ShortDeckHandEvaluator.evaluate_from_input(full_house_hand)
|
||||||
|
|
||||||
|
# Flush should beat Full House
|
||||||
|
assert flush_result > full_house_result
|
||||||
|
assert flush_result.hand_type == ShortDeckHandType.FLUSH
|
||||||
|
assert full_house_result.hand_type == ShortDeckHandType.FULL_HOUSE
|
||||||
|
|
||||||
|
def test_shortdeck_wheel_straight(self):
|
||||||
|
"""Test Short Deck wheel straight: A-6-7-8-9"""
|
||||||
|
|
||||||
|
# Wheel straight
|
||||||
|
wheel_hand = "As9h8d7c6s"
|
||||||
|
wheel_result = ShortDeckHandEvaluator.evaluate_from_input(wheel_hand)
|
||||||
|
|
||||||
|
# Regular straight
|
||||||
|
regular_hand = "TsJhQdKcAs"
|
||||||
|
regular_result = ShortDeckHandEvaluator.evaluate_from_input(regular_hand)
|
||||||
|
|
||||||
|
assert wheel_result.hand_type == ShortDeckHandType.STRAIGHT
|
||||||
|
assert regular_result.hand_type == ShortDeckHandType.STRAIGHT
|
||||||
|
|
||||||
|
# A-high straight should beat 9-high wheel
|
||||||
|
assert regular_result > wheel_result
|
||||||
|
|
||||||
|
# Check string representations
|
||||||
|
assert str(wheel_result) == "Straight(9 high)"
|
||||||
|
assert str(regular_result) == "Straight(A high)"
|
||||||
|
|
||||||
|
def test_no_invalid_short_deck_cards(self):
|
||||||
|
"""Test that cards 2, 3, 4, 5 are properly rejected"""
|
||||||
|
|
||||||
|
invalid_hands = [
|
||||||
|
"2s3s4s5s6s", # Contains 2, 3, 4, 5
|
||||||
|
"AsKs2h3h4h", # Mixed valid and invalid
|
||||||
|
"5sAsKsQsJs" # Contains 5
|
||||||
|
]
|
||||||
|
|
||||||
|
for invalid_hand in invalid_hands:
|
||||||
|
with pytest.raises(ValueError, match="Invalid rank for short deck"):
|
||||||
|
ShortDeckHandEvaluator.evaluate_from_input(invalid_hand)
|
||||||
|
|
||||||
|
def test_all_short_deck_straights(self):
|
||||||
|
"""Test all 6 possible straights in Short Deck"""
|
||||||
|
|
||||||
|
straight_tests = [
|
||||||
|
("As9h8d7c6s", "Straight(9 high)"), # A-6-7-8-9 (wheel)
|
||||||
|
("TsJhQdKcAs", "Straight(A high)"), # T-J-Q-K-A (broadway)
|
||||||
|
("Ts9h8d7c6s", "Straight(T high)"), # 6-7-8-9-T
|
||||||
|
("JsTh9d8c7s", "Straight(J high)"), # 7-8-9-T-J
|
||||||
|
("QsJhTd9c8s", "Straight(Q high)"), # 8-9-T-J-Q
|
||||||
|
("KsQhJdTc9s", "Straight(K high)"), # 9-T-J-Q-K
|
||||||
|
]
|
||||||
|
|
||||||
|
for hand_str, expected_str in straight_tests:
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_from_input(hand_str)
|
||||||
|
assert result.hand_type == ShortDeckHandType.STRAIGHT
|
||||||
|
assert str(result) == expected_str
|
||||||
|
|
||||||
|
def test_shortdeck_card_count(self):
|
||||||
|
"""Test that Short Deck has exactly 36 cards"""
|
||||||
|
# 使用Card类的ShortDeck模式
|
||||||
|
all_cards = []
|
||||||
|
for rank in ShortDeckRank:
|
||||||
|
for suit in Suit:
|
||||||
|
all_cards.append(Card(rank, suit))
|
||||||
|
|
||||||
|
assert len(all_cards) == 36 # 9 ranks * 4 suits
|
||||||
|
|
||||||
|
# Verify no duplicates
|
||||||
|
card_strings = [str(card) for card in all_cards]
|
||||||
|
assert len(set(card_strings)) == 36
|
||||||
|
|
||||||
|
# Verify all ranks 6-A are present
|
||||||
|
ranks_present = set(card.rank for card in all_cards)
|
||||||
|
expected_ranks = set(ShortDeckRank)
|
||||||
|
assert ranks_present == expected_ranks
|
||||||
|
|
||||||
|
# Verify no low cards (2,3,4,5)
|
||||||
|
for card in all_cards:
|
||||||
|
assert card.rank.numeric_value >= 6
|
||||||
|
|
||||||
|
def test_shortdeck_hand_rankings_order(self):
|
||||||
|
"""Test the complete hand ranking order for Short Deck"""
|
||||||
|
|
||||||
|
# Create hands of different types
|
||||||
|
hands = [
|
||||||
|
("AsKhQdJs9c", ShortDeckHandType.HIGH_CARD), # High Card
|
||||||
|
("AsAhKdQsJc", ShortDeckHandType.ONE_PAIR), # One Pair
|
||||||
|
("AsAhKdKsQc", ShortDeckHandType.TWO_PAIR), # Two Pair
|
||||||
|
("AsAhAdKsQc", ShortDeckHandType.THREE_OF_A_KIND), # Three of a Kind
|
||||||
|
("AsKhQdJsTc", ShortDeckHandType.STRAIGHT), # Straight
|
||||||
|
("AsAhAdKsKc", ShortDeckHandType.FULL_HOUSE), # Full House
|
||||||
|
("AsKsQsJs9s", ShortDeckHandType.FLUSH), # Flush (beats Full House!)
|
||||||
|
("AsAhAdAcKs", ShortDeckHandType.FOUR_OF_A_KIND), # Four of a Kind
|
||||||
|
("9s8s7s6sTs", ShortDeckHandType.STRAIGHT_FLUSH), # Straight Flush
|
||||||
|
("AsKsQsJsTs", ShortDeckHandType.ROYAL_FLUSH), # Royal Flush
|
||||||
|
]
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for hand_str, expected_type in hands:
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_from_input(hand_str)
|
||||||
|
assert result.hand_type == expected_type
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
# Test that they are in ascending order of strength
|
||||||
|
for i in range(len(results) - 1):
|
||||||
|
assert results[i] < results[i + 1], f"{results[i].hand_type} should be less than {results[i + 1].hand_type}"
|
||||||
|
|
||||||
|
def test_shortdeck_main_script_functionality(self):
|
||||||
|
"""Test that the main script input format works correctly"""
|
||||||
|
|
||||||
|
# Test various input formats that the main script should handle
|
||||||
|
test_inputs = [
|
||||||
|
"AsKsQsJsTs", # 5 cards
|
||||||
|
"AsKs QsJs TsAh Ad", # 7 cards with spaces
|
||||||
|
"6s7s8s9sTs", # Low straight flush
|
||||||
|
]
|
||||||
|
|
||||||
|
for input_str in test_inputs:
|
||||||
|
# This should not raise an exception
|
||||||
|
result = ShortDeckHandEvaluator.evaluate_from_input(input_str)
|
||||||
|
assert result is not None
|
||||||
|
assert hasattr(result, 'hand_type')
|
||||||
|
assert hasattr(result, 'key_ranks')
|
||||||
|
assert len(str(result)) > 0
|
||||||
|
|
||||||
|
def test_edge_cases(self):
|
||||||
|
"""Test various edge cases"""
|
||||||
|
|
||||||
|
# All same rank except one (should be four of a kind)
|
||||||
|
result1 = ShortDeckHandEvaluator.evaluate_from_input("6s6h6d6cAs")
|
||||||
|
assert result1.hand_type == ShortDeckHandType.FOUR_OF_A_KIND
|
||||||
|
|
||||||
|
# Almost straight but not quite
|
||||||
|
result2 = ShortDeckHandEvaluator.evaluate_from_input("AsKhQdJs8c") # Missing 10
|
||||||
|
assert result2.hand_type == ShortDeckHandType.HIGH_CARD
|
||||||
|
|
||||||
|
# Almost flush but not quite
|
||||||
|
result3 = ShortDeckHandEvaluator.evaluate_from_input("AsKsQsJs8h") # Mixed suits, not straight
|
||||||
|
assert result3.hand_type == ShortDeckHandType.HIGH_CARD
|
||||||
@@ -52,39 +52,39 @@ class TestCard:
|
|||||||
|
|
||||||
def test_from_string_valid(self):
|
def test_from_string_valid(self):
|
||||||
"""Test creating card from valid string"""
|
"""Test creating card from valid string"""
|
||||||
card = Card.createCard("As")
|
card = Card.create_card("As")
|
||||||
assert card.rank == Rank.ACE
|
assert card.rank == Rank.ACE
|
||||||
assert card.suit == Suit.SPADES
|
assert card.suit == Suit.SPADES
|
||||||
|
|
||||||
card2 = Card.createCard("Kh")
|
card2 = Card.create_card("Kh")
|
||||||
assert card2.rank == Rank.KING
|
assert card2.rank == Rank.KING
|
||||||
assert card2.suit == Suit.HEARTS
|
assert card2.suit == Suit.HEARTS
|
||||||
|
|
||||||
card3 = Card.createCard("2c")
|
card3 = Card.create_card("2c")
|
||||||
assert card3.rank == Rank.TWO
|
assert card3.rank == Rank.TWO
|
||||||
assert card3.suit == Suit.CLUBS
|
assert card3.suit == Suit.CLUBS
|
||||||
|
|
||||||
card4 = Card.createCard("Td")
|
card4 = Card.create_card("Td")
|
||||||
assert card4.rank == Rank.TEN
|
assert card4.rank == Rank.TEN
|
||||||
assert card4.suit == Suit.DIAMONDS
|
assert card4.suit == Suit.DIAMONDS
|
||||||
|
|
||||||
def test_from_string_invalid(self):
|
def test_from_string_invalid(self):
|
||||||
"""Test creating card from invalid string"""
|
"""Test creating card from invalid string"""
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Card.createCard("A") # Too short
|
Card.create_card("A") # Too short
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Card.createCard("Asx") # Too long
|
Card.create_card("Asx") # Too long
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Card.createCard("Xs") # Invalid rank
|
Card.create_card("Xs") # Invalid rank
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Card.createCard("Ax") # Invalid suit
|
Card.create_card("Ax") # Invalid suit
|
||||||
|
|
||||||
def test_parse_cards_valid(self):
|
def test_parse_cards_valid(self):
|
||||||
"""Test parsing multiple cards from string"""
|
"""Test parsing multiple cards from string"""
|
||||||
cards = Card.parseCards("AsKs AhAdAc6s7s")
|
cards = Card.parse_cards("AsKs AhAdAc6s7s")
|
||||||
assert len(cards) == 7
|
assert len(cards) == 7
|
||||||
assert str(cards[0]) == "As"
|
assert str(cards[0]) == "As"
|
||||||
assert str(cards[1]) == "Ks"
|
assert str(cards[1]) == "Ks"
|
||||||
@@ -93,23 +93,23 @@ class TestCard:
|
|||||||
|
|
||||||
def test_parse_cards_with_spaces(self):
|
def test_parse_cards_with_spaces(self):
|
||||||
"""Test parsing cards with various spacing"""
|
"""Test parsing cards with various spacing"""
|
||||||
cards = Card.parseCards("As Ks Ah Ad Ac 6s 7s")
|
cards = Card.parse_cards("As Ks Ah Ad Ac 6s 7s")
|
||||||
assert len(cards) == 7
|
assert len(cards) == 7
|
||||||
assert str(cards[0]) == "As"
|
assert str(cards[0]) == "As"
|
||||||
assert str(cards[6]) == "7s"
|
assert str(cards[6]) == "7s"
|
||||||
|
|
||||||
def test_parse_cards_empty(self):
|
def test_parse_cards_empty(self):
|
||||||
"""Test parsing empty string"""
|
"""Test parsing empty string"""
|
||||||
cards = Card.parseCards("")
|
cards = Card.parse_cards("")
|
||||||
assert len(cards) == 0
|
assert len(cards) == 0
|
||||||
|
|
||||||
cards = Card.parseCards(" ")
|
cards = Card.parse_cards(" ")
|
||||||
assert len(cards) == 0
|
assert len(cards) == 0
|
||||||
|
|
||||||
def test_parse_cards_invalid(self):
|
def test_parse_cards_invalid(self):
|
||||||
"""Test parsing invalid card strings"""
|
"""Test parsing invalid card strings"""
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Card.parseCards("AsKs A") # Incomplete card
|
Card.parse_cards("AsKs A") # Incomplete card
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Card.parseCards("AsKs Ax") # Invalid suit
|
Card.parse_cards("AsKs Ax") # Invalid suit
|
||||||
179
tests/test_coverage_edge_cases.py
Normal file
179
tests/test_coverage_edge_cases.py
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
"""
|
||||||
|
Edge case tests to achieve 100% code coverage
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from poker.card import Card, Rank, ShortDeckRank, Suit
|
||||||
|
from poker.hand_evaluator import HandEvaluator
|
||||||
|
from poker.hand_ranking import HandType, HandRanking
|
||||||
|
from shortdeck.hand_ranking import ShortDeckHandType, ShortDeckHandRanking
|
||||||
|
|
||||||
|
|
||||||
|
class TestCoverageEdgeCases:
|
||||||
|
"""Tests for edge cases to improve code coverage"""
|
||||||
|
|
||||||
|
def test_rank_comparison_with_invalid_types(self):
|
||||||
|
"""Test rank comparisons with invalid types"""
|
||||||
|
rank = Rank.ACE
|
||||||
|
|
||||||
|
# Test __eq__ with non-Rank object
|
||||||
|
result = rank.__eq__("invalid")
|
||||||
|
assert result is NotImplemented
|
||||||
|
|
||||||
|
# Test __lt__ with non-Rank object
|
||||||
|
result = rank.__lt__("invalid")
|
||||||
|
assert result is NotImplemented
|
||||||
|
|
||||||
|
def test_shortdeck_rank_comparison_with_invalid_types(self):
|
||||||
|
"""Test short deck rank comparisons with invalid types"""
|
||||||
|
rank = ShortDeckRank.ACE
|
||||||
|
|
||||||
|
# Test __eq__ with non-ShortDeckRank object
|
||||||
|
result = rank.__eq__("invalid")
|
||||||
|
assert result is NotImplemented
|
||||||
|
|
||||||
|
# Test __lt__ with non-ShortDeckRank object
|
||||||
|
result = rank.__lt__("invalid")
|
||||||
|
assert result is NotImplemented
|
||||||
|
|
||||||
|
def test_card_creation_with_valid_ranks(self):
|
||||||
|
"""Test Card creation with valid ranks"""
|
||||||
|
# Standard deck card
|
||||||
|
card1 = Card(Rank.ACE, Suit.HEARTS)
|
||||||
|
assert card1.rank == Rank.ACE
|
||||||
|
assert card1.suit == Suit.HEARTS
|
||||||
|
assert not card1.is_short_deck()
|
||||||
|
|
||||||
|
# Short deck card
|
||||||
|
card2 = Card(ShortDeckRank.ACE, Suit.HEARTS)
|
||||||
|
assert card2.rank == ShortDeckRank.ACE
|
||||||
|
assert card2.suit == Suit.HEARTS
|
||||||
|
assert card2.is_short_deck()
|
||||||
|
|
||||||
|
def test_hand_evaluator_exactly_5_cards_error(self):
|
||||||
|
"""Test HandEvaluator with wrong number of cards"""
|
||||||
|
cards = [Card(Rank.ACE, Suit.HEARTS)] # Only 1 card
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Expected 5 cards, got 1"):
|
||||||
|
HandEvaluator.evaluate_5_cards(cards)
|
||||||
|
|
||||||
|
def test_hand_ranking_comparison_with_invalid_types(self):
|
||||||
|
"""Test HandRanking comparisons with invalid types"""
|
||||||
|
cards = [
|
||||||
|
Card(Rank.ACE, Suit.HEARTS),
|
||||||
|
Card(Rank.KING, Suit.HEARTS),
|
||||||
|
Card(Rank.QUEEN, Suit.HEARTS),
|
||||||
|
Card(Rank.JACK, Suit.HEARTS),
|
||||||
|
Card(Rank.TEN, Suit.HEARTS)
|
||||||
|
]
|
||||||
|
|
||||||
|
ranking = HandRanking(HandType.ROYAL_FLUSH, [Rank.ACE], cards)
|
||||||
|
|
||||||
|
# Test comparisons with non-HandRanking objects should return False, not NotImplemented
|
||||||
|
assert ranking != "invalid"
|
||||||
|
assert not (ranking == "invalid")
|
||||||
|
|
||||||
|
# These should not raise exceptions
|
||||||
|
try:
|
||||||
|
ranking < "invalid"
|
||||||
|
ranking <= "invalid"
|
||||||
|
ranking > "invalid"
|
||||||
|
ranking >= "invalid"
|
||||||
|
except TypeError:
|
||||||
|
pass # Expected behavior
|
||||||
|
|
||||||
|
def test_shortdeck_hand_ranking_comparison_with_invalid_types(self):
|
||||||
|
"""Test ShortDeckHandRanking comparisons with invalid types"""
|
||||||
|
cards = [
|
||||||
|
Card(ShortDeckRank.ACE, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.KING, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.QUEEN, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.JACK, Suit.HEARTS),
|
||||||
|
Card(ShortDeckRank.TEN, Suit.HEARTS)
|
||||||
|
]
|
||||||
|
|
||||||
|
ranking = ShortDeckHandRanking(ShortDeckHandType.ROYAL_FLUSH, [ShortDeckRank.ACE], cards)
|
||||||
|
|
||||||
|
# Test comparisons with non-ShortDeckHandRanking objects
|
||||||
|
assert ranking != "invalid"
|
||||||
|
assert not (ranking == "invalid")
|
||||||
|
|
||||||
|
def test_card_comparison_and_equality(self):
|
||||||
|
"""Test Card comparison and equality edge cases"""
|
||||||
|
card1 = Card(Rank.ACE, Suit.HEARTS)
|
||||||
|
card2 = Card(Rank.ACE, Suit.HEARTS)
|
||||||
|
card3 = Card(Rank.KING, Suit.HEARTS)
|
||||||
|
|
||||||
|
# Test equality
|
||||||
|
assert card1 == card2
|
||||||
|
assert card1 != card3
|
||||||
|
|
||||||
|
# Test comparison
|
||||||
|
assert card1 > card3 # Ace > King
|
||||||
|
|
||||||
|
# Test inequality with non-Card object
|
||||||
|
result = card1.__eq__("not a card")
|
||||||
|
assert result is NotImplemented
|
||||||
|
|
||||||
|
result = card1.__lt__("not a card")
|
||||||
|
assert result is NotImplemented
|
||||||
|
|
||||||
|
def test_rank_hash_consistency(self):
|
||||||
|
"""Test rank hash consistency"""
|
||||||
|
rank1 = Rank.ACE
|
||||||
|
rank2 = Rank.ACE
|
||||||
|
rank3 = Rank.KING
|
||||||
|
|
||||||
|
assert hash(rank1) == hash(rank2)
|
||||||
|
assert hash(rank1) != hash(rank3)
|
||||||
|
|
||||||
|
def test_shortdeck_rank_hash_consistency(self):
|
||||||
|
"""Test short deck rank hash consistency"""
|
||||||
|
rank1 = ShortDeckRank.ACE
|
||||||
|
rank2 = ShortDeckRank.ACE
|
||||||
|
rank3 = ShortDeckRank.KING
|
||||||
|
|
||||||
|
assert hash(rank1) == hash(rank2)
|
||||||
|
assert hash(rank1) != hash(rank3)
|
||||||
|
|
||||||
|
def test_card_string_parsing_valid_cases(self):
|
||||||
|
"""Test valid card string parsing cases"""
|
||||||
|
# Test valid input
|
||||||
|
cards = Card.parse_cards("As Kh")
|
||||||
|
assert len(cards) == 2
|
||||||
|
assert cards[0].rank == Rank.ACE
|
||||||
|
assert cards[0].suit == Suit.SPADES
|
||||||
|
assert cards[1].rank == Rank.KING
|
||||||
|
assert cards[1].suit == Suit.HEARTS
|
||||||
|
|
||||||
|
# Test empty string returns empty list
|
||||||
|
cards = Card.parse_cards("")
|
||||||
|
assert len(cards) == 0
|
||||||
|
|
||||||
|
def test_card_getter_methods(self):
|
||||||
|
"""Test Card getter methods for coverage"""
|
||||||
|
card = Card(Rank.ACE, Suit.HEARTS)
|
||||||
|
assert card.get_rank() == Rank.ACE
|
||||||
|
assert card.get_suit() == Suit.HEARTS
|
||||||
|
|
||||||
|
def test_card_legacy_parse_methods(self):
|
||||||
|
"""Test legacy parsing methods"""
|
||||||
|
# Test parseLongCards method
|
||||||
|
cards = Card.parseLongCards("As Kh")
|
||||||
|
assert len(cards) == 2
|
||||||
|
|
||||||
|
# Test parse_short_deck_cards method
|
||||||
|
cards = Card.parse_short_deck_cards("As Kh")
|
||||||
|
assert len(cards) == 2
|
||||||
|
|
||||||
|
def test_hand_type_comparisons_with_invalid_types(self):
|
||||||
|
"""Test HandType comparisons with invalid types"""
|
||||||
|
hand_type = HandType.ROYAL_FLUSH
|
||||||
|
|
||||||
|
# Test __lt__ with invalid type
|
||||||
|
result = hand_type.__lt__("invalid")
|
||||||
|
assert result is NotImplemented
|
||||||
|
|
||||||
|
# Test __eq__ with invalid type
|
||||||
|
result = hand_type.__eq__("invalid")
|
||||||
|
assert result is NotImplemented
|
||||||
@@ -14,7 +14,7 @@ class TestHandEvaluator:
|
|||||||
def test_royal_flush(self):
|
def test_royal_flush(self):
|
||||||
"""Test royal flush detection"""
|
"""Test royal flush detection"""
|
||||||
cards_str = "AhKhQhJhTh2c3c"
|
cards_str = "AhKhQhJhTh2c3c"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.ROYAL_FLUSH
|
assert ranking.hand_type == HandType.ROYAL_FLUSH
|
||||||
assert str(ranking) == "Royal Flush"
|
assert str(ranking) == "Royal Flush"
|
||||||
@@ -22,7 +22,7 @@ class TestHandEvaluator:
|
|||||||
def test_straight_flush(self):
|
def test_straight_flush(self):
|
||||||
"""Test straight flush detection"""
|
"""Test straight flush detection"""
|
||||||
cards_str = "2h3h4h5h6h7s8s"
|
cards_str = "2h3h4h5h6h7s8s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.STRAIGHT_FLUSH
|
assert ranking.hand_type == HandType.STRAIGHT_FLUSH
|
||||||
assert str(ranking) == "Straight Flush(6 high)"
|
assert str(ranking) == "Straight Flush(6 high)"
|
||||||
@@ -30,7 +30,7 @@ class TestHandEvaluator:
|
|||||||
def test_four_of_a_kind(self):
|
def test_four_of_a_kind(self):
|
||||||
"""Test four of a kind detection"""
|
"""Test four of a kind detection"""
|
||||||
cards_str = "AsKs AhAdAc6s7s"
|
cards_str = "AsKs AhAdAc6s7s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.FOUR_OF_A_KIND
|
assert ranking.hand_type == HandType.FOUR_OF_A_KIND
|
||||||
assert str(ranking) == "Quad(A)"
|
assert str(ranking) == "Quad(A)"
|
||||||
@@ -38,7 +38,7 @@ class TestHandEvaluator:
|
|||||||
def test_full_house(self):
|
def test_full_house(self):
|
||||||
"""Test full house detection"""
|
"""Test full house detection"""
|
||||||
cards_str = "AsAhAd KsKh6s7s"
|
cards_str = "AsAhAd KsKh6s7s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.FULL_HOUSE
|
assert ranking.hand_type == HandType.FULL_HOUSE
|
||||||
assert "Full House(A over K)" in str(ranking)
|
assert "Full House(A over K)" in str(ranking)
|
||||||
@@ -46,7 +46,7 @@ class TestHandEvaluator:
|
|||||||
def test_flush(self):
|
def test_flush(self):
|
||||||
"""Test flush detection"""
|
"""Test flush detection"""
|
||||||
cards_str = "AhKh6h4h2h7s8s"
|
cards_str = "AhKh6h4h2h7s8s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.FLUSH
|
assert ranking.hand_type == HandType.FLUSH
|
||||||
assert "Flush(A high)" in str(ranking)
|
assert "Flush(A high)" in str(ranking)
|
||||||
@@ -54,7 +54,7 @@ class TestHandEvaluator:
|
|||||||
def test_straight(self):
|
def test_straight(self):
|
||||||
"""Test straight detection"""
|
"""Test straight detection"""
|
||||||
cards_str = "As2h3d4c5h7s8s"
|
cards_str = "As2h3d4c5h7s8s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.STRAIGHT
|
assert ranking.hand_type == HandType.STRAIGHT
|
||||||
assert str(ranking) == "Straight(5 high)" # A-2-3-4-5 wheel
|
assert str(ranking) == "Straight(5 high)" # A-2-3-4-5 wheel
|
||||||
@@ -62,7 +62,7 @@ class TestHandEvaluator:
|
|||||||
def test_straight_ace_high(self):
|
def test_straight_ace_high(self):
|
||||||
"""Test straight with ace high"""
|
"""Test straight with ace high"""
|
||||||
cards_str = "AsTsJhQdKh7s8s"
|
cards_str = "AsTsJhQdKh7s8s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.STRAIGHT
|
assert ranking.hand_type == HandType.STRAIGHT
|
||||||
assert str(ranking) == "Straight(A high)"
|
assert str(ranking) == "Straight(A high)"
|
||||||
@@ -70,7 +70,7 @@ class TestHandEvaluator:
|
|||||||
def test_three_of_a_kind(self):
|
def test_three_of_a_kind(self):
|
||||||
"""Test three of a kind detection"""
|
"""Test three of a kind detection"""
|
||||||
cards_str = "AsAhAd6s7h8s9s"
|
cards_str = "AsAhAd6s7h8s9s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.THREE_OF_A_KIND
|
assert ranking.hand_type == HandType.THREE_OF_A_KIND
|
||||||
assert str(ranking) == "Three of a Kind(A)"
|
assert str(ranking) == "Three of a Kind(A)"
|
||||||
@@ -78,7 +78,7 @@ class TestHandEvaluator:
|
|||||||
def test_two_pair(self):
|
def test_two_pair(self):
|
||||||
"""Test two pair detection"""
|
"""Test two pair detection"""
|
||||||
cards_str = "AsAh6d6s7h8s9s"
|
cards_str = "AsAh6d6s7h8s9s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.TWO_PAIR
|
assert ranking.hand_type == HandType.TWO_PAIR
|
||||||
assert "Two Pair(A and 6)" in str(ranking)
|
assert "Two Pair(A and 6)" in str(ranking)
|
||||||
@@ -86,7 +86,7 @@ class TestHandEvaluator:
|
|||||||
def test_one_pair(self):
|
def test_one_pair(self):
|
||||||
"""Test one pair detection"""
|
"""Test one pair detection"""
|
||||||
cards_str = "AsAh6d4s2h3cJd"
|
cards_str = "AsAh6d4s2h3cJd"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.ONE_PAIR
|
assert ranking.hand_type == HandType.ONE_PAIR
|
||||||
assert str(ranking) == "Pair(A)"
|
assert str(ranking) == "Pair(A)"
|
||||||
@@ -94,7 +94,7 @@ class TestHandEvaluator:
|
|||||||
def test_high_card(self):
|
def test_high_card(self):
|
||||||
"""Test high card detection"""
|
"""Test high card detection"""
|
||||||
cards_str = "As6h4d8s9hJdKc"
|
cards_str = "As6h4d8s9hJdKc"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.HIGH_CARD
|
assert ranking.hand_type == HandType.HIGH_CARD
|
||||||
assert str(ranking) == "High Card(A)"
|
assert str(ranking) == "High Card(A)"
|
||||||
@@ -102,7 +102,7 @@ class TestHandEvaluator:
|
|||||||
def test_wheel_straight(self):
|
def test_wheel_straight(self):
|
||||||
"""Test A-2-3-4-5 straight (wheel)"""
|
"""Test A-2-3-4-5 straight (wheel)"""
|
||||||
cards_str = "As2h3d4c5h7s8s"
|
cards_str = "As2h3d4c5h7s8s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.STRAIGHT
|
assert ranking.hand_type == HandType.STRAIGHT
|
||||||
assert ranking.key_ranks[0] == Rank.FIVE # 5 is high in wheel
|
assert ranking.key_ranks[0] == Rank.FIVE # 5 is high in wheel
|
||||||
@@ -110,21 +110,21 @@ class TestHandEvaluator:
|
|||||||
def test_invalid_input_not_seven_cards(self):
|
def test_invalid_input_not_seven_cards(self):
|
||||||
"""Test error handling for wrong number of cards"""
|
"""Test error handling for wrong number of cards"""
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
HandEvaluator.evaluateFromInput("AsKh")
|
HandEvaluator.evaluate_from_input("AsKh")
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
HandEvaluator.evaluateFromInput("AsKhQdJc9h8s7d6c")
|
HandEvaluator.evaluate_from_input("AsKhQdJc9h8s7d6c")
|
||||||
|
|
||||||
def test_invalid_card_format(self):
|
def test_invalid_card_format(self):
|
||||||
"""Test error handling for invalid card format"""
|
"""Test error handling for invalid card format"""
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
HandEvaluator.evaluateFromInput("AsKhQdJcXh8s7d")
|
HandEvaluator.evaluate_from_input("AsKhQdJcXh8s7d")
|
||||||
|
|
||||||
def test_seven_cards_best_five_selected(self):
|
def test_seven_cards_best_five_selected(self):
|
||||||
"""Test that best 5 cards are selected from 7"""
|
"""Test that best 5 cards are selected from 7"""
|
||||||
# Should pick the straight flush over the pair
|
# Should pick the straight flush over the pair
|
||||||
cards_str = "2h3h4h5h6hAsAd"
|
cards_str = "2h3h4h5h6hAsAd"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.STRAIGHT_FLUSH
|
assert ranking.hand_type == HandType.STRAIGHT_FLUSH
|
||||||
assert str(ranking) == "Straight Flush(6 high)"
|
assert str(ranking) == "Straight Flush(6 high)"
|
||||||
@@ -132,7 +132,7 @@ class TestHandEvaluator:
|
|||||||
def test_multiple_possible_straights(self):
|
def test_multiple_possible_straights(self):
|
||||||
"""Test selecting highest straight from multiple possibilities"""
|
"""Test selecting highest straight from multiple possibilities"""
|
||||||
cards_str = "As2h3d4c5h6s7s"
|
cards_str = "As2h3d4c5h6s7s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.STRAIGHT
|
assert ranking.hand_type == HandType.STRAIGHT
|
||||||
# Should pick 3-4-5-6-7 over A-2-3-4-5
|
# Should pick 3-4-5-6-7 over A-2-3-4-5
|
||||||
@@ -141,7 +141,7 @@ class TestHandEvaluator:
|
|||||||
def test_multiple_possible_flushes(self):
|
def test_multiple_possible_flushes(self):
|
||||||
"""Test selecting best flush from multiple suits"""
|
"""Test selecting best flush from multiple suits"""
|
||||||
cards_str = "AhKh6h4h2h7s8s"
|
cards_str = "AhKh6h4h2h7s8s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert ranking.hand_type == HandType.FLUSH
|
assert ranking.hand_type == HandType.FLUSH
|
||||||
assert ranking.key_ranks[0] == Rank.ACE
|
assert ranking.key_ranks[0] == Rank.ACE
|
||||||
@@ -155,6 +155,6 @@ class TestHandEvaluator:
|
|||||||
Card(Rank.ACE, Suit.CLUBS),
|
Card(Rank.ACE, Suit.CLUBS),
|
||||||
Card(Rank.KING, Suit.SPADES)
|
Card(Rank.KING, Suit.SPADES)
|
||||||
]
|
]
|
||||||
ranking = HandEvaluator.evaluate5Cards(cards)
|
ranking = HandEvaluator.evaluate_5_cards(cards)
|
||||||
assert ranking.hand_type == HandType.FOUR_OF_A_KIND
|
assert ranking.hand_type == HandType.FOUR_OF_A_KIND
|
||||||
assert ranking.key_ranks[0] == Rank.ACE
|
assert ranking.key_ranks[0] == Rank.ACE
|
||||||
@@ -15,7 +15,7 @@ class TestMainProgram:
|
|||||||
def test_example_input(self):
|
def test_example_input(self):
|
||||||
"""Test the example input from the requirements"""
|
"""Test the example input from the requirements"""
|
||||||
cards_str = "AsKs AhAdAc6s7s"
|
cards_str = "AsKs AhAdAc6s7s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert str(ranking) == "Quad(A)"
|
assert str(ranking) == "Quad(A)"
|
||||||
|
|
||||||
@@ -35,13 +35,13 @@ class TestMainProgram:
|
|||||||
]
|
]
|
||||||
|
|
||||||
for cards_str, expected_result in test_cases:
|
for cards_str, expected_result in test_cases:
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
assert str(ranking) == expected_result, f"Failed for {cards_str}"
|
assert str(ranking) == expected_result, f"Failed for {cards_str}"
|
||||||
|
|
||||||
def test_wheel_straight(self):
|
def test_wheel_straight(self):
|
||||||
"""Test A-2-3-4-5 straight"""
|
"""Test A-2-3-4-5 straight"""
|
||||||
cards_str = "As2h3d4c5h7s8s"
|
cards_str = "As2h3d4c5h7s8s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert str(ranking) == "Straight(5 high)"
|
assert str(ranking) == "Straight(5 high)"
|
||||||
|
|
||||||
@@ -56,29 +56,29 @@ class TestMainProgram:
|
|||||||
expected_result = "Quad(A)"
|
expected_result = "Quad(A)"
|
||||||
|
|
||||||
for cards_str in formats:
|
for cards_str in formats:
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
assert str(ranking) == expected_result
|
assert str(ranking) == expected_result
|
||||||
|
|
||||||
def test_case_insensitive_suits(self):
|
def test_case_insensitive_suits(self):
|
||||||
"""Test that suits are case insensitive"""
|
"""Test that suits are case insensitive"""
|
||||||
cards_str = "AsKsAhAdAc6s7s"
|
cards_str = "AsKsAhAdAc6s7s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
|
|
||||||
assert str(ranking) == "Quad(A)"
|
assert str(ranking) == "Quad(A)"
|
||||||
|
|
||||||
def test_edge_cases(self):
|
def test_edge_cases(self):
|
||||||
"""Test edge cases and boundary conditions"""
|
"""Test edge cases and boundary conditions"""
|
||||||
cards_str = "2s2h2d2c3s4h5d"
|
cards_str = "2s2h2d2c3s4h5d"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
assert str(ranking) == "Quad(2)"
|
assert str(ranking) == "Quad(2)"
|
||||||
|
|
||||||
# Minimum straight
|
# Minimum straight
|
||||||
cards_str = "As2h3d4c5h6s7s"
|
cards_str = "As2h3d4c5h6s7s"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
# Should pick 3-4-5-6-7 over A-2-3-4-5
|
# Should pick 3-4-5-6-7 over A-2-3-4-5
|
||||||
assert "Straight(7 high)" in str(ranking)
|
assert "Straight(7 high)" in str(ranking)
|
||||||
|
|
||||||
# Maximum straight
|
# Maximum straight
|
||||||
cards_str = "9sTsJhQdKhAsAd"
|
cards_str = "9sTsJhQdKhAsAd"
|
||||||
ranking = HandEvaluator.evaluateFromInput(cards_str)
|
ranking = HandEvaluator.evaluate_from_input(cards_str)
|
||||||
assert str(ranking) == "Straight(A high)"
|
assert str(ranking) == "Straight(A high)"
|
||||||
92
uv.lock
generated
92
uv.lock
generated
@@ -11,6 +11,67 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.10.7"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@@ -41,22 +102,25 @@ wheels = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "poker-emd-task"
|
name = "poker-emd-task"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { editable = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "pytest" },
|
{ name = "coverage" },
|
||||||
|
{ name = "pytest-cov" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [{ name = "pytest", specifier = ">=8.4.2" }]
|
||||||
{ name = "pytest", specifier = ">=8.4.2" },
|
|
||||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" },
|
[package.metadata.requires-dev]
|
||||||
|
dev = [
|
||||||
|
{ name = "coverage", specifier = ">=7.10.7" },
|
||||||
|
{ name = "pytest-cov", specifier = ">=7.0.0" },
|
||||||
]
|
]
|
||||||
provides-extras = ["dev"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
@@ -82,3 +146,17 @@ sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a
|
|||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-cov"
|
||||||
|
version = "7.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "coverage" },
|
||||||
|
{ name = "pluggy" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
|
||||||
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user