Dwi Wahyudi

Senior Software Engineer (Ruby, Golang, Java)


After getting 5 cards from deck, we can then calculate the values based on holdem poker rule.

Overview

https://en.wikipedia.org/wiki/Texas_hold_%27em

My approach for this is quite simple, create a class, for calculating those 5 cards. Check the cards for each hand value with if else and the checking is prioritized by highest values first. So if a hand is already Royal Flush, we don’t need to check other hand values.

module HoldemPoker
  class HandValuesCalculator
    ROYAL_FLUSH_RANKS = [10, 11, 12, 13, 1].to_set

    def initialize(cards)
      @cards = cards
    end

    def perform
      raise 'Must supply 5 cards.' if @cards.length != 5

      if royal_flush?
        'Royal Flush'
      elsif straight_flush?
        'Straight Flush'
      elsif four_of_a_kind?
        'Four of a Kind'
      elsif full_house?
        'Full House'
      elsif flush?
        'Flush'
      elsif straight?
        'Straight'
      elsif three_of_a_kind?
        'Three of a Kind'
      elsif two_pairs?
        'Two Pairs'
      elsif pair?
        'Pair'
      else
        'Highest Card'
      end
    end

    private
    
    # ... Private methods for checking the values.
  end 
end

Calculating Hands

Now let’s write the private methods for checking the hands for each hand value.

Royal Flush

Royal Flush

Royal Flush example is like 10♣ J♣ Q♣ K♣ A♣. It is the highest hand value in holdem poker. In order for a hand to have this value, cards must be in the same suit, and they must have 10, J, Q, K and A.

Before creating royal_flush? method, let’s create a method to check if cards have same suit.

    def same_suit?
      suits = @cards.map(&:suit).uniq
      suits.length == 1
    end

Then we create new method values which gathers all of values from the cards.

    def values
      @cards.map(&:value)
    end

Then we can use those 2 methods to check if the hand is royal flush.

    def royal_flush?
      return false if !same_suit?

      ranks_set = values.to_set
      ROYAL_FLUSH_RANKS == ranks_set
    end

NB. Set data will ignore order.

Straight Flush

Straight Flush

Straight Flush is basically a hand value where cards are in the same suit, and is sequential/continuous but not Royal Flush.

There are 2 kinds of straight: the normal one, and the ace one. Straight with ace one is Royal Flush, this Straight Flush is only normal straight (without A / Ace).

We will create a method to check whether a hand is normal straight.

But, do note that a straight hand value must not contain duplicated rank/value. The highest value is 4 values higher than the lowest value.

    def contain_duplicate_value?
      values.uniq.length < 5
    end

    def normal_values_straight?
      return false if contain_duplicate_value?

      sorted_values = values.sort
      (values.sum % 5 == 0) && (sorted_values[4] - sorted_values[0] == 4)
    end

After creating above methods, we can now create straight_flush? method.

    def straight_flush?
      return false if !same_suit?

      normal_values_straight?
    end

Four of a Kind

Four of a Kind

Four of a Kind is where we have 4 cards with the same rank.

Before we create the method, we will need to write another method in order to help us to group cards by values. This is in order to identify card-pairing.

    def grouped_by_values
      grouped_by_values = @cards.group_by(&:value)
      grouped_by_values.map { |value, cards| cards.length }.sort
    end

In newest Ruby version (2.7), we can use Enumerable#tally, https://ruby-doc.org/core-2.7.0/Enumerable.html#method-i-tally

But since I’m still using older version of Ruby, that method will do.

We can then create four_of_a_kind? method.

    def four_of_a_kind?
      full_house_set = [1, 4].sort
      grouped_by_values == full_house_set
    end

Full House

Full House

Full House is where we have 3 cards with the same rank, and another 2 cards with another same rank as well.

  • 3 Kings and 2 7s.
  • 3 10s and 2 Aces.
  • 3 2s and 2 8s.
  • etc.

Since we already have grouped_by_values method above, checking Full House is easy.

    def full_house?
      full_house_set = [2, 3].sort
      grouped_by_values == full_house_set
    end

Flush

Flush

Flush is when we have all 5 cards to have same suit.

We already have same_suit? method above.

    def flush?
      same_suit?
    end

Straight

Straight

Straight is when all 5 cards are sequential/continuous, but doesn’t have the same suit. We already have normal_values_straight? method which checks straight without Ace.

Now we will create a method to check straight with Ace. Like straight without Ace, straight with Ace, all 5 cards must have unique rank. Unlike normal straight which has 4 values difference between highest and lowest values, straight with Ace has 12, because King has 13 value and Ace has 1 value. 13 - 1 = 12. And all values summed together must be 47.

    def upper_ace_straight
      return false if contain_duplicate_value?

      sorted_values = values.sort
      values.sum == 47 && (sorted_values[4] - sorted_values[0] == 12)
    end

Now we can create straight? method.

    def straight?
      normal_values_straight? || upper_ace_straight
    end

Three of a Kind

Three of a Kind

Three of a Kind is where we have 3 cards with the same rank.

We already have grouped_by_values method above. So now we can easily create three_of_a_kind? method.

   def three_of_a_kind?
      three_of_a_kind_set = [1, 1, 3].sort
      grouped_by_values == three_of_a_kind_set
    end

Two Pairs

Two Pairs

Then two_pairs? method.

    def two_pairs?
      two_pairs_set = [2, 2, 1].sort
      grouped_by_values == two_pairs_set
    end

One Pair

One Pair

And finally pair? method, which check if hand is One Pair.

    def pair?
      pair_set = [2, 1, 1, 1].sort
      grouped_by_values == pair_set
    end

References

https://en.wikipedia.org/wiki/Texas_hold_%27em