Back

Published on March 30th, 2019

Algorithms

# Poker Hand Analyser in Python

An algorithm that parses a five-card poker hand and determines it's rank.

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:

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.