I've never played Poker and don't think I ever will because I'm not a fan of gambling and placing bets. However, I ran into an interesting problem on Project Euler that led me to write a poker hand analyser to determine the rank of each hand.
Before writing this article, I didn't know anything about cards or Poker, I had to do some research on Wikipedia about it. So, forgive me if there's any information that's not accurate in the article.
Poker Hands
From what I had understood, a hand is a set of five cards and each card has a rank, which is in the order shown below:
Cards are valued in the order of lowest to highest (Left to Right):
2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, King, Ace
Based on the card pattern formed in each hand, the ranking category is determined and it's ranked within it's category based on the ranks of it's cards.
Hand Ranking Categories
In Poker, there are about 10 ranking categories from lowest to highest:
- High Card
- One Pair
- Two Pairs
- Three of a Kind
- Straight
- Flush
- Full House
- Four of a Kind
- Straight Flush
- Royal Flush
Before diving into the code snippets, I wrote a library named poker_lib.py which contains all the methods used in the code snippets.
To make things simple, I created a class named Card that has two attributes, face and suit, with namedtuple() as it's datatype.
High Card
This hand contains no pairs and it doesn't fall into any other category.
def high_card(hand):
# collect all faces from each card
allfaces = [f for f,s in hand]
#sort the faces and show the highest card
return "high_card", sorted(allfaces, key=lambda f: allfaces.index(f), reverse=True)[0]
One Pair
This hand contains two cards of one rank and three cards of three other ranks.
def one_pair(hand):
allfaces = [f for f,s in hand]
allftypes = set(allfaces)
# collect pairs
pairs = [f for f in allftypes if allfaces.count(f) == 2]
# if there's more than one pair
if len(pairs) != 1:
return False
allftypes.remove(pairs[0])
return 'one-pair', pairs + sorted(allftypes, key=lambda f: face.index(f), reverse=True)
Two Pairs
This hand contains two cards of one rank, two cards of a second rank and one card of a third rank.
def two_pair(hand):
allfaces = [f for f,s in hand]
allftypes = set(allfaces)
# collect pairs
pairs = [f for f in allftypes if allfaces.count(f) == 2]
# if there are more than two pairs
if len(pairs) != 2:
return False
p1, p2 = pairs
# get the difference using sets
other_cards = [(allftypes - set(pairs)).pop()]
return 'two-pair', pairs + other_cards if(face.index(p1) > face.index(p2)) else pairs[::-1] + other_cards
Three of a Kind
This hand, also known as trips or a set, contains three cards of one rank and two cards of two other ranks.
def three_of_a_kind(hand):
allfaces = [f for f,s in hand]
uniqueRanks = set(allfaces)
if len(uniqueRanks) != 3:
return False
for f in uniqueRanks:
if allfaces.count(f) == 3:
uniqueRanks.remove(f)
return "three-of-a-kind", f
return False;
Straight
This hand contains five cards arranged in a sequential order but not all of them have same suits.
def straight(hand):
ordered = sorted(hand, key=lambda card: (faces.index(card.face), card.suit))
if ''.join(card.face for card in ordered) in ''.join(face):
return 'straight', ordered[-1].face
return False;
Flush
This hand contains five cards of the same suit and not necessarily arranged in sequential order.
def flush(hand):
allfaces = [f for f,s in hand]
first_card = hand[0]
other_cards = hand[1:]
if all(first_card.suit == card.suit for card in other_cards):
return 'flush', sorted(allfaces, key=lambda f: face.index(f), reverse=True)
return False
Full House
This hand, also known as full boat or a boat, contains three cards of one rank and two cards of another rank.
def full_house(hand):
allfaces = [f for f,s in hand]
rankFrequency = pe_lib.character_frequency(allfaces)
# if there are 2 types of ranks and there's a card with 1 pair and 3 of a kind
if len(rankFrequency) == 2 and (rankFrequency.values()[0] == 2 and rankFrequency.values()[1] == 3):
return 'full-house'
return False
Four of a Kind
This hand, also known as quads, contains four cards of one rank and one card of another rank.
def four_of_a_kind(hand):
allfaces = [f for f,s in hand]
# create a unique set of ranks
uniqueRanks = set(allfaces)
# if there are more than 2 ranks, it's not four of a kind
if len(uniqueRanks) != 2:
return False
for f in uniqueRanks:
# if there are 4 faces, it is four of a kind
if allfaces.count(f) == 4:
uniqueRanks.remove(f)
return "four-of-a-kind", f
return False
Straight Flush
This hand contains five cards arranged in a sequential order with all cards having the same suit.
def straight_flush(hand):
# sort the cards based on the face rank of each card
ordered = sorted(hand, key=lambda card: (faces.index(card.face), card.suit))
first_card = ordered[0]
other_cards = ordered[1:]
# check if all are of the same suit
if all(first_card.suit == card.suit for card in other_cards):
# check if they are in sequential order
# compare the ordered faces substring with the face list (which is converted to string)
if ''.join(card.face for card in ordered) in ''.join(face):
return 'straight-flush', ordered[-1].face
return False
Royal Flush
This hand contains the royal ranks in sequential order in the same suit.
def royal_flush(hand):
royalface = "TJQKA"
# sort the cards based on the face rank of each card
ordered = sorted(hand, key=lambda card: (faces.index(card.face), card.suit))
first_card = ordered[0]
other_cards = ordered[1:]
# check if all are of the same suit
if all(first_card.suit == card.suit for card in other_cards):
# check if they are in sequential order
# compare the ordered faces substring with the face list (which is converted to string)
if ''.join(card.face for card in ordered) in royalface:
return 'royal-flush', ordered[-1].face
return False
Conclusion
It was a fun project to work on and I learnt new styles of array and string manipulation techniques using Python.
Inspired by this, I'm planning to create an interactive version of this project using Javascript and talk about it on another article.
The code for this program can be found in my GitHub repository.
Hope you liked reading this article!
Stay tuned for more!