This commit is contained in:
2025-09-23 11:11:47 +08:00
parent 674a7b16b7
commit 45f86ba95e
11 changed files with 121 additions and 377 deletions

120
README.md
View File

@@ -1,14 +1,6 @@
# Poker Hand Evaluation Program # Poker Task1
一个用Python实现的扑克牌手牌评估程序能够从7张牌中找出最佳的5张牌组合并返回手牌排名 实现从7张牌中找出最佳的5张牌组合并返回handranking
## 功能特性
- **完整的数据结构**实现了Card、Deck、HandRanking等核心类
- **字符串解析**:支持从字符串格式解析扑克牌(如 "AsKs AhAdAc6s7s"
- **手牌评估**:支持所有标准扑克手牌类型的识别和排名
- **最佳组合选择**从7张牌中自动选择最佳的5张牌组合
- **全面测试**:包含完整的单元测试套件
## 支持的手牌类型 ## 支持的手牌类型
@@ -23,8 +15,6 @@
9. **一对** (One Pair) - 一个对子 9. **一对** (One Pair) - 一个对子
10. **高牌** (High Card) - 没有组合的单张高牌 10. **高牌** (High Card) - 没有组合的单张高牌
## 安装和使用
### 环境要求 ### 环境要求
- Python 3.13+ - Python 3.13+
- uv 包管理器 - uv 包管理器
@@ -53,8 +43,6 @@ python main.py "JsJhJdQcQs2h3d"
- 第一个字符是点数:`2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `T`, `J`, `Q`, `K`, `A` - 第一个字符是点数:`2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `T`, `J`, `Q`, `K`, `A`
- 第二个字符是花色:`s`(黑桃), `h`(红桃), `d`(方块), `c`(梅花) - 第二个字符是花色:`s`(黑桃), `h`(红桃), `d`(方块), `c`(梅花)
牌之间可以用空格分隔,也可以连续书写。
### 运行测试 ### 运行测试
```bash ```bash
@@ -65,113 +53,11 @@ python run_tests.py
pytest tests/ -v pytest tests/ -v
``` ```
## 项目结构
```
poker_emd_task/
├── poker/ # 核心模块
│ ├── __init__.py # 模块初始化
│ ├── card.py # Card、Rank、Suit类
│ ├── deck.py # Deck类
│ ├── hand_ranking.py # HandRanking、HandType类
│ └── hand_evaluator.py # HandEvaluator类核心算法
├── tests/ # 测试文件
│ ├── test_card.py
│ ├── test_deck.py
│ ├── test_hand_ranking.py
│ ├── test_hand_evaluator.py
│ └── test_integration.py
├── main.py # 主程序入口
├── run_tests.py # 简化的测试运行器
├── pyproject.toml # 项目配置
└── README.md # 项目说明
```
## 设计特点
### 核心数据结构
- **Card类**:表示单张扑克牌,包含点数和花色
- **Rank枚举**:定义牌的点数,支持比较操作
- **Suit枚举**:定义牌的花色
- **HandType枚举**:定义所有手牌类型及其强度
- **HandRanking类**:表示手牌评估结果,包含类型和关键牌
- **Deck类**表示一副标准52张牌
- **HandEvaluator类**:核心算法,评估手牌并选择最佳组合
### 算法实现
1. **穷举法**从7张牌中穷举所有可能的5张牌组合21种
2. **模式识别**对每个5张牌组合识别手牌类型
3. **排名比较**:选择强度最高的手牌组合
4. **特殊处理**正确处理A-2-3-4-5轮子顺子等特殊情况
### 错误处理
- 输入验证检查牌数是否为7张
- 格式验证:验证每张牌的格式是否正确
- 范围验证:确保点数和花色在有效范围内
- 友好错误信息:提供清晰的错误提示
## 示例用法
```python
from poker.hand_evaluator import HandEvaluator
from poker.card import Card, Rank, Suit
# 方式1从字符串评估
ranking = HandEvaluator.evaluate_from_input("AsKs AhAdAc6s7s")
print(ranking) # Quad(A)
# 方式2从Card对象评估
cards = [
Card(Rank.ACE, Suit.SPADES),
Card(Rank.KING, Suit.SPADES),
# ... 更多牌
]
ranking = HandEvaluator.evaluate_hand(cards)
print(ranking)
```
## 测试覆盖
项目包含全面的测试套件,覆盖:
- 所有核心类的功能测试
- 各种手牌类型的识别测试
- 边界条件和错误情况测试
- 集成测试和端到端测试
运行测试确保所有功能正常工作:
```bash
python run_tests.py
```
预期输出:
```
Running poker hand evaluation tests...
Testing Card functionality...
✓ Card tests passed
Testing hand parsing...
✓ Hand parsing tests passed
Testing hand evaluation...
✓ Hand evaluation tests passed
Testing wheel straight...
✓ Wheel straight test passed
Testing deck functionality...
✓ Deck tests passed
Testing error handling...
✓ Error handling tests passed
Test Results: 6 passed, 0 failed
All tests passed! 🎉
```
## 技术栈 ## 技术栈
- **语言**Python 3.13 - **语言**Python 3.13
- **包管理**uv / poetry - **包管理**uv
- **测试**自定义测试框架也支持pytest - **测试**自定义测试框架也支持pytest
- **开发环境**VS Code - **开发环境**VS Code

18
main.py
View File

@@ -1,31 +1,17 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""
Poker hand evaluation program
Takes 7 cards as string input and outputs the best HandRanking
"""
import sys import sys
from poker.hand_evaluator import HandEvaluator from poker.hand_evaluator import HandEvaluator
def main(): def main():
if len(sys.argv) != 2: if len(sys.argv) != 2:
print("Usage: python main.py \"<7 cards>\"")
print("Example: python main.py \"AsKs AhAdAc6s7s\"") print("Example: python main.py \"AsKs AhAdAc6s7s\"")
sys.exit(1) sys.exit(1)
cards_str = sys.argv[1] cards_str = sys.argv[1]
try: hand_ranking = HandEvaluator.evaluateFromInput(cards_str)
# 评估手牌 print(hand_ranking)
hand_ranking = HandEvaluator.evaluate_from_input(cards_str)
# 输出结果
print(hand_ranking)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,7 +1,4 @@
""" from functools import total_ordering
Card module for poker game
"""
from enum import Enum from enum import Enum
from typing import List, Tuple, Optional from typing import List, Tuple, Optional
@@ -15,7 +12,6 @@ class Suit(Enum):
def __str__(self): def __str__(self):
return self.value return self.value
class Rank(Enum): class Rank(Enum):
TWO = (2, '2') TWO = (2, '2')
THREE = (3, '3') THREE = (3, '3')
@@ -41,12 +37,21 @@ class Rank(Enum):
def __str__(self): def __str__(self):
return self.symbol return self.symbol
def __eq__(self, other):
if isinstance(other, Rank):
return self.numeric_value == other.numeric_value
return NotImplemented
def __lt__(self, other): def __lt__(self, other):
if not isinstance(other, Rank): if isinstance(other, Rank):
return NotImplemented return self.numeric_value < other.numeric_value
return self.numeric_value < other.numeric_value return NotImplemented
def __hash__(self):
return hash(self.numeric_value)
@total_ordering
class Card: class Card:
def __init__(self, rank: Rank, suit: Suit): def __init__(self, rank: Rank, suit: Suit):
self.rank = rank self.rank = rank
@@ -55,22 +60,20 @@ class Card:
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 not isinstance(other, Card): if isinstance(other, Card):
return False return self.rank == other.rank and self.suit == other.suit
return self.rank == other.rank and self.suit == other.suit return NotImplemented
def __lt__(self, other): def __lt__(self, other):
if not isinstance(other, Card): if isinstance(other, Card):
return NotImplemented if self.rank != other.rank:
return self.rank.numeric_value < other.rank.numeric_value return self.rank < other.rank
return self.suit.value < other.suit.value
return NotImplemented
@classmethod @classmethod
def create_card(cls, card_str: str) -> 'Card': def createCard(cls, card_str) -> 'Card':
"""
从字符串创建Card对象例如 "As", "Kh", "2c"
"""
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}")
@@ -100,9 +103,9 @@ class Card:
return cls(rank, suit) return cls(rank, suit)
@classmethod @classmethod
def parse_cards(cls, cards_str: str) -> List['Card']: def parseCards(cls, cards_str) -> List['Card']:
""" """
从字符串解多张牌,例如 "AsKs AhAdAc6s7s" 从字符串解多张牌, "AsKs AhAdAc6s7s"
""" """
cards_str = cards_str.strip() cards_str = cards_str.strip()
if not cards_str: if not cards_str:
@@ -122,8 +125,4 @@ 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}")
result = [] return [cls.createCard(card_str) for card_str in card_strings]
for card_str in card_strings:
card_tmp = cls.create_card(card_str)
result.append(card_tmp)
return result

View File

@@ -1,7 +1,3 @@
"""
Hand evaluator module for poker game
"""
from typing import List, Tuple, Dict from typing import List, Tuple, Dict
from collections import Counter from collections import Counter
from itertools import combinations from itertools import combinations
@@ -10,14 +6,10 @@ from .hand_ranking import HandRanking, HandType
class HandEvaluator: class HandEvaluator:
"""
手牌评估器类,用于评估扑克手牌
"""
@staticmethod @staticmethod
def evaluate_hand(cards: List[Card]) -> HandRanking: def evaluateHand(cards) -> HandRanking:
""" """
从7张牌中评估出最好的5张牌组合 从7张牌中出最好的5张牌组合
""" """
if len(cards) != 7: if len(cards) != 7:
raise ValueError(f"Expected 7 cards, got {len(cards)}") raise ValueError(f"Expected 7 cards, got {len(cards)}")
@@ -25,23 +17,19 @@ class HandEvaluator:
best_ranking = None best_ranking = None
best_cards = None best_cards = None
# 尝试所有可能的5张牌组合 # 所有可能的5张牌组合
for five_cards in combinations(cards, 5): for five_cards in combinations(cards, 5):
ranking = HandEvaluator._evaluate_five_cards(list(five_cards)) ranking = HandEvaluator.evaluate5Cards(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
best_cards = list(five_cards) best_cards = list(five_cards)
# 更新最佳ranking的cards
best_ranking.cards = best_cards best_ranking.cards = best_cards
return best_ranking return best_ranking
@staticmethod @staticmethod
def _evaluate_five_cards(cards: List[Card]) -> HandRanking: def evaluate5Cards(cards) -> HandRanking:
"""
评估5张牌的手牌类型
"""
if len(cards) != 5: if len(cards) != 5:
raise ValueError(f"Expected 5 cards, got {len(cards)}") raise ValueError(f"Expected 5 cards, got {len(cards)}")
@@ -54,11 +42,11 @@ class HandEvaluator:
rank_counts = Counter(ranks) rank_counts = Counter(ranks)
count_values = sorted(rank_counts.values(), reverse=True) count_values = sorted(rank_counts.values(), reverse=True)
# 检查是否是同花 # 同花
is_flush = len(set(suits)) == 1 is_flush = len(set(suits)) == 1
# 检查是否是顺子 # 顺子
is_straight, straight_high = HandEvaluator._is_straight(ranks) is_straight, straight_high = HandEvaluator._isStraight(ranks)
# 根据牌型返回相应的HandRanking # 根据牌型返回相应的HandRanking
if is_straight and is_flush: if is_straight and is_flush:
@@ -102,10 +90,7 @@ class HandEvaluator:
return HandRanking(HandType.HIGH_CARD, ranks, sorted_cards) return HandRanking(HandType.HIGH_CARD, ranks, sorted_cards)
@staticmethod @staticmethod
def _is_straight(ranks: List[Rank]) -> Tuple[bool, Rank]: def _isStraight(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)
@@ -125,16 +110,13 @@ class HandEvaluator:
break break
return True, highest_rank return True, highest_rank
# 检查A-2-3-4-5特殊顺子(轮子) # A-2-3-4-5特殊顺子
if values == [14, 5, 4, 3, 2]: # A, 5, 4, 3, 2 if values == [14, 5, 4, 3, 2]: # A, 5, 4, 3, 2
return True, Rank.FIVE # 在轮子中,5是最高牌 return True, Rank.FIVE # 5是最高牌
return False, None return False, None
@staticmethod @staticmethod
def evaluate_from_input(cards_str: str) -> HandRanking: def evaluateFromInput(cards_str) -> HandRanking:
""" cards = Card.parseCards(cards_str)
从字符串评估手牌,例如 "AsKs AhAdAc6s7s" return HandEvaluator.evaluateHand(cards)
"""
cards = Card.parse_cards(cards_str)
return HandEvaluator.evaluate_hand(cards)

View File

@@ -1,16 +1,11 @@
"""
Hand ranking module for poker game
"""
from enum import Enum from enum import Enum
from functools import total_ordering
from typing import List, Tuple from typing import List, Tuple
from .card import Card, Rank from .card import Card, Rank
@total_ordering
class HandType(Enum): class HandType(Enum):
"""
手牌类型枚举,按强度排序
"""
HIGH_CARD = (1, "High Card") HIGH_CARD = (1, "High Card")
ONE_PAIR = (2, "Pair") ONE_PAIR = (2, "Pair")
TWO_PAIR = (3, "Two Pair") TWO_PAIR = (3, "Two Pair")
@@ -33,40 +28,26 @@ class HandType(Enum):
return self.type_name return self.type_name
def __lt__(self, other): def __lt__(self, other):
if not isinstance(other, HandType): if isinstance(other, HandType):
return NotImplemented return self.strength < other.strength
return self.strength < other.strength return NotImplemented
def __le__(self, other): def __eq__(self, other):
if not isinstance(other, HandType): if isinstance(other, HandType):
return NotImplemented return self.strength == other.strength
return self.strength <= other.strength return NotImplemented
def __gt__(self, other): def __hash__(self):
if not isinstance(other, HandType): return hash(self.strength)
return NotImplemented
return self.strength > other.strength
def __ge__(self, other):
if not isinstance(other, HandType):
return NotImplemented
return self.strength >= other.strength
class HandRanking: class HandRanking:
"""
手牌排名类,包含手牌类型和关键牌
"""
def __init__(self, hand_type: HandType, key_ranks: List[Rank], cards: List[Card]): def __init__(self, hand_type: HandType, key_ranks: List[Rank], 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 # 组成这个排名的5张牌 self.cards = cards # 组成这个ranking的5张牌
def __str__(self): def __str__(self):
"""
返回手牌排名的字符串表示
"""
if self.hand_type == HandType.FOUR_OF_A_KIND: if self.hand_type == HandType.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 self.hand_type == HandType.FULL_HOUSE:
@@ -91,35 +72,18 @@ class HandRanking:
else: # HIGH_CARD else: # HIGH_CARD
return f"High Card({self.key_ranks[0].symbol})" return f"High Card({self.key_ranks[0].symbol})"
def __repr__(self):
return f"HandRanking({self.hand_type}, {[r.symbol for r in self.key_ranks]})"
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 return (self.hand_type == other.hand_type and self.key_ranks == other.key_ranks)
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: if self.hand_type != other.hand_type:
return self.hand_type < other.hand_type return self.hand_type < other.hand_type
# 手牌类型相同比较点数
# 如果手牌类型相同,比较关键点数
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
return False
return False # 完全相等
def __le__(self, other):
return self == other or self < other
def __gt__(self, other):
return not self <= other
def __ge__(self, other):
return not self < other

View File

@@ -8,13 +8,6 @@ dependencies = [
"pytest>=8.4.2", "pytest>=8.4.2",
] ]
[project.optional-dependencies]
dev = ["pytest>=7.0.0"]
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[tool.pytest.ini_options] [tool.pytest.ini_options]
testpaths = ["tests"] testpaths = ["tests"]
python_files = ["test_*.py"] python_files = ["test_*.py"]

View File

@@ -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.from_string("Kh") parsed_card = Card.createCard("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.parse_cards("AsKs AhAdAc6s7s") cards = Card.parseCards("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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.from_string("Xx") Card.createCard("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.evaluate_from_input("AsKh") HandEvaluator.evaluateFromInput("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

View File

@@ -5,37 +5,19 @@ Tests for Card, Rank, and Suit classes
import pytest import pytest
from poker.card import Card, Rank, Suit from poker.card import Card, Rank, Suit
# card.py测试
class TestRank: class TestRank:
"""Test cases for Rank enum""" """Test cases for Rank enum"""
def test_rank_values(self): def test_rank_values(self):
"""Test rank numeric values""" """Test rank numeric values"""
assert Rank.TWO.numeric_value == 2 assert Rank.TWO.numeric_value == 2
assert Rank.ACE.numeric_value == 14 assert Rank.ACE.numeric_value == 14
assert Rank.KING.numeric_value == 13
assert Rank.JACK.numeric_value == 11
def test_rank_symbols(self): def test_rank_symbols(self):
"""Test rank symbols""" """Test rank symbols"""
assert Rank.TWO.symbol == '2' assert Rank.TWO.symbol == '2'
assert Rank.ACE.symbol == 'A' assert Rank.ACE.symbol == 'A'
assert Rank.KING.symbol == 'K'
assert Rank.TEN.symbol == 'T'
def test_rank_comparison(self):
"""Test rank comparison operations"""
assert Rank.TWO < Rank.THREE
assert Rank.KING < Rank.ACE
assert Rank.JACK > Rank.TEN
assert Rank.ACE >= Rank.KING
assert Rank.TWO <= Rank.TWO
def test_rank_str(self):
"""Test string representation"""
assert str(Rank.ACE) == 'A'
assert str(Rank.KING) == 'K'
assert str(Rank.TWO) == '2'
class TestSuit: class TestSuit:
@@ -47,11 +29,7 @@ class TestSuit:
assert Suit.HEARTS.value == 'h' assert Suit.HEARTS.value == 'h'
assert Suit.DIAMONDS.value == 'd' assert Suit.DIAMONDS.value == 'd'
assert Suit.CLUBS.value == 'c' assert Suit.CLUBS.value == 'c'
def test_suit_str(self):
"""Test string representation"""
assert str(Suit.SPADES) == 's'
assert str(Suit.HEARTS) == 'h'
class TestCard: class TestCard:
@@ -71,60 +49,42 @@ class TestCard:
card2 = Card(Rank.KING, Suit.HEARTS) card2 = Card(Rank.KING, Suit.HEARTS)
assert str(card2) == 'Kh' assert str(card2) == 'Kh'
def test_card_equality(self):
"""Test card equality"""
card1 = Card(Rank.ACE, Suit.SPADES)
card2 = Card(Rank.ACE, Suit.SPADES)
card3 = Card(Rank.ACE, Suit.HEARTS)
assert card1 == card2
assert card1 != card3
def test_card_comparison(self):
"""Test card comparison (by rank)"""
ace_spades = Card(Rank.ACE, Suit.SPADES)
king_hearts = Card(Rank.KING, Suit.HEARTS)
two_clubs = Card(Rank.TWO, Suit.CLUBS)
assert two_clubs < king_hearts
assert king_hearts < ace_spades
assert not (ace_spades < king_hearts)
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.from_string("As") card = Card.createCard("As")
assert card.rank == Rank.ACE assert card.rank == Rank.ACE
assert card.suit == Suit.SPADES assert card.suit == Suit.SPADES
card2 = Card.from_string("Kh") card2 = Card.createCard("Kh")
assert card2.rank == Rank.KING assert card2.rank == Rank.KING
assert card2.suit == Suit.HEARTS assert card2.suit == Suit.HEARTS
card3 = Card.from_string("2c") card3 = Card.createCard("2c")
assert card3.rank == Rank.TWO assert card3.rank == Rank.TWO
assert card3.suit == Suit.CLUBS assert card3.suit == Suit.CLUBS
card4 = Card.from_string("Td") card4 = Card.createCard("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.from_string("A") # Too short Card.createCard("A") # Too short
with pytest.raises(ValueError): with pytest.raises(ValueError):
Card.from_string("Asx") # Too long Card.createCard("Asx") # Too long
with pytest.raises(ValueError): with pytest.raises(ValueError):
Card.from_string("Xs") # Invalid rank Card.createCard("Xs") # Invalid rank
with pytest.raises(ValueError): with pytest.raises(ValueError):
Card.from_string("Ax") # Invalid suit Card.createCard("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.parse_cards("AsKs AhAdAc6s7s") cards = Card.parseCards("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"
@@ -133,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.parse_cards("As Ks Ah Ad Ac 6s 7s") cards = Card.parseCards("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.parse_cards("") cards = Card.parseCards("")
assert len(cards) == 0 assert len(cards) == 0
cards = Card.parse_cards(" ") cards = Card.parseCards(" ")
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.parse_cards("AsKs A") # Incomplete card Card.parseCards("AsKs A") # Incomplete card
with pytest.raises(ValueError): with pytest.raises(ValueError):
Card.parse_cards("AsKs Ax") # Invalid suit Card.parseCards("AsKs Ax") # Invalid suit

View File

@@ -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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input("AsKh") HandEvaluator.evaluateFromInput("AsKh")
with pytest.raises(ValueError): with pytest.raises(ValueError):
HandEvaluator.evaluate_from_input("AsKhQdJc9h8s7d6c") HandEvaluator.evaluateFromInput("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.evaluate_from_input("AsKhQdJcXh8s7d") HandEvaluator.evaluateFromInput("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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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
@@ -150,12 +150,11 @@ class TestHandEvaluator:
"""Test evaluating exactly 5 cards""" """Test evaluating exactly 5 cards"""
cards = [ cards = [
Card(Rank.ACE, Suit.SPADES), Card(Rank.ACE, Suit.SPADES),
Card(Rank.ACE, Suit.HEARTS), Card(Rank.ACE, Suit.HEARTS),
Card(Rank.ACE, Suit.DIAMONDS), Card(Rank.ACE, Suit.DIAMONDS),
Card(Rank.ACE, Suit.CLUBS), Card(Rank.ACE, Suit.CLUBS),
Card(Rank.KING, Suit.SPADES) Card(Rank.KING, Suit.SPADES)
] ]
ranking = HandEvaluator._evaluate_five_cards(cards) ranking = HandEvaluator.evaluate5Cards(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

View File

@@ -90,48 +90,25 @@ class TestHandRanking:
def test_three_of_a_kind_string_representation(self): def test_three_of_a_kind_string_representation(self):
"""Test string representation for three of a kind""" """Test string representation for three of a kind"""
cards = [Card(Rank.ACE, Suit.SPADES)] * 5 # Dummy cards cards = [Card(Rank.ACE, Suit.SPADES)] * 5
ranking = HandRanking(HandType.THREE_OF_A_KIND, [Rank.ACE], cards) ranking = HandRanking(HandType.THREE_OF_A_KIND, [Rank.ACE], cards)
assert str(ranking) == "Three of a Kind(A)" assert str(ranking) == "Three of a Kind(A)"
def test_two_pair_string_representation(self): def test_two_pair_string_representation(self):
"""Test string representation for two pair""" """Test string representation for two pair"""
cards = [Card(Rank.ACE, Suit.SPADES)] * 5 # Dummy cards cards = [Card(Rank.ACE, Suit.SPADES)] * 5
ranking = HandRanking(HandType.TWO_PAIR, [Rank.ACE, Rank.KING], cards) ranking = HandRanking(HandType.TWO_PAIR, [Rank.ACE, Rank.KING], cards)
assert str(ranking) == "Two Pair(A and K)" assert str(ranking) == "Two Pair(A and K)"
def test_one_pair_string_representation(self): def test_one_pair_string_representation(self):
"""Test string representation for one pair""" """Test string representation for one pair"""
cards = [Card(Rank.ACE, Suit.SPADES)] * 5 # Dummy cards cards = [Card(Rank.ACE, Suit.SPADES)] * 5
ranking = HandRanking(HandType.ONE_PAIR, [Rank.ACE], cards) ranking = HandRanking(HandType.ONE_PAIR, [Rank.ACE], cards)
assert str(ranking) == "Pair(A)" assert str(ranking) == "Pair(A)"
def test_high_card_string_representation(self): def test_high_card_string_representation(self):
"""Test string representation for high card""" """Test string representation for high card"""
cards = [Card(Rank.ACE, Suit.SPADES)] * 5 # Dummy cards cards = [Card(Rank.ACE, Suit.SPADES)] * 5
ranking = HandRanking(HandType.HIGH_CARD, [Rank.ACE], cards) ranking = HandRanking(HandType.HIGH_CARD, [Rank.ACE], cards)
assert str(ranking) == "High Card(A)" assert str(ranking) == "High Card(A)"
def test_hand_ranking_equality(self):
"""Test hand ranking equality"""
cards = [Card(Rank.ACE, Suit.SPADES)] * 5 # Dummy cards
ranking1 = HandRanking(HandType.FOUR_OF_A_KIND, [Rank.ACE, Rank.KING], cards)
ranking2 = HandRanking(HandType.FOUR_OF_A_KIND, [Rank.ACE, Rank.KING], cards)
ranking3 = HandRanking(HandType.FOUR_OF_A_KIND, [Rank.KING, Rank.ACE], cards)
assert ranking1 == ranking2
assert ranking1 != ranking3
def test_hand_ranking_comparison(self):
"""Test hand ranking comparison"""
cards = [Card(Rank.ACE, Suit.SPADES)] * 5 # Dummy cards
# Different hand types
quad_aces = HandRanking(HandType.FOUR_OF_A_KIND, [Rank.ACE], cards)
full_house = HandRanking(HandType.FULL_HOUSE, [Rank.ACE], cards)
assert full_house < quad_aces
# Same hand type, different ranks
quad_aces = HandRanking(HandType.FOUR_OF_A_KIND, [Rank.ACE], cards)
quad_kings = HandRanking(HandType.FOUR_OF_A_KIND, [Rank.KING], cards)
assert quad_kings < quad_aces

View File

@@ -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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(cards_str)
assert str(ranking) == "Quad(A)" assert str(ranking) == "Quad(A)"
@@ -35,19 +35,18 @@ class TestMainProgram:
] ]
for cards_str, expected_result in test_cases: for cards_str, expected_result in test_cases:
ranking = HandEvaluator.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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 (wheel)""" """Test A-2-3-4-5 straight"""
cards_str = "As2h3d4c5h7s8s" cards_str = "As2h3d4c5h7s8s"
ranking = HandEvaluator.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(cards_str)
assert str(ranking) == "Straight(5 high)" assert str(ranking) == "Straight(5 high)"
def test_different_card_formats(self): def test_different_card_formats(self):
"""Test different ways of writing the same cards""" """Test different ways of writing the same cards"""
# These should all represent the same hand
formats = [ formats = [
"AsKsAhAdAc6s7s", "AsKsAhAdAc6s7s",
"As Ks Ah Ad Ac 6s 7s", "As Ks Ah Ad Ac 6s 7s",
@@ -57,30 +56,29 @@ class TestMainProgram:
expected_result = "Quad(A)" expected_result = "Quad(A)"
for cards_str in formats: for cards_str in formats:
ranking = HandEvaluator.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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"""
# All same rank except one
cards_str = "2s2h2d2c3s4h5d" cards_str = "2s2h2d2c3s4h5d"
ranking = HandEvaluator.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(cards_str)
assert str(ranking) == "Quad(2)" assert str(ranking) == "Quad(2)"
# Minimum straight # Minimum straight
cards_str = "As2h3d4c5h6s7s" cards_str = "As2h3d4c5h6s7s"
ranking = HandEvaluator.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(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.evaluate_from_input(cards_str) ranking = HandEvaluator.evaluateFromInput(cards_str)
assert str(ranking) == "Straight(A high)" assert str(ranking) == "Straight(A high)"