Skip to content

Commit 1d2bb6d

Browse files
chore: combinations work
1 parent 2251c29 commit 1d2bb6d

2 files changed

Lines changed: 315 additions & 0 deletions

File tree

src/poker/card.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
from enum import Enum
2+
from typing import List, Tuple, Dict
3+
from dataclasses import dataclass
4+
from collections import Counter
5+
6+
7+
class Suit(Enum):
8+
CLUBS = 'c'
9+
DIAMONDS = 'd'
10+
HEARTS = 'h'
11+
SPADES = 's'
12+
13+
class Face(Enum):
14+
TWO = '2'
15+
THREE = '3'
16+
FOUR = '4'
17+
FIVE = '5'
18+
SIX = '6'
19+
SEVEN = '7'
20+
EIGHT = '8'
21+
NINE = '9'
22+
TEN = 'T'
23+
JACK = 'J'
24+
QUEEN = 'Q'
25+
KING = 'K'
26+
ACE = 'A'
27+
28+
29+
face_values = {
30+
'2': 2,
31+
'3': 3,
32+
'4': 4,
33+
'5': 5,
34+
'6': 6,
35+
'7': 7,
36+
'8': 8,
37+
'9': 9,
38+
'T': 10,
39+
'J': 11,
40+
'Q': 12,
41+
'K': 13,
42+
'A': 14
43+
}
44+
45+
@dataclass
46+
class Card:
47+
face: Face
48+
suit: Suit
49+
50+
Hand = List[Card]
51+
52+
53+
def parse_cards(input_line: str) -> Hand:
54+
face_map = {face.value: face for face in Face}
55+
suit_map = {suit.value: suit for suit in Suit}
56+
cards = input_line.split()
57+
return [Card(face=face_map[card[0]], suit=suit_map[card[1]]) for card in cards]
58+
59+
60+
def get_similar_cards(hand: Hand) -> Dict[str, List]:
61+
faces = [card.face for card in hand]
62+
face_counts = Counter(faces)
63+
combinations_set = set()
64+
for face, count in face_counts.items():
65+
if count > 1:
66+
combinations_set.add((count, face.value))
67+
combinations = sorted(combinations_set, reverse=True)
68+
69+
kickers = sorted([face.value for face in faces if face_counts[face] == 1], reverse=True)
70+
71+
return {
72+
'combinations': combinations,
73+
'kickers': kickers
74+
}
75+
76+
77+
def is_flush(hand: List[Card]) -> bool:
78+
suits = [card.suit for card in hand]
79+
return len(set(suits)) == 1
80+
81+
def is_straight(hand: List[Card]) -> Tuple[bool, int]:
82+
faces = sorted([face_values[card.face.value] for card in hand])
83+
unique_faces = list(sorted(set(faces)))
84+
85+
if len(unique_faces) < 5:
86+
return False, 0
87+
88+
for i in range(len(unique_faces) - 4):
89+
if unique_faces[i:i+5] == list(range(unique_faces[i], unique_faces[i]+5)):
90+
return True, unique_faces[i+4]
91+
# specific case with ace when we have a low straight
92+
if set(unique_faces) >= {2, 3, 4, 5, 14}:
93+
return True, 5
94+
95+
return False, 0
96+
97+
def is_royal_flush(hand: List[Card]) -> bool:
98+
is_flush_hand = is_flush(hand)
99+
is_straight_hand, high_card = is_straight(hand)
100+
return is_flush_hand and is_straight_hand and high_card == 14
101+
102+
103+
104+
def identify_hand(hand: Hand) -> str:
105+
hand_ranks = get_similar_cards(hand)
106+
combinations = hand_ranks['combinations']
107+
flush = is_flush(hand)
108+
straight, high_card = is_straight(hand)
109+
110+
if is_royal_flush(hand):
111+
return "Royal Flush"
112+
elif straight and flush:
113+
return "Straight Flush"
114+
elif combinations and combinations[0][0] == 4:
115+
return "Four of a Kind"
116+
elif len(combinations) == 2 and combinations[0][0] == 3 and combinations[1][0] == 2:
117+
return "Full House"
118+
elif flush and not straight:
119+
return "Flush"
120+
elif straight and not flush:
121+
return "Straight"
122+
elif len(combinations) == 2 and combinations[0][0] == 2 and combinations[1][0] == 2:
123+
return "Two Pair"
124+
elif len(combinations) == 1 and combinations[0][0] == 3:
125+
return "Three of a Kind"
126+
elif len(combinations) == 1 and combinations[0][0] == 2:
127+
return "Pair"
128+
else:
129+
return "High Card"

tests/poker/test_card.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import pytest
2+
3+
from src.poker.card import Face, Suit, Hand, Card, parse_cards, identify_hand, get_similar_cards
4+
5+
6+
def test_parse_cards():
7+
input_line = "Kc 9s Ks Kd 9d 3c 6d"
8+
expected = [
9+
Card(face=Face.KING, suit=Suit.CLUBS),
10+
Card(face=Face.NINE, suit=Suit.SPADES),
11+
Card(face=Face.KING, suit=Suit.SPADES),
12+
Card(face=Face.KING, suit=Suit.DIAMONDS),
13+
Card(face=Face.NINE, suit=Suit.DIAMONDS),
14+
Card(face=Face.THREE, suit=Suit.CLUBS),
15+
Card(face=Face.SIX, suit=Suit.DIAMONDS)
16+
]
17+
assert parse_cards(input_line) == expected
18+
19+
20+
21+
22+
23+
test_data = [
24+
("test_full_house", [
25+
Card(face=Face.KING, suit=Suit.CLUBS),
26+
Card(face=Face.NINE, suit=Suit.SPADES),
27+
Card(face=Face.KING, suit=Suit.SPADES),
28+
Card(face=Face.KING, suit=Suit.DIAMONDS),
29+
Card(face=Face.NINE, suit=Suit.DIAMONDS),
30+
Card(face=Face.THREE, suit=Suit.CLUBS),
31+
Card(face=Face.SIX, suit=Suit.DIAMONDS)
32+
], {
33+
'combinations': [(3, 'K'), (2, '9')],
34+
'kickers': ['6', '3']
35+
}),
36+
37+
("test_two_pair", [
38+
Card(face=Face.KING, suit=Suit.CLUBS),
39+
Card(face=Face.NINE, suit=Suit.SPADES),
40+
Card(face=Face.KING, suit=Suit.SPADES),
41+
Card(face=Face.NINE, suit=Suit.DIAMONDS),
42+
Card(face=Face.THREE, suit=Suit.CLUBS),
43+
Card(face=Face.SIX, suit=Suit.DIAMONDS)
44+
], {
45+
'combinations': [(2, 'K'), (2, '9')],
46+
'kickers': ['6', '3']
47+
}),
48+
49+
("test_three_of_a_kind", [
50+
Card(face=Face.KING, suit=Suit.CLUBS),
51+
Card(face=Face.NINE, suit=Suit.SPADES),
52+
Card(face=Face.KING, suit=Suit.SPADES),
53+
Card(face=Face.KING, suit=Suit.DIAMONDS),
54+
Card(face=Face.THREE, suit=Suit.CLUBS),
55+
Card(face=Face.SIX, suit=Suit.DIAMONDS)
56+
], {
57+
'combinations': [(3, 'K')],
58+
'kickers': ['9', '6', '3']
59+
}),
60+
61+
("test_pair", [
62+
Card(face=Face.KING, suit=Suit.CLUBS),
63+
Card(face=Face.NINE, suit=Suit.SPADES),
64+
Card(face=Face.KING, suit=Suit.SPADES),
65+
Card(face=Face.THREE, suit=Suit.CLUBS),
66+
Card(face=Face.SIX, suit=Suit.DIAMONDS)
67+
], {
68+
'combinations': [(2, 'K')],
69+
'kickers': ['9', '6', '3']
70+
}),
71+
72+
("test_high_card", [
73+
Card(face=Face.KING, suit=Suit.CLUBS),
74+
Card(face=Face.NINE, suit=Suit.SPADES),
75+
Card(face=Face.THREE, suit=Suit.CLUBS),
76+
Card(face=Face.SIX, suit=Suit.DIAMONDS)
77+
], {
78+
'combinations': [],
79+
'kickers': ['K', '9', '6', '3']
80+
}),
81+
]
82+
83+
@pytest.mark.parametrize("name, hand, expected", test_data)
84+
def test_get_hand_ranks(name, hand, expected):
85+
assert get_similar_cards(hand) == expected
86+
87+
88+
89+
def test_identify_hand():
90+
hand = [
91+
Card(face=Face.KING, suit=Suit.CLUBS),
92+
Card(face=Face.NINE, suit=Suit.SPADES),
93+
Card(face=Face.KING, suit=Suit.SPADES),
94+
Card(face=Face.KING, suit=Suit.DIAMONDS),
95+
Card(face=Face.NINE, suit=Suit.DIAMONDS),
96+
Card(face=Face.THREE, suit=Suit.CLUBS),
97+
Card(face=Face.SIX, suit=Suit.DIAMONDS)
98+
]
99+
assert identify_hand(hand) == "Full House"
100+
101+
102+
test_data_identify_hand = [
103+
("test_full_house", [
104+
Card(face=Face.KING, suit=Suit.CLUBS),
105+
Card(face=Face.NINE, suit=Suit.SPADES),
106+
Card(face=Face.KING, suit=Suit.SPADES),
107+
Card(face=Face.KING, suit=Suit.DIAMONDS),
108+
Card(face=Face.NINE, suit=Suit.DIAMONDS),
109+
Card(face=Face.THREE, suit=Suit.CLUBS),
110+
Card(face=Face.SIX, suit=Suit.DIAMONDS)
111+
], "Full House"),
112+
113+
("test_two_pair", [
114+
Card(face=Face.KING, suit=Suit.CLUBS),
115+
Card(face=Face.NINE, suit=Suit.SPADES),
116+
Card(face=Face.KING, suit=Suit.SPADES),
117+
Card(face=Face.NINE, suit=Suit.DIAMONDS),
118+
Card(face=Face.THREE, suit=Suit.CLUBS),
119+
Card(face=Face.SIX, suit=Suit.DIAMONDS)
120+
], "Two Pair"),
121+
122+
("test_three_of_a_kind", [
123+
Card(face=Face.KING, suit=Suit.CLUBS),
124+
Card(face=Face.NINE, suit=Suit.SPADES),
125+
Card(face=Face.KING, suit=Suit.SPADES),
126+
Card(face=Face.KING, suit=Suit.DIAMONDS),
127+
Card(face=Face.THREE, suit=Suit.CLUBS),
128+
Card(face=Face.SIX, suit=Suit.DIAMONDS)
129+
], "Three of a Kind"),
130+
131+
("test_pair", [
132+
Card(face=Face.KING, suit=Suit.CLUBS),
133+
Card(face=Face.NINE, suit=Suit.SPADES),
134+
Card(face=Face.KING, suit=Suit.SPADES),
135+
Card(face=Face.THREE, suit=Suit.CLUBS),
136+
Card(face=Face.SIX, suit=Suit.DIAMONDS)
137+
], "Pair"),
138+
139+
("test_high_card", [
140+
Card(face=Face.KING, suit=Suit.CLUBS),
141+
Card(face=Face.NINE, suit=Suit.SPADES),
142+
Card(face=Face.THREE, suit=Suit.CLUBS),
143+
Card(face=Face.SIX, suit=Suit.DIAMONDS)
144+
], "High Card"),
145+
("test_straight", [
146+
Card(face=Face.FIVE, suit=Suit.CLUBS),
147+
Card(face=Face.NINE, suit=Suit.SPADES),
148+
Card(face=Face.SIX, suit=Suit.SPADES),
149+
Card(face=Face.SEVEN, suit=Suit.DIAMONDS),
150+
Card(face=Face.EIGHT, suit=Suit.CLUBS),
151+
Card(face=Face.TEN, suit=Suit.DIAMONDS)
152+
], "Straight"),
153+
("test_flush", [
154+
Card(face=Face.FOUR, suit=Suit.CLUBS),
155+
Card(face=Face.NINE, suit=Suit.CLUBS),
156+
Card(face=Face.THREE, suit=Suit.CLUBS),
157+
Card(face=Face.SEVEN, suit=Suit.CLUBS),
158+
Card(face=Face.EIGHT, suit=Suit.CLUBS),
159+
Card(face=Face.TEN, suit=Suit.CLUBS)
160+
], "Flush"),
161+
("test_straight_flush", [
162+
Card(face=Face.FOUR, suit=Suit.CLUBS),
163+
Card(face=Face.NINE, suit=Suit.CLUBS),
164+
Card(face=Face.SIX, suit=Suit.CLUBS),
165+
Card(face=Face.SEVEN, suit=Suit.CLUBS),
166+
Card(face=Face.EIGHT, suit=Suit.CLUBS),
167+
Card(face=Face.TEN, suit=Suit.CLUBS)
168+
], "Straight Flush"),
169+
("test_royal_flush", [
170+
Card(face=Face.ACE, suit=Suit.CLUBS),
171+
Card(face=Face.JACK, suit=Suit.CLUBS),
172+
Card(face=Face.KING, suit=Suit.CLUBS),
173+
Card(face=Face.QUEEN, suit=Suit.CLUBS),
174+
Card(face=Face.TEN, suit=Suit.CLUBS)
175+
], "Royal Flush"),
176+
("test_four_of_a_kind", [
177+
Card(face=Face.ACE, suit=Suit.CLUBS),
178+
Card(face=Face.ACE, suit=Suit.SPADES),
179+
Card(face=Face.ACE, suit=Suit.DIAMONDS),
180+
Card(face=Face.ACE, suit=Suit.HEARTS),
181+
Card(face=Face.TEN, suit=Suit.CLUBS)
182+
], "Four of a Kind"),
183+
]
184+
@pytest.mark.parametrize("name, hand, expected", test_data_identify_hand)
185+
def test_identify_hand(name, hand, expected):
186+
assert identify_hand(hand) == expected

0 commit comments

Comments
 (0)