problem https projecteuler netproblem54 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 onethousand 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 orgwikitexashold27em https en wikipedia orgwikilistofpokerhands similar problem on codewars https www codewars comkatarankingpokerhands https www codewars comkatasortablepokerhands create an object representing a poker hand based on an input of a string which represents the best 5card combination from the player s hand and board cards attributes readonly hand a string representing the hand consisting of five cards methods comparewithopponent 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 the opponent s hand handname 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 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 valuecard suit the first character is the value of the card 2 3 4 5 6 7 8 9 ten jack queen king ace the second character represents the suit spades hearts diamonds clubs for example 6s 4c kc as th split removes duplicate whitespaces so no need of strip returns the self hand return self hand def comparewithself other pokerhand str 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 nonzero 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 handtype other handtype return win elif self handtype other handtype return loss elif self firstpair other firstpair if self secondpair other secondpair return self comparecardsother else return win if self secondpair other secondpair else loss return win if self firstpair other firstpair else loss this function is not part of the problem i did it just for fun def handnameself str name pokerhand handnameself handtype 14 high pokerhand cardnameself highcard pair1 pokerhand cardnameself firstpair pair2 pokerhand cardnameself secondpair if self handtype in 22 19 18 return name f highhigh elif self handtype in 21 17 15 return name f pair1s elif self handtype in 20 16 join over if self handtype 20 else and return name f pair1s join pair2s elif self handtype 23 return name else return name f high def comparecardsself other pokerhand str enumerate gives us the index as well as the element of a list for index cardvalue in enumerateself cardvalues if cardvalue other cardvaluesindex return win if cardvalue other cardvaluesindex else loss return tie def gethandtypeself 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 isflush if self isfivehighstraight or self isstraight return 23 if sumself cardvalues 60 else 22 return 19 elif self isfivehighstraight or self isstraight return 18 return 14 self issamekind def isflushself bool return lenself cardsuit 1 def isfivehighstraightself 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 testmultiplecallsfivehighstraight in testpokerhand py if sortedself cardvalues 2 3 4 5 14 if self cardvalues0 14 remember our list is sorted in reverse order acecard self cardvalues pop0 self cardvalues appendacecard return true return false def isstraightself bool for i in range4 if self cardvaluesi self cardvaluesi 1 1 return false return true def issamekindself 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 range4 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 cardvaluesi self cardvaluesi 1 if not val1 val1 self cardvaluesi kind 1 elif val1 self cardvaluesi kind 2 elif not val2 val2 self cardvaluesi kind 1 elif val2 self cardvaluesi kind 2 for consistency in hand type look at note in gethandtype function kind kind 2 if kind in 4 5 else kind first meaning first pair to compare in comparewith first maxval1 val2 second minval1 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 cardvalues countfirst 3 first second second first self firstpair first self secondpair second return kind def internalstateself tuplelistint setstr 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 newhand self hand translatestr maketranstrans split cardvalues intcard 1 for card in newhand cardsuit card1 for card in newhand return sortedcardvalues reversetrue cardsuit def reprself return f self classself hand def strself 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 eqself other if isinstanceother pokerhand return self comparewithother tie return notimplemented def ltself other if isinstanceother pokerhand return self comparewithother loss return notimplemented def leself other if isinstanceother pokerhand return self other or self other return notimplemented def gtself other if isinstanceother pokerhand return not self other and self other return notimplemented def geself other if isinstanceother pokerhand return not self other return notimplemented def hashself return object hashself def solution int solution for problem number 54 from project euler input from pokerhands txt file answer 0 scriptdir os path abspathos path dirnamefile pokerhands os path joinscriptdir pokerhands txt with openpokerhands as filehand for line in filehand playerhand line 14 strip opponenthand line15 strip player opponent pokerhandplayerhand pokerhandopponenthand output player comparewithopponent if output win answer 1 return answer if name main solution 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 a 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 the opponent s hand hand_name returns a string made up of two parts hand name and high card supported operators rich comparison operators supported built in methods and functions list sort sorted placeholder as tuples are zero indexed 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 split removes duplicate whitespaces so no need of strip returns the self hand 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 this function is not part of the problem i did it just for fun 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 enumerate gives us the index as well as the element of a list 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 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 remember our list is sorted in reverse order 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 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 for consistency in hand type look at note in _get_hand_type function first meaning first pair to compare in compare_with 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 internal representation of hand as a list of card values and a set of card suit 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 solution for problem number 54 from project euler input from poker_hands txt file
from __future__ import annotations

import os


class PokerHand:
    

    _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 = (
        "",  
        "One",
        "Two",
        "Three",
        "Four",
        "Five",
        "Six",
        "Seven",
        "Eight",
        "Nine",
        "Ten",
        "Jack",
        "Queen",
        "King",
        "Ace",
    )

    def __init__(self, hand: str) -> None:
        
        if not isinstance(hand, str):
            msg = f"Hand should be of type 'str': {hand!r}"
            raise TypeError(msg)
        
        if len(hand.split(" ")) != 5:
            msg = f"Hand should contain only 5 cards: {hand!r}"
            raise ValueError(msg)
        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):
        
        return self._hand

    def compare_with(self, other: PokerHand) -> str:
        
        
        
        
        

        
        
        
        
        
        
        
        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"

    
    def hand_name(self) -> str:
        
        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:
        
        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:
        
        
        
        
        
        
        
        
        
        
        
        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 sorted(self._card_values) == [2, 3, 4, 5, 14]:
            if self._card_values[0] == 14:
                
                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 = val1 = val2 = 0
        for i in range(4):
            
            
            
            
            
            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
        
        kind = kind + 2 if kind in [4, 5] else kind
        
        first = max(val1, val2)
        second = min(val1, val2)
        
        
        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]]:
        
        
        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

    
    
    
    
    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)


def solution() -> int:
    
    
    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) 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
    return answer


if __name__ == "__main__":
    solution()