Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Fast, Texas Holdem Hand Evaluation and Analysis

4.89/5 (98 votes)
18 Apr 2006GPL313 min read 17   18.7K  
A C# native, fast Texas Holdem Hand Evaluator.

Hand Odds Application Image

Introduction

Recently, I was laid up with a prolonged illness. During that time, I needed to entertain myself. I quickly determined that daytime TV was not worth watching. Instead, I started playing Texas Holdem online. A few thousand hands later, I determined that the medication I was taking was interfering with my ability to concentrate. This limited the skill level that I could consistently play at. So instead of continuing to play Texas Holdem poorly, I decided to write a software to analyze poker.

The first thing I realized I needed was a fast way to iterate through and evaluate Texas Holdem hands. After searching the net and trying several libraries, I found that nothing met my needs. Native C# code was too slow, and the best C library (poker-eval) was very difficult to connect to C# using interop. So I started writing my own C# library.

After a few days of work, I was able to evaluate a few hundred thousand hands a second. This was several orders of magnitude slower than I needed. So I looked at poker-eval again. After working through this gnarly, heavily macroed and table driven C library, I realized that the techniques they were using should work with C#. I ported the portion of the tables and evaluation functions that I needed to analyze Texas Holdem. The resulting code is definitely fast enough for most of my needs.

The following article walks through using my C# variant of the poker.eval hand evaluator library, and includes a few of the nifty tools I built with it. These tools include:

  • A fast Texas Holdem hand evaluator class library with a benchmark application.
  • A vector card image class for displaying high quality, scalable card faces.
  • A Hand Odds application that displays hand probability like you see on TV Poker shows.
  • An Odds Grid application that gives the chances of a hand winning against an average opponent.
  • A Jacks or Better Video Poker trainer. This program helps to learn how to play Jacks or Better Video poker perfectly.

Taking from the Best

When analyzing poker hands, doing an exhaustive search quickly is much more straightforward and more accurate than having to do a Monte Carlo simulation or coming up with some clever heuristic. The Poker-eval library appears to be one of the fastest and the most used Poker libraries around.

Poker-eval can be found at the poker-eval web page. It is a very fast, heavily macroed C library. It has limited documentation and is very gnarly (which means it's not terribly programmer friendly), but it is very, very fast -- which makes up for most other deficiencies.

On the other hand, most object oriented poker hand evaluation libraries like to have classes for card decks, individual cards, and poker hands. These abstractions, though nice for human programmers, really get in the way of making a fast library. Lots of objects have to be created and deleted to do even simple analysis. This makes this style of a library slow.

Poker-eval dispenses with all of this object oriented non-sense and defines everything as bit-fields in unsigned integers. This may not be the clearest way for humans to represent this information, but for computers it's natural, and in this case natural means fast.

Ease of Use

My second goal (after making it fast) was to make it easy to use this class for simple things. The class is built by wrapping the underlying, fast, static methods with a C# class. This makes the code easy to use without having to get into the internal details of the implementation. However, the underlying static methods can still be accessed for more advanced usage.

The following example shows how to create instances of two Texas Holdem hands, prints out a description of each hand, and then compares the two hands:

C#
using System;
using HoldemHand;

// Simple example of comparing hands using Hand instances.
class Program
{
    static void Main(string[] args)
    {
        string board = "4d 5d 6c 3c 2d";
        Hand player1 = new Hand("ac as", board);
        Hand player2 = new Hand("ad ks", board);

        // Print out a hand description: in this case both hands are
        // A straight, six high
        Console.WriteLine("Player1 Hand: {0}", player1.Description);
        Console.WriteLine("Player2 Hand: {0}", player2.Description);

        // Compare hands
        if (player1 == player2)
        {
            Console.WriteLine("player1's hand is" + 
                              " equal to player2's hand");
        } 
        else if (player1 > player2)
        {
            Console.WriteLine("player1's hand is " + 
                              "greater than player2's hand");
        }
        else
        {
            Console.WriteLine("player2's hand is greater" + 
                              " than or equal player1's hand");
        }
    }
}

Make it Fast

My primary goal when porting Poker-eval to C# was to make a very fast native C# poker hand evaluator that does not require any interop.

Currently, using custom iteration, I can get between 23 million and 33 million hand evaluations per second on my laptop computer (and even more on my desktop computer) depending on the number of cards in each hand.

If I use C# iterator functions, I get about half that speed, but the C# iterator functions are much easier to use than hand in-lined iterations.

Benchmark Application Image

By the way, you must run the benchmark twice to get good results. The first time, you are benchmarking the JIT time too. The second run produces more reasonable results.

The Poker-eval Model

The Poker-eval defines two basic kinds of masks. One is a hand mask. This consists of a bit field containing 52 bits, one bit for each card. A hand is represented by ORing each card bit to create a definition of a hand.

The second kind of mask is called a hand value. A hand value is produced by passing a hand mask into a Poker-eval evaluation function. A hand value can be compared, using standard integer comparison operators, with other hand values to determine whether one hand beats another.

To make the code fast, I've preserved the notion of a hand mask and a hand value. Most of the high speed code is defined in static member functions found in the HoldemHand.Hand class. The following code snippet shows how to parse a string description of a hand into a hand mask and then evaluate the hand mask to create a hand value:

C#
using System;
using HoldemHand;

// Simple example of parsing and evaluating a Texas Holdem hand.
class Program
{
    static void Main(string[] args)
    {
        // Parse hand into a hand mask
        ulong handmask = Hand.ParseHand("ac as 4d 5d 6c 7c 8d");

        // Convert hand mask into a compariable hand value.
        uint handval = Hand.Evaluate(handmask, 7);

        // Print a description of the hand.
        Console.WriteLine("Hand: {0}", 
                Hand.DescriptionFromHandValue(handval));
    }
}

Hand Iteration

Iterating through hands simply requires toggling all of the possible 7 bit combinations for 7 card hands or 5 bit combinations for 5 card hands. To assist in this task, C# iterators can be used to simplify the process.

There are two iterator functions defined:

C#
static IEnumerable Hand.Hands(int numberOfCards)

and

C#
static IEnumerable Hand.Hands(ulong shared, ulong dead, int numberOfCards)

The Hand.Hands(int numberOfCards) method iterates through all possible hand mask combinations for a given number of cards.

The Hand.Hands(ulong shared, ulong dead, int numberOfCards) method iterates through all possible hand mask combinations for the specified number of cards, where the hand mask shares the cards passed in the shared argument and must not contain cards in from the dead argument.

C#
using System;
using HoldemHand;

// Simple example of iteration using Holdem.Hand class.
class Program
{
    static void Main(string[] args)
    {
        long count = 0;

        /// Parse hand and create a mask
        ulong partialHandmask = Hand.ParseHand("ac as 4d 5d 6c");

        /// iterate through all 7 card hands
        /// that share the cards in our partial hand.
        foreach (ulong handmask in Hand.Hands(partialHandmask, 0UL, 7))
        {
            count++;
        }

        Console.WriteLine("Total hands to check: {0}", count);
    }
}

Though the C# iterator functions are simple to use and reasonably fast for many things, they aren't as fast as inlining the equivalent loops.

The equivalent code with the iteration code inlined looks like the following:

C#
using System;
using HoldemHand;

// Simple inlined iteration using Holdem.Hand class.
class Program
{
    static void Main(string[] args)
    {
        int     _i1, _i2;
        ulong   _card1, _card2, dead, handmask;
        long    count = 0;

        /// Parse hand and create a mask
        ulong partialHandmask = Hand.ParseHand("ac as 4d 5d 6c");

        // We should not repeat cards we already hold.
        dead = partialHandmask;

        // Loop through the all remaining two card combinations.
        for (_i1 = Hand.NumberOfCards - 1; _i1 >= 0; _i1--)
        {
            _card1 = (1UL << _i1);
            if ((dead & _card1) != 0) continue; // Ignore dead cards
            for (_i2 = _i1 - 1; _i2 >= 0; _i2--)
            {
                _card2 = (1UL << _i2);
                if ((dead & _card2) != 0) continue; // Ignore dead cards
                // OR back our 5 card partial hand mask to make a seven card hand.
                handmask = _card1 | _card2 | partialHandmask;
                count++;
            }
        }

        Console.WriteLine("Total hands to check: {0}", count);
    }
}

Though much uglier than using C# iterator methods, this inlined loop is usually 2x to 3x times faster.

Building a Hand Odds Calculator

Hand Odds Application Image

If you've watched poker on TV, you've seen the on-screen display of the odds each player has for winning the hand. This application makes the same calculations used on those TV shows.

The underlying algorithm is extremely simple. All possible board combinations are iterated through. For each board combination, a hand mask is calculated for each set of pocket cards. The hand masks are evaluated and then compared. The winners are tallied for each player. The probability is determined by simply dividing the winning hands for each player by the total number of hands.

The following code illustrates how this is calculated:

C#
using System;
using HoldemHand;

// Iterate through all possible hands
// to see how these two hands compare
class Program
{
    static void Main(string[] args)
    {
        // Create hand masks
        ulong player1Mask = Hand.ParseHand("as ks");
        ulong player2Mask = Hand.ParseHand("jd jc");
        ulong deadcards = Hand.ParseHand("2h 8s");
        long player1Wins = 0, player2Wins = 0;
        long count = 0;

        // Iterate through all possible boards
        foreach (ulong board in Hand.Hands(0UL, 
                       deadcards | player1Mask | player2Mask, 5))
        {
            // Create a hand value for each player
            uint player1HandValue = Hand.Evaluate(board | player1Mask, 7);
            uint player2HandValue = Hand.Evaluate(board | player2Mask, 7);

            // Calculate Winners
            if (player1HandValue > player2HandValue)
            {
                player1Wins++;
            }
            else if (player1HandValue < player2HandValue)
            {
                player2Wins++;
            }
            count++;
        }

        // Print results
        Console.WriteLine("Player1: {0:0.0}%", 
                ((double) player1Wins) / ((double) count) * 100.0);
        Console.WriteLine("Player2: {0:0.0}%", 
                ((double) player2Wins) / ((double) count) * 100.0);
    }
}

Building a Detailed Hand Odds Calculator

Hand Odds Grid Application Image

When you are playing poker on-line, you don't have access to other players' pocket cards. One technique that can be used is to analyze the potential of a player's hand compared to an average opponent. This is calculated by comparing all possible boards and all possible opponent pocket cards to the player’s hand. This gives a good feeling for the potential of a hand. However, rarely are you playing against an "average" player. So keep that in mind if you are using this technique in real poker play.

The following code example shows how this calculation is made:

C#
using System;
using HoldemHand;

// Compare a player hand to all possible opponent hands
class Program
{
    static void Main(string[] args)
    {
        ulong playerMask = Hand.ParseHand("as ks"); // Player Pocket Cards
        ulong board = Hand.ParseHand("Ts Qs 2d");   // Partial Board
        // Calculate values for each hand type
        double[] playerWins =   { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
        double[] opponentWins = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; 
        // Count of total hands examined.
        long count = 0;

        // Iterate through all possible opponent hands
        foreach (ulong opponentMask in Hand.Hands(0UL, 
                             board | playerMask, 2)) {
            // Iterate through all possible boards
            foreach (ulong boardMask in Hand.Hands(board, 
                           opponentMask | playerMask, 5))
            {
                // Create a hand value for each player
                uint playerHandValue = 
                       Hand.Evaluate(boardMask | playerMask, 7);
                uint opponentHandValue = 
                       Hand.Evaluate(boardMask | opponentMask, 7);

                // Calculate Winners
                if (playerHandValue > opponentHandValue)
                {
                    // Player Win
                    playerWins[Hand.HandType(playerHandValue)] += 1.0;
                }
                else if (playerHandValue < opponentHandValue)
                {
                    // Opponent Win
                    opponentWins[Hand.HandType(opponentHandValue)] += 1.0;
                }
                else if (playerHandValue == opponentHandValue)
                {
                    // Give half credit for ties.
                    playerWins[Hand.HandType(playerHandValue)] += 0.5;
                    opponentWins[Hand.HandType(opponentHandValue)] += 0.5;
                }
                count++;
            }
        }

        // Print results
        Console.WriteLine("Player Results");
        Console.WriteLine("High Card:\t{0:0.0}%", 
          playerWins[(int) Hand.HandTypes.HighCard] / ((double)count) * 100.0);
        Console.WriteLine("Pair:\t{0:0.0}%", 
          playerWins[(int) Hand.HandTypes.Pair]/((double) count)*100.0);
        Console.WriteLine("Two Pair:\t{0:0.0}%", 
          playerWins[(int) Hand.HandTypes.TwoPair] / ((double)count) * 100.0);
        Console.WriteLine("Three of Kind:\t{0:0.0}%", 
          playerWins[(int) Hand.HandTypes.Trips] / ((double)count) * 100.0);
        Console.WriteLine("Straight:\t{0:0.0}%", 
          playerWins[(int) Hand.HandTypes.Straight] / ((double)count) * 100.0);
        Console.WriteLine("Flush:\t{0:0.0}%", 
          playerWins[(int) Hand.HandTypes.Flush] / ((double)count) * 100.0);
        Console.WriteLine("Fullhouse:\t{0:0.0}%", 
          playerWins[(int)Hand.HandTypes.FullHouse] / ((double)count) * 100.0);
        Console.WriteLine("Four of a Kind:\t{0:0.0}%", 
          playerWins[(int)Hand.HandTypes.FourOfAKind] / ((double)count) * 100.0);
        Console.WriteLine("Straight Flush:\t{0:0.0}%", 
          playerWins[(int)Hand.HandTypes.StraightFlush] / ((double)count) * 100.0);

        Console.WriteLine();
        Console.WriteLine("Opponent Results");
        Console.WriteLine("High Card:\t{0:0.0}%", 
          opponentWins[(int)Hand.HandTypes.HighCard] / ((double)count) * 100.0);
        Console.WriteLine("Pair:\t{0:0.0}%", 
          opponentWins[(int)Hand.HandTypes.Pair] / ((double)count) * 100.0);
        Console.WriteLine("Two Pair:\t{0:0.0}%", 
          opponentWins[(int)Hand.HandTypes.TwoPair] / ((double)count) * 100.0);
        Console.WriteLine("Three of Kind:\t{0:0.0}%", 
          opponentWins[(int)Hand.HandTypes.Trips] / ((double)count) * 100.0);
        Console.WriteLine("Straight:\t{0:0.0}%", 
          opponentWins[(int)Hand.HandTypes.Straight] / ((double)count) * 100.0);
        Console.WriteLine("Flush:\t{0:0.0}%", 
          opponentWins[(int)Hand.HandTypes.Flush] / ((double)count) * 100.0);
        Console.WriteLine("Fullhouse:\t{0:0.0}%", 
          opponentWins[(int)Hand.HandTypes.FullHouse] / ((double)count) * 100.0);
        Console.WriteLine("Four of a Kind:\t{0:0.0}%", 
          opponentWins[(int)Hand.HandTypes.FourOfAKind] / ((double)count) * 100.0);
        Console.WriteLine("Straight Flush:\t{0:0.0}%", 
          opponentWins[(int)Hand.HandTypes.StraightFlush] / ((double)count) * 100.0);
    }

Building a Jacks or Better Video Poker Trainer

Video Poker Trainer Application Image

A while back, a family member loaned me a hand held Jacks or Better poker game. After playing it for a few hours, I began to wonder what the correct strategy for playing each hand was. To explore this, I built a Jacks or Better video poker trainer.

The program is a very simple Jacks or Better Video poker application. The one difference is that it tells you what the best expected value is (see the text in the upper right) and the current expected value based on the hold settings.

The algorithm for computing the best expected value is straightforward. There are 32 possible hold settings. Each of the 32 possible values is iterated through in Main(). For each hold setting, the expected value is calculated by iterating through all possible 5 card hands and summing the winning amount. The expected value is simply the summed winnings divided by the total number of hands evaluated. The best expected value and its associated hold mask is saved and printed out. The following code illustrates this. To modify this to handle a different video poker payout schedule, you need to replace the JacksOrBetterWinnings function with the alternate payout schedule.

C#
using System;
using HoldemHand;

// Determine best possible cards to hold.
class Program
{
    // Returns the pay off on a 9/6 Jacks or better machine
    static double JacksOrBetterWinnings(uint handval, int coins)
    {
        switch ((Hand.HandTypes)Hand.HandType(handval))
        {
            case Hand.HandTypes.StraightFlush:
                if (Hand.CardRank((int)Hand.TopCard(handval)) == Hand.RankAce)
                    if (coins < 5) return 250.0 * coins;
                    else return 4000.0;
                return 40.0 * coins;
            case Hand.HandTypes.FourOfAKind: return 20.0 * coins;
            case Hand.HandTypes.FullHouse: return 9.0 * coins;
            case Hand.HandTypes.Flush: return 6.0 * coins;
            case Hand.HandTypes.Straight: return 4.0 * coins;
            case Hand.HandTypes.Trips: return 3.0 * coins;
            case Hand.HandTypes.TwoPair: return 2.0 * coins;
            case Hand.HandTypes.Pair:
                if (Hand.CardRank((int)Hand.TopCard(handval)) >= Hand.RankJack)
                    return 1.0 * coins;
                break;
        }
        return 0.0;
    }

    // Calculate the expected value with the specified holdmask
    static double ExpectedValue(uint holdmask, ref int[] cards, int bet)
    {
        ulong handmask = 0UL, deadcards = 0UL;
        double winnings = 0.0;
        long count = 0;

        // Create Hold mask and Dead card mask
        for (int i = 0; i < 5; i++)
        {
            if ((holdmask & (1UL << i)) != 0)
                handmask |= (1UL << cards[i]);
            else
                deadcards |= (1UL << cards[i]);
        }

        // Iterate through all possible masks
        foreach (ulong mask in Hand.Hands(handmask, deadcards, 5))
        {
            winnings += JacksOrBetterWinnings(Hand.Evaluate(mask, 5), bet);
            count++;
        }

        return (count > 0 ? winnings / count : 0.0);
    }

    // Calculate Best cards to hold.
    static void Main(string[] args)
    {
        double bestev = 0.0; 
        ulong bestmask = 0UL;
        int bet = 5; // coins bet

        string[] cardString = { "as", "ks", "kh", "4s", "4d" };
        int[] cards = new int[cardString.Length];

        // Parse Cards
        for (int i = 0; i < cardString.Length; i++)
            cards[i] = Hand.ParseCard(cardString[i]);

        // Try all possible hold masks
        for (uint holdmask = 0; holdmask < 32; holdmask++)
        {
            double expectedValue = ExpectedValue(holdmask, ref cards, bet);
            if (expectedValue > bestev)
            {
                bestev = expectedValue;
                bestmask = holdmask;
            }
        }

        // Print Out Results
        Console.WriteLine("Best EV: {0:0.0###}", bestev);
        for (int i = 0; i < 5; i++)
        {
            if ((bestmask & (1UL << i)) != 0)
                Console.WriteLine("{0}\t Hold", cardString[i]);
            else
                Console.WriteLine("{0}", cardString[i]);
        }
    }
}

Vector Card Images

Card Vector

All of the card image code and controls that I've run across use bitmaps for card images. This has the advantage of being straightforward, but bitmap images don't scale well and have other issues.

A while back, I found David Bellot's SVG card website. He has a vector based card library that I was able to import into Adobe Illustrator. I have access to an Illustrator plug-in that emits C#/GDI+ code. I took David Bellot's cards and emitted them as C# code and wrapped that as a user control. This makes a very useful vector card control. The advantage of this control is that the cards look good regardless of their size. The disadvantage is that the control is large (about 7 Megs) and the source code is even larger (clocking in at a whopping 22.5 Megs). Even though the control is large, it renders reasonably quickly. However, loading and JITing can take a few seconds.

The usage is very straightforward. You load it as you would load any other control into the toolbox (right click on the Toolbox, select "Choose Items...", and then Browse for the "CardVectorImage.dll"). Once loaded, you can drag it on to a form. The property "Card" is an enumeration of the card faces and card backs that can be displayed. The values for the card faces correspond with the card values used by the Hand.Hands class. This simplifies the assignment of card values to the control.

If you visit David Bellot's website, you will notice that he has a newer (and nicer) version of his SVG cards. However, for some reason, Adobe Illustrator doesn't load the new cards correctly. I haven't found a workaround for that. Until I do, this older version of his card library will have to do.

Compatibility

All of the applications and libraries are written in C# and can be compiled in Visual Studio 2005 or Visual C# Express 2005 (which is free).

The file HandEvaluator.cs can be compiled in Visual Studio 2003 if the partial keyword is removed from the Hand class declaration. However, the HandIterator.cs file uses C# 2.0 features and will not work on previous versions of C#.

Acknowledgements

  • Poker-eval - a great C library for card evaluation that I used as a basis for my class library.
  • David Bellot's SVG Card Library - the source art work for the vector based card control.

I've been working on this code for quite a while (I was on sick leave for 5 months). I'm sure there are several websites that gave me ideas. These are just the ones that I can document as making it into this software.

Interesting Issues

I started this project because I was interested in doing some analysis of poker, and I wanted a real project to try out Visual Studio 2005 (it was beta at the time). During the process of trying to speed things up, I discovered several things that surprised me.

  1. The JITer doesn't inline anything for you - I found this surprising because of all of the promises from Microsoft that JITed C# code will be faster than native C++ code. I found myself missing the "inline" construct of C++. I've even been tempted to port the C# library into managed C++ just because I can still inline code there.
  2. It's faster to lookup a single bit left shift from a table than it is to do a left shift directly - This one left me scratching my head. This shouldn't be true, but try for yourself and you'll see that a lookup table is faster. Compare the operator (1UL << n) to the lookup table cardMasksTable[n] in the Hand class if you'd like to confirm this.
  3. It's amazing how much of a difference look up tables can make in speeding up code - It makes sense that the pre-calculated data will speed code up. But looking at poker.eval, it's impressive how many tables where used and how much faster it makes the evaluation code.
  4. The Visual Studio 2005 IDE is a bit flaky with large source files - I am sure that Microsoft doesn't consider 20+ Meg C# files to be a likely usage scenario, but when you have one, strange things seem to happen. I've had the IDE crash and then crash again when trying to send the error report to Microsoft because of a heap corruption. Intellisense occasionally goes a bit bonkers, and load time is very long on big files.
  5. Vector Cards don't run correctly on the 64 bit version of .NET 2.0 - I ran this library on a 64 bit processor with a 64-bit version of .NET 2.0. Most things worked as expected, but the vector card control is very slow. I suspect this is an issue with the 64-bit runtime and very large methods.

History

  • First version released - Dec 1st 2005.
  • Updated Dec 9th 2005 - Fixed a problem where the build failed for users of VS 2005 Team Editions.
  • Updated Feb 6th, 2006 - Fixed table used for two card hand odds. Added random hand iterator (for Monte Carlo analysis), included NUnit test, speeded up hand iterators by strongly typing the return value.
  • Updated April 18th 2006 - Fixed two reported bugs. One related to the generation of hand description strings (flushes and straight-flushes were labeled with the incorrect suit). Another issue was fixed related to stack overflow when the equal operator was used on a Hand instance.

License

The code that the hand evaluator is based on (poker-eval) is GPLed. The art used for the vector card images is from David Bellot's SVG playing cards. He has also LGPLed his images. That means the HandEvaluator assembly and the CardVectorImage control are both GPLed by other authors.

Since that covers most of the core pieces of the applications I've written, I also am GPLing my code. See each project for more information.

Conclusion

This article shows just a small amount of the code I wrote when I had nothing better to do than tinker. Hopefully, it's interesting and useful to those poker players (and bot writers) out there.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)