A fast Texas Hold’em hand strength calculator – part 1

I love to come back to old algorithm problems and try to solve them better then i did before. A good example of this is a poker hand strength calculator. If you played poker before you know that a poker hand is computed over five cards and its pretty simple to quickly decide who won.

It’s simple to write a function that determines the strength, but in order to make it really fast, you need to thinker a bit. You do not only need to calculate the rank of the hand (high card, pair, etc up to straight flush) but also between the same rank which hand is the better.

There is a total of 2,598,960 different hands in poker (order of cards doesn’t matter so = the number of combinations of 52 cards taken 5 at a time)

Rank Occurrences
Straight Flush 40
Four of a Kind 624
Full House 3744
Flush 5108
Straight 10,200
Three of a Kind 54,912
Two Pair 123,552
One Pair 1,098,240
High Card 1,302,540

Just to remind you the card values are 2, 3, 4, 5, 6, 7, 8, 9, T, J, Q, K, A and they come in four colors (s-spade, h-heart, c-club, d-diamond).

We have the a few trickier requirements,

  1. [7♣, 9♠, J♥, 4♦, J♠] should be equal to [J♥, 7♣, 9♠, 4♦, J♠]
  2. A sequence of 2, 3, 4, 5, A should be correctly identified as a Straight
  3. [7♣, 9♠, J♥, 4♦, J♠] must have a smaller value than [7♣, 9♠, J♥, K♦, J♠]
class SimpleHandEvaluator
{
	public int Eval(int[] cards)
	{
		var o = new int[13];
		var v = new int[5];
		var c = new int[5];
		for (int i = 0; i < cards.Length; i++)
		{
			v[i] = cards[i] % 13; // values
			c[i] = cards[i] / 13; // colors
			o[v[i]]++;            // occurances
		}
		v = v.OrderByDescending(x => (o[x] << 4) | x)
			.ToArray();
		switch (o[v[0]])
		{
			case 4:  // four of a kind
				return FinalValue(7, v);
			case 3:  // full house, three of a kind
				return FinalValue(o[v[3]] * 3, v);
			case 2:  // two pair, pair
				return FinalValue(o[v[2]], v);
			default: // no pairs detected
				var isColor = 
					c[0] == c[1] &&
					c[1] == c[2] &&
					c[2] == c[3] &&
					c[3] == c[4];
				var isLargeStraight = 
					v[0] == v[1] + 1 &&
					v[1] == v[2] + 1 &&
					v[2] == v[3] + 1 &&
					v[3] == v[4] + 1;
				var isSmallStraight = 
					!isLargeStraight &&
					v[0] == 12 &&
					v[1] == 3 &&
					v[2] == 2 &&
					v[3] == 1 &&
					v[4] == 0;
				var isStraight = isSmallStraight | isLargeStraight;
				if (isStraight && isColor) // straight flush
					return FinalValue(8, v);
				else if (isColor)  // flush
					return FinalValue(5, v);
				else if (isLargeStraight) // straight
					return FinalValue(4, v);
				else if (isSmallStraight) // small straight
					return FinalValue(4, new [] { 4, 3, 2, 1, 12 });
				else // highcard
					return FinalValue(0, v);
		}
	}

	private static int FinalValue(int handValue, int[] v)
	{
		return 
			(handValue << 20) | 
			(v[0] << 16) | 
			(v[1] << 12) | 
			(v[2] << 8) | 
			(v[3] << 4) | 
			(v[4]);
	}
}

Lets examine the code a bit. First we go trough the cards and we split the data into value, color and occurrence information. We order the cards by occurrence and value. This will help us to fulfill each of our requirements. After this we fall basically into two bigger categories.

  • The highest number of card occurrence is 1. This mean we either have High Card, Straight, Flush, or Straight Flush. So the code in the default block is executed
  • There is a card repetition. This means we have either a Pair, Two Pairs, Three of a Kind, Full House, or Four of a kind. Case 2,3,4 handles these scenarios.

No matter in which of the cases we are, we factor in the hand strength and then we proceed to factor in the cards values. This will take care of requirement 3 and it’s happening in the FinalValue function. There we form integer values in the form of 0x[strength, value1, value2, value3, value4, value5]. Examining how requirement 3 is handled we see that for the first hand we come up with the value 0x199752, while for the second hand 0x199B75. Thus hand 2 is stronger than hand 1.

This piece of code runs trough all the 2.6 million combination in roughly 1200 milliseconds. I know you could shave of a few millisecond there and there, but wait for part two where i show you a method with which we could double or even triple our speed. Stay tuned!