mirror of
https://github.com/TheAlgorithms/Python.git
synced 2025-01-19 00:37:02 +00:00
Create problem_54 in project Euler (#2451)
* Add solution and test files for project euler 54 * Update sol1.py * updating DIRECTORY.md * Fix: use proper path to open files * Commit suggestions: - Use list comprehension instead of map - Sort imports using isort * Changes made as suggested (simplified a lot): - List and set comprehension instead of itemgetter - Using enumerate as it's easy to read - Divided into list of card values and set of card suit as set will remove all the duplicate values. So, no need for double indexing. - Add test for testing multiple calls to five_high_straight function * Add suggestions and simplified: - Split generate_random_hands function into two: - First will generate a random hand - Second, which will be called, will return a generator object Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
This commit is contained in:
parent
9b73884def
commit
ea0759dbaa
|
@ -86,6 +86,7 @@
|
||||||
|
|
||||||
## Conversions
|
## Conversions
|
||||||
* [Binary To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_decimal.py)
|
* [Binary To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_decimal.py)
|
||||||
|
* [Binary To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_octal.py)
|
||||||
* [Decimal To Any](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_any.py)
|
* [Decimal To Any](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_any.py)
|
||||||
* [Decimal To Binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py)
|
* [Decimal To Binary](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_binary.py)
|
||||||
* [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py)
|
* [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py)
|
||||||
|
@ -628,6 +629,9 @@
|
||||||
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py)
|
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_52/sol1.py)
|
||||||
* Problem 53
|
* Problem 53
|
||||||
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py)
|
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_53/sol1.py)
|
||||||
|
* Problem 54
|
||||||
|
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_54/sol1.py)
|
||||||
|
* [Test Poker Hand](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_54/test_poker_hand.py)
|
||||||
* Problem 55
|
* Problem 55
|
||||||
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_55/sol1.py)
|
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_55/sol1.py)
|
||||||
* Problem 551
|
* Problem 551
|
||||||
|
|
0
project_euler/problem_54/__init__.py
Normal file
0
project_euler/problem_54/__init__.py
Normal file
1000
project_euler/problem_54/poker_hands.txt
Normal file
1000
project_euler/problem_54/poker_hands.txt
Normal file
File diff suppressed because it is too large
Load Diff
358
project_euler/problem_54/sol1.py
Normal file
358
project_euler/problem_54/sol1.py
Normal file
|
@ -0,0 +1,358 @@
|
||||||
|
"""
|
||||||
|
Problem: https://projecteuler.net/problem=54
|
||||||
|
|
||||||
|
In the card game poker, a hand consists of five cards and are ranked,
|
||||||
|
from lowest to highest, in the following way:
|
||||||
|
|
||||||
|
High Card: Highest value card.
|
||||||
|
One Pair: Two cards of the same value.
|
||||||
|
Two Pairs: Two different pairs.
|
||||||
|
Three of a Kind: Three cards of the same value.
|
||||||
|
Straight: All cards are consecutive values.
|
||||||
|
Flush: All cards of the same suit.
|
||||||
|
Full House: Three of a kind and a pair.
|
||||||
|
Four of a Kind: Four cards of the same value.
|
||||||
|
Straight Flush: All cards are consecutive values of same suit.
|
||||||
|
Royal Flush: Ten, Jack, Queen, King, Ace, in same suit.
|
||||||
|
|
||||||
|
The cards are valued in the order:
|
||||||
|
2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, King, Ace.
|
||||||
|
|
||||||
|
If two players have the same ranked hands then the rank made up of the highest
|
||||||
|
value wins; for example, a pair of eights beats a pair of fives.
|
||||||
|
But if two ranks tie, for example, both players have a pair of queens, then highest
|
||||||
|
cards in each hand are compared; if the highest cards tie then the next highest
|
||||||
|
cards are compared, and so on.
|
||||||
|
|
||||||
|
The file, poker.txt, contains one-thousand random hands dealt to two players.
|
||||||
|
Each line of the file contains ten cards (separated by a single space): the
|
||||||
|
first five are Player 1's cards and the last five are Player 2's cards.
|
||||||
|
You can assume that all hands are valid (no invalid characters or repeated cards),
|
||||||
|
each player's hand is in no specific order, and in each hand there is a clear winner.
|
||||||
|
|
||||||
|
How many hands does Player 1 win?
|
||||||
|
|
||||||
|
Resources used:
|
||||||
|
https://en.wikipedia.org/wiki/Texas_hold_%27em
|
||||||
|
https://en.wikipedia.org/wiki/List_of_poker_hands
|
||||||
|
|
||||||
|
Similar problem on codewars:
|
||||||
|
https://www.codewars.com/kata/ranking-poker-hands
|
||||||
|
https://www.codewars.com/kata/sortable-poker-hands
|
||||||
|
"""
|
||||||
|
from typing import List, Set, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
class PokerHand(object):
|
||||||
|
"""Create an object representing a Poker Hand based on an input of a
|
||||||
|
string which represents the best 5 card combination from the player's hand
|
||||||
|
and board cards.
|
||||||
|
|
||||||
|
Attributes: (read-only)
|
||||||
|
hand: string representing the hand consisting of five cards
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
compare_with(opponent): takes in player's hand (self) and
|
||||||
|
opponent's hand (opponent) and compares both hands according to
|
||||||
|
the rules of Texas Hold'em.
|
||||||
|
Returns one of 3 strings (Win, Loss, Tie) based on whether
|
||||||
|
player's hand is better than opponent's hand.
|
||||||
|
|
||||||
|
hand_name(): Returns a string made up of two parts: hand name
|
||||||
|
and high card.
|
||||||
|
|
||||||
|
Supported operators:
|
||||||
|
Rich comparison operators: <, >, <=, >=, ==, !=
|
||||||
|
|
||||||
|
Supported builtin methods and functions:
|
||||||
|
list.sort(), sorted()
|
||||||
|
"""
|
||||||
|
|
||||||
|
_HAND_NAME = [
|
||||||
|
"High card",
|
||||||
|
"One pair",
|
||||||
|
"Two pairs",
|
||||||
|
"Three of a kind",
|
||||||
|
"Straight",
|
||||||
|
"Flush",
|
||||||
|
"Full house",
|
||||||
|
"Four of a kind",
|
||||||
|
"Straight flush",
|
||||||
|
"Royal flush",
|
||||||
|
]
|
||||||
|
|
||||||
|
_CARD_NAME = [
|
||||||
|
"", # placeholder as lists are zero indexed
|
||||||
|
"One",
|
||||||
|
"Two",
|
||||||
|
"Three",
|
||||||
|
"Four",
|
||||||
|
"Five",
|
||||||
|
"Six",
|
||||||
|
"Seven",
|
||||||
|
"Eight",
|
||||||
|
"Nine",
|
||||||
|
"Ten",
|
||||||
|
"Jack",
|
||||||
|
"Queen",
|
||||||
|
"King",
|
||||||
|
"Ace",
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, hand: str) -> None:
|
||||||
|
"""
|
||||||
|
Initialize hand.
|
||||||
|
Hand should of type str and should contain only five cards each
|
||||||
|
separated by a space.
|
||||||
|
|
||||||
|
The cards should be of the following format:
|
||||||
|
[card value][card suit]
|
||||||
|
|
||||||
|
The first character is the value of the card:
|
||||||
|
2, 3, 4, 5, 6, 7, 8, 9, T(en), J(ack), Q(ueen), K(ing), A(ce)
|
||||||
|
|
||||||
|
The second character represents the suit:
|
||||||
|
S(pades), H(earts), D(iamonds), C(lubs)
|
||||||
|
|
||||||
|
For example: "6S 4C KC AS TH"
|
||||||
|
"""
|
||||||
|
if not isinstance(hand, str):
|
||||||
|
raise TypeError(f"Hand should be of type 'str': {hand!r}")
|
||||||
|
# split removes duplicate whitespaces so no need of strip
|
||||||
|
if len(hand.split(" ")) != 5:
|
||||||
|
raise ValueError(f"Hand should contain only 5 cards: {hand!r}")
|
||||||
|
self._hand = hand
|
||||||
|
self._first_pair = 0
|
||||||
|
self._second_pair = 0
|
||||||
|
self._card_values, self._card_suit = self._internal_state()
|
||||||
|
self._hand_type = self._get_hand_type()
|
||||||
|
self._high_card = self._card_values[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hand(self):
|
||||||
|
"""Returns the self hand"""
|
||||||
|
return self._hand
|
||||||
|
|
||||||
|
def compare_with(self, other: "PokerHand") -> str:
|
||||||
|
"""
|
||||||
|
Determines the outcome of comparing self hand with other hand.
|
||||||
|
Returns the output as 'Win', 'Loss', 'Tie' according to the rules of
|
||||||
|
Texas Hold'em.
|
||||||
|
|
||||||
|
Here are some examples:
|
||||||
|
>>> player = PokerHand("2H 3H 4H 5H 6H") # Stright flush
|
||||||
|
>>> opponent = PokerHand("KS AS TS QS JS") # Royal flush
|
||||||
|
>>> player.compare_with(opponent)
|
||||||
|
'Loss'
|
||||||
|
|
||||||
|
>>> player = PokerHand("2S AH 2H AS AC") # Full house
|
||||||
|
>>> opponent = PokerHand("2H 3H 5H 6H 7H") # Flush
|
||||||
|
>>> player.compare_with(opponent)
|
||||||
|
'Win'
|
||||||
|
|
||||||
|
>>> player = PokerHand("2S AH 4H 5S 6C") # High card
|
||||||
|
>>> opponent = PokerHand("AD 4C 5H 6H 2C") # High card
|
||||||
|
>>> player.compare_with(opponent)
|
||||||
|
'Tie'
|
||||||
|
"""
|
||||||
|
# Breaking the tie works on the following order of precedence:
|
||||||
|
# 1. First pair (default 0)
|
||||||
|
# 2. Second pair (default 0)
|
||||||
|
# 3. Compare all cards in reverse order because they are sorted.
|
||||||
|
|
||||||
|
# First pair and second pair will only be a non-zero value if the card
|
||||||
|
# type is either from the following:
|
||||||
|
# 21: Four of a kind
|
||||||
|
# 20: Full house
|
||||||
|
# 17: Three of a kind
|
||||||
|
# 16: Two pairs
|
||||||
|
# 15: One pair
|
||||||
|
if self._hand_type > other._hand_type:
|
||||||
|
return "Win"
|
||||||
|
elif self._hand_type < other._hand_type:
|
||||||
|
return "Loss"
|
||||||
|
elif self._first_pair == other._first_pair:
|
||||||
|
if self._second_pair == other._second_pair:
|
||||||
|
return self._compare_cards(other)
|
||||||
|
else:
|
||||||
|
return "Win" if self._second_pair > other._second_pair else "Loss"
|
||||||
|
return "Win" if self._first_pair > other._first_pair else "Loss"
|
||||||
|
|
||||||
|
# This function is not part of the problem, I did it just for fun
|
||||||
|
def hand_name(self) -> str:
|
||||||
|
"""
|
||||||
|
Return the name of the hand in the following format:
|
||||||
|
'hand name, high card'
|
||||||
|
|
||||||
|
Here are some examples:
|
||||||
|
>>> PokerHand("KS AS TS QS JS").hand_name()
|
||||||
|
'Royal flush'
|
||||||
|
|
||||||
|
>>> PokerHand("2D 6D 3D 4D 5D").hand_name()
|
||||||
|
'Straight flush, Six-high'
|
||||||
|
|
||||||
|
>>> PokerHand("JC 6H JS JD JH").hand_name()
|
||||||
|
'Four of a kind, Jacks'
|
||||||
|
|
||||||
|
>>> PokerHand("3D 2H 3H 2C 2D").hand_name()
|
||||||
|
'Full house, Twos over Threes'
|
||||||
|
|
||||||
|
>>> PokerHand("2H 4D 3C AS 5S").hand_name() # Low ace
|
||||||
|
'Straight, Five-high'
|
||||||
|
|
||||||
|
Source: https://en.wikipedia.org/wiki/List_of_poker_hands
|
||||||
|
"""
|
||||||
|
name = PokerHand._HAND_NAME[self._hand_type - 14]
|
||||||
|
high = PokerHand._CARD_NAME[self._high_card]
|
||||||
|
pair1 = PokerHand._CARD_NAME[self._first_pair]
|
||||||
|
pair2 = PokerHand._CARD_NAME[self._second_pair]
|
||||||
|
if self._hand_type in [22, 19, 18]:
|
||||||
|
return name + f", {high}-high"
|
||||||
|
elif self._hand_type in [21, 17, 15]:
|
||||||
|
return name + f", {pair1}s"
|
||||||
|
elif self._hand_type in [20, 16]:
|
||||||
|
join = "over" if self._hand_type == 20 else "and"
|
||||||
|
return name + f", {pair1}s {join} {pair2}s"
|
||||||
|
elif self._hand_type == 23:
|
||||||
|
return name
|
||||||
|
else:
|
||||||
|
return name + f", {high}"
|
||||||
|
|
||||||
|
def _compare_cards(self, other: "PokerHand") -> str:
|
||||||
|
# Enumerate gives us the index as well as the element of a list
|
||||||
|
for index, card_value in enumerate(self._card_values):
|
||||||
|
if card_value != other._card_values[index]:
|
||||||
|
return "Win" if card_value > other._card_values[index] else "Loss"
|
||||||
|
return "Tie"
|
||||||
|
|
||||||
|
def _get_hand_type(self) -> int:
|
||||||
|
# Number representing the type of hand internally:
|
||||||
|
# 23: Royal flush
|
||||||
|
# 22: Straight flush
|
||||||
|
# 21: Four of a kind
|
||||||
|
# 20: Full house
|
||||||
|
# 19: Flush
|
||||||
|
# 18: Straight
|
||||||
|
# 17: Three of a kind
|
||||||
|
# 16: Two pairs
|
||||||
|
# 15: One pair
|
||||||
|
# 14: High card
|
||||||
|
if self._is_flush():
|
||||||
|
if self._is_five_high_straight() or self._is_straight():
|
||||||
|
return 23 if sum(self._card_values) == 60 else 22
|
||||||
|
return 19
|
||||||
|
elif self._is_five_high_straight() or self._is_straight():
|
||||||
|
return 18
|
||||||
|
return 14 + self._is_same_kind()
|
||||||
|
|
||||||
|
def _is_flush(self) -> bool:
|
||||||
|
return len(self._card_suit) == 1
|
||||||
|
|
||||||
|
def _is_five_high_straight(self) -> bool:
|
||||||
|
# If a card is a five high straight (low ace) change the location of
|
||||||
|
# ace from the start of the list to the end. Check whether the first
|
||||||
|
# element is ace or not. (Don't want to change again)
|
||||||
|
# Five high straight (low ace): AH 2H 3S 4C 5D
|
||||||
|
# Why use sorted here? One call to this function will mutate the list to
|
||||||
|
# [5, 4, 3, 2, 14] and so for subsequent calls (which will be rare) we
|
||||||
|
# need to compare the sorted version.
|
||||||
|
# Refer test_multiple_calls_five_high_straight in test_poker_hand.py
|
||||||
|
if sorted(self._card_values) == [2, 3, 4, 5, 14]:
|
||||||
|
if self._card_values[0] == 14:
|
||||||
|
# Remember, our list is sorted in reverse order
|
||||||
|
ace_card = self._card_values.pop(0)
|
||||||
|
self._card_values.append(ace_card)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _is_straight(self) -> bool:
|
||||||
|
for i in range(4):
|
||||||
|
if self._card_values[i] - self._card_values[i + 1] != 1:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _is_same_kind(self) -> int:
|
||||||
|
# Kind Values for internal use:
|
||||||
|
# 7: Four of a kind
|
||||||
|
# 6: Full house
|
||||||
|
# 3: Three of a kind
|
||||||
|
# 2: Two pairs
|
||||||
|
# 1: One pair
|
||||||
|
# 0: False
|
||||||
|
kind = val1 = val2 = 0
|
||||||
|
for i in range(4):
|
||||||
|
# Compare two cards at a time, if they are same increase 'kind',
|
||||||
|
# add the value of the card to val1, if it is repeating again we
|
||||||
|
# will add 2 to 'kind' as there are now 3 cards with same value.
|
||||||
|
# If we get card of different value than val1, we will do the same
|
||||||
|
# thing with val2
|
||||||
|
if self._card_values[i] == self._card_values[i + 1]:
|
||||||
|
if not val1:
|
||||||
|
val1 = self._card_values[i]
|
||||||
|
kind += 1
|
||||||
|
elif val1 == self._card_values[i]:
|
||||||
|
kind += 2
|
||||||
|
elif not val2:
|
||||||
|
val2 = self._card_values[i]
|
||||||
|
kind += 1
|
||||||
|
elif val2 == self._card_values[i]:
|
||||||
|
kind += 2
|
||||||
|
# For consistency in hand type (look at note in _get_hand_type function)
|
||||||
|
kind = kind + 2 if kind in [4, 5] else kind
|
||||||
|
# first meaning first pair to compare in 'compare_with'
|
||||||
|
first = max(val1, val2)
|
||||||
|
second = min(val1, val2)
|
||||||
|
# If it's full house (three count pair + two count pair), make sure
|
||||||
|
# first pair is three count and if not then switch them both.
|
||||||
|
if kind == 6 and self._card_values.count(first) != 3:
|
||||||
|
first, second = second, first
|
||||||
|
self._first_pair = first
|
||||||
|
self._second_pair = second
|
||||||
|
return kind
|
||||||
|
|
||||||
|
def _internal_state(self) -> Tuple[List[int], Set[str]]:
|
||||||
|
# Internal representation of hand as a list of card values and
|
||||||
|
# a set of card suit
|
||||||
|
trans: dict = {"T": "10", "J": "11", "Q": "12", "K": "13", "A": "14"}
|
||||||
|
new_hand = self._hand.translate(str.maketrans(trans)).split()
|
||||||
|
card_values = [int(card[:-1]) for card in new_hand]
|
||||||
|
card_suit = {card[-1] for card in new_hand}
|
||||||
|
return sorted(card_values, reverse=True), card_suit
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'{self.__class__}("{self._hand}")'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self._hand
|
||||||
|
|
||||||
|
# Rich comparison operators (used in list.sort() and sorted() builtin functions)
|
||||||
|
# Note that this is not part of the problem but another extra feature where
|
||||||
|
# if you have a list of PokerHand objects, you can sort them just through
|
||||||
|
# the builtin functions.
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, PokerHand):
|
||||||
|
return self.compare_with(other) == "Tie"
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if isinstance(other, PokerHand):
|
||||||
|
return self.compare_with(other) == "Loss"
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
if isinstance(other, PokerHand):
|
||||||
|
return self < other or self == other
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
if isinstance(other, PokerHand):
|
||||||
|
return not self < other and self != other
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
if isinstance(other, PokerHand):
|
||||||
|
return not self < other
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return object.__hash__(self)
|
228
project_euler/problem_54/test_poker_hand.py
Normal file
228
project_euler/problem_54/test_poker_hand.py
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
import os
|
||||||
|
from itertools import chain
|
||||||
|
from random import randrange, shuffle
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .sol1 import PokerHand
|
||||||
|
|
||||||
|
SORTED_HANDS = (
|
||||||
|
"4S 3H 2C 7S 5H",
|
||||||
|
"9D 8H 2C 6S 7H",
|
||||||
|
"2D 6D 9D TH 7D",
|
||||||
|
"TC 8C 2S JH 6C",
|
||||||
|
"JH 8S TH AH QH",
|
||||||
|
"TS KS 5S 9S AC",
|
||||||
|
"KD 6S 9D TH AD",
|
||||||
|
"KS 8D 4D 9S 4S", # pair
|
||||||
|
"8C 4S KH JS 4D", # pair
|
||||||
|
"QH 8H KD JH 8S", # pair
|
||||||
|
"KC 4H KS 2H 8D", # pair
|
||||||
|
"KD 4S KC 3H 8S", # pair
|
||||||
|
"AH 8S AS KC JH", # pair
|
||||||
|
"3H 4C 4H 3S 2H", # 2 pairs
|
||||||
|
"5S 5D 2C KH KH", # 2 pairs
|
||||||
|
"3C KH 5D 5S KH", # 2 pairs
|
||||||
|
"AS 3C KH AD KH", # 2 pairs
|
||||||
|
"7C 7S 3S 7H 5S", # 3 of a kind
|
||||||
|
"7C 7S KH 2H 7H", # 3 of a kind
|
||||||
|
"AC KH QH AH AS", # 3 of a kind
|
||||||
|
"2H 4D 3C AS 5S", # straight (low ace)
|
||||||
|
"3C 5C 4C 2C 6H", # straight
|
||||||
|
"6S 8S 7S 5H 9H", # straight
|
||||||
|
"JS QS 9H TS KH", # straight
|
||||||
|
"QC KH TS JS AH", # straight (high ace)
|
||||||
|
"8C 9C 5C 3C TC", # flush
|
||||||
|
"3S 8S 9S 5S KS", # flush
|
||||||
|
"4C 5C 9C 8C KC", # flush
|
||||||
|
"JH 8H AH KH QH", # flush
|
||||||
|
"3D 2H 3H 2C 2D", # full house
|
||||||
|
"2H 2C 3S 3H 3D", # full house
|
||||||
|
"KH KC 3S 3H 3D", # full house
|
||||||
|
"JC 6H JS JD JH", # 4 of a kind
|
||||||
|
"JC 7H JS JD JH", # 4 of a kind
|
||||||
|
"JC KH JS JD JH", # 4 of a kind
|
||||||
|
"2S AS 4S 5S 3S", # straight flush (low ace)
|
||||||
|
"2D 6D 3D 4D 5D", # straight flush
|
||||||
|
"5C 6C 3C 7C 4C", # straight flush
|
||||||
|
"JH 9H TH KH QH", # straight flush
|
||||||
|
"JH AH TH KH QH", # royal flush (high ace straight flush)
|
||||||
|
)
|
||||||
|
|
||||||
|
TEST_COMPARE = (
|
||||||
|
("2H 3H 4H 5H 6H", "KS AS TS QS JS", "Loss"),
|
||||||
|
("2H 3H 4H 5H 6H", "AS AD AC AH JD", "Win"),
|
||||||
|
("AS AH 2H AD AC", "JS JD JC JH 3D", "Win"),
|
||||||
|
("2S AH 2H AS AC", "JS JD JC JH AD", "Loss"),
|
||||||
|
("2S AH 2H AS AC", "2H 3H 5H 6H 7H", "Win"),
|
||||||
|
("AS 3S 4S 8S 2S", "2H 3H 5H 6H 7H", "Win"),
|
||||||
|
("2H 3H 5H 6H 7H", "2S 3H 4H 5S 6C", "Win"),
|
||||||
|
("2S 3H 4H 5S 6C", "3D 4C 5H 6H 2S", "Tie"),
|
||||||
|
("2S 3H 4H 5S 6C", "AH AC 5H 6H AS", "Win"),
|
||||||
|
("2S 2H 4H 5S 4C", "AH AC 5H 6H AS", "Loss"),
|
||||||
|
("2S 2H 4H 5S 4C", "AH AC 5H 6H 7S", "Win"),
|
||||||
|
("6S AD 7H 4S AS", "AH AC 5H 6H 7S", "Loss"),
|
||||||
|
("2S AH 4H 5S KC", "AH AC 5H 6H 7S", "Loss"),
|
||||||
|
("2S 3H 6H 7S 9C", "7H 3C TH 6H 9S", "Loss"),
|
||||||
|
("4S 5H 6H TS AC", "3S 5H 6H TS AC", "Win"),
|
||||||
|
("2S AH 4H 5S 6C", "AD 4C 5H 6H 2C", "Tie"),
|
||||||
|
("AS AH 3H AD AC", "AS AH 2H AD AC", "Win"),
|
||||||
|
("AH AC 5H 5C QS", "AH AC 5H 5C KS", "Loss"),
|
||||||
|
("AH AC 5H 5C QS", "KH KC 5H 5C QS", "Win"),
|
||||||
|
("7C 7S KH 2H 7H", "3C 3S AH 2H 3H", "Win"),
|
||||||
|
("3C 3S AH 2H 3H", "7C 7S KH 2H 7H", "Loss"),
|
||||||
|
("6H 5H 4H 3H 2H", "5H 4H 3H 2H AH", "Win"),
|
||||||
|
("5H 4H 3H 2H AH", "5H 4H 3H 2H AH", "Tie"),
|
||||||
|
("5H 4H 3H 2H AH", "6H 5H 4H 3H 2H", "Loss"),
|
||||||
|
("AH AD KS KC AC", "AH KD KH AC KC", "Win"),
|
||||||
|
("2H 4D 3C AS 5S", "2H 4D 3C 6S 5S", "Loss"),
|
||||||
|
("2H 3S 3C 3H 2S", "3S 3C 2S 2H 2D", "Win"),
|
||||||
|
("4D 6D 5D 2D JH", "3S 8S 3H TC KH", "Loss"),
|
||||||
|
("4S 6C 8S 3S 7S", "AD KS 2D 7D 7C", "Loss"),
|
||||||
|
("6S 4C 7H 8C 3H", "5H JC AH 9D 9C", "Loss"),
|
||||||
|
("9D 9H JH TC QH", "3C 2S JS 5C 7H", "Win"),
|
||||||
|
("2H TC 8S AD 9S", "4H TS 7H 2C 5C", "Win"),
|
||||||
|
("9D 3S 2C 7S 7C", "JC TD 3C TC 9H", "Loss"),
|
||||||
|
)
|
||||||
|
|
||||||
|
TEST_FLUSH = (
|
||||||
|
("2H 3H 4H 5H 6H", True),
|
||||||
|
("AS AH 2H AD AC", False),
|
||||||
|
("2H 3H 5H 6H 7H", True),
|
||||||
|
("KS AS TS QS JS", True),
|
||||||
|
("8H 9H QS JS TH", False),
|
||||||
|
("AS 3S 4S 8S 2S", True),
|
||||||
|
)
|
||||||
|
|
||||||
|
TEST_STRAIGHT = (
|
||||||
|
("2H 3H 4H 5H 6H", True),
|
||||||
|
("AS AH 2H AD AC", False),
|
||||||
|
("2H 3H 5H 6H 7H", False),
|
||||||
|
("KS AS TS QS JS", True),
|
||||||
|
("8H 9H QS JS TH", True),
|
||||||
|
)
|
||||||
|
|
||||||
|
TEST_FIVE_HIGH_STRAIGHT = (
|
||||||
|
("2H 4D 3C AS 5S", True, [5, 4, 3, 2, 14]),
|
||||||
|
("2H 5D 3C AS 5S", False, [14, 5, 5, 3, 2]),
|
||||||
|
("JH QD KC AS TS", False, [14, 13, 12, 11, 10]),
|
||||||
|
("9D 3S 2C 7S 7C", False, [9, 7, 7, 3, 2]),
|
||||||
|
)
|
||||||
|
|
||||||
|
TEST_KIND = (
|
||||||
|
("JH AH TH KH QH", 0),
|
||||||
|
("JH 9H TH KH QH", 0),
|
||||||
|
("JC KH JS JD JH", 7),
|
||||||
|
("KH KC 3S 3H 3D", 6),
|
||||||
|
("8C 9C 5C 3C TC", 0),
|
||||||
|
("JS QS 9H TS KH", 0),
|
||||||
|
("7C 7S KH 2H 7H", 3),
|
||||||
|
("3C KH 5D 5S KH", 2),
|
||||||
|
("QH 8H KD JH 8S", 1),
|
||||||
|
("2D 6D 9D TH 7D", 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
TEST_TYPES = (
|
||||||
|
("JH AH TH KH QH", 23),
|
||||||
|
("JH 9H TH KH QH", 22),
|
||||||
|
("JC KH JS JD JH", 21),
|
||||||
|
("KH KC 3S 3H 3D", 20),
|
||||||
|
("8C 9C 5C 3C TC", 19),
|
||||||
|
("JS QS 9H TS KH", 18),
|
||||||
|
("7C 7S KH 2H 7H", 17),
|
||||||
|
("3C KH 5D 5S KH", 16),
|
||||||
|
("QH 8H KD JH 8S", 15),
|
||||||
|
("2D 6D 9D TH 7D", 14),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_random_hand():
|
||||||
|
play, oppo = randrange(len(SORTED_HANDS)), randrange(len(SORTED_HANDS))
|
||||||
|
expected = ["Loss", "Tie", "Win"][(play >= oppo) + (play > oppo)]
|
||||||
|
hand, other = SORTED_HANDS[play], SORTED_HANDS[oppo]
|
||||||
|
return hand, other, expected
|
||||||
|
|
||||||
|
|
||||||
|
def generate_random_hands(number_of_hands: int = 100):
|
||||||
|
return (generate_random_hand() for _ in range(number_of_hands))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("hand, expected", TEST_FLUSH)
|
||||||
|
def test_hand_is_flush(hand, expected):
|
||||||
|
assert PokerHand(hand)._is_flush() == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("hand, expected", TEST_STRAIGHT)
|
||||||
|
def test_hand_is_straight(hand, expected):
|
||||||
|
assert PokerHand(hand)._is_straight() == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("hand, expected, card_values", TEST_FIVE_HIGH_STRAIGHT)
|
||||||
|
def test_hand_is_five_high_straight(hand, expected, card_values):
|
||||||
|
player = PokerHand(hand)
|
||||||
|
assert player._is_five_high_straight() == expected
|
||||||
|
assert player._card_values == card_values
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("hand, expected", TEST_KIND)
|
||||||
|
def test_hand_is_same_kind(hand, expected):
|
||||||
|
assert PokerHand(hand)._is_same_kind() == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("hand, expected", TEST_TYPES)
|
||||||
|
def test_hand_values(hand, expected):
|
||||||
|
assert PokerHand(hand)._hand_type == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("hand, other, expected", TEST_COMPARE)
|
||||||
|
def test_compare_simple(hand, other, expected):
|
||||||
|
assert PokerHand(hand).compare_with(PokerHand(other)) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("hand, other, expected", generate_random_hands())
|
||||||
|
def test_compare_random(hand, other, expected):
|
||||||
|
assert PokerHand(hand).compare_with(PokerHand(other)) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_hand_sorted():
|
||||||
|
POKER_HANDS = [PokerHand(hand) for hand in SORTED_HANDS]
|
||||||
|
list_copy = POKER_HANDS.copy()
|
||||||
|
shuffle(list_copy)
|
||||||
|
user_sorted = chain(sorted(list_copy))
|
||||||
|
for index, hand in enumerate(user_sorted):
|
||||||
|
assert hand == POKER_HANDS[index]
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_sort_five_high_straight():
|
||||||
|
# Test that five high straights are compared correctly.
|
||||||
|
pokerhands = [PokerHand("2D AC 3H 4H 5S"), PokerHand("2S 3H 4H 5S 6C")]
|
||||||
|
pokerhands.sort(reverse=True)
|
||||||
|
assert pokerhands[0].__str__() == "2S 3H 4H 5S 6C"
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_calls_five_high_straight():
|
||||||
|
# Multiple calls to five_high_straight function should still return True
|
||||||
|
# and shouldn't mutate the list in every call other than the first.
|
||||||
|
pokerhand = PokerHand("2C 4S AS 3D 5C")
|
||||||
|
expected = True
|
||||||
|
expected_card_values = [5, 4, 3, 2, 14]
|
||||||
|
for _ in range(10):
|
||||||
|
assert pokerhand._is_five_high_straight() == expected
|
||||||
|
assert pokerhand._card_values == expected_card_values
|
||||||
|
|
||||||
|
|
||||||
|
def test_euler_project():
|
||||||
|
# Problem number 54 from Project Euler
|
||||||
|
# Testing from poker_hands.txt file
|
||||||
|
answer = 0
|
||||||
|
script_dir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
poker_hands = os.path.join(script_dir, "poker_hands.txt")
|
||||||
|
with open(poker_hands, "r") as file_hand:
|
||||||
|
for line in file_hand:
|
||||||
|
player_hand = line[:14].strip()
|
||||||
|
opponent_hand = line[15:].strip()
|
||||||
|
player, opponent = PokerHand(player_hand), PokerHand(opponent_hand)
|
||||||
|
output = player.compare_with(opponent)
|
||||||
|
if output == "Win":
|
||||||
|
answer += 1
|
||||||
|
assert answer == 376
|
Loading…
Reference in New Issue
Block a user