Click here to Skip to main content
Email Password   helpLost your password?

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:

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:

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:

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:

static IEnumerable Hand.Hands(int numberOfCards)

and

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.

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:

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:

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:

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.

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

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

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.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
NewsPoker sites to test analysis
jp731
10:30 1 Nov '08  
What a interesting article! good job Keith!

I wonder if I can test it with real poker games.
In

Top Online Poker

I found a couple of good poker portals.

There are online casinos with poker games in

Best Online Casino

too.
GeneralI think some calcs are wrong - flush example [modified]
mrgoos
23:40 1 Apr '08  
When you have a flush draw, your chances are 19.6% and not 30%+ as your app outputs.
when you have a flush draw it means that only 9 cards can help you out of 46 cards (52 in a deck - 2 that you have and - 4 that are already open is 46).
so your chances are 9 to 37 (9 that can help you win against 37 that will loose), which is 9/(37+9) = 19.6%.

modified on Wednesday, April 2, 2008 4:51 AM

GeneralRe: I think some calcs are wrong - flush example
Keith Rule
14:08 2 Apr '08  
The current version of this library can be found here: http://www.codeproject.com/KB/game/MoreTexasHoldemAnalysis1.aspx[^]

A lot of eyes have looked at that version and it has a bunch of bug fixes. It also has many new features. So give it a try.

If you still have the problem with that version, please let me know and I will look into it deeper.

Thanks,

Keith Rule

GeneralpPot calculation [modified]
minpincsh
12:40 31 Mar '08  
First, thanks so much for providing this library - very, very helpful.

I'm wondering about the pPot calculation. Darse Billings (the originator of the algorithm you are using) defines pPot as:

"The Positive Potential, or PPot, is the probability that our hand will improve after a new card is dealt. In general, improvement is measured by counting each situation where we are currently behind and will end up ahead or tied. Likewise, Negative potential, or NPot, is the count of hands where we are currently ahead, but will end up behind."

For most of my sample data, the values seem appropriate. But I can't figure this one out:

JsTs flop: 9s8s2h

The pPot value seems low (pPot = .269), versus this hand:

AsKs flop: 8s7c4s (pPot = .088) - really, really low

The JsTs is actually a better hand versus random hands in this situation (and is in fact a favorite against most hands) due to the drawing strength (flush+straight). So this makes sense... but then try this:

JhTh flop 9h8h2s (pPot = .401)

This situation is identical to the JsTs above, how can the pPot value be so dramatically different?

modified on Monday, March 31, 2008 5:55 PM

GeneralRe: pPot calculation
Keith Rule
13:54 31 Mar '08  
minpincsh wrote:
First, thanks so much for providing this library - very, very helpful.

I'm wondering about the pPot calculation. Darse Billings (the originator of the algorithm you are using) defines pPot as:

"The Positive Potential, or PPot, is the probability that our hand will improve after a new card is dealt. In general, improvement is measured by counting each situation where we are currently behind and will end up ahead or tied. Likewise, Negative potential, or NPot, is the count of hands where we are currently ahead, but will end up behind."


I updated this code sometime ago. You will find the current code at http://www.codeproject.com/KB/game/MoreTexasHoldemAnalysis1.aspx[^].

I haven't looked at the code for sometime. I don't believe I've made changes to the Postive/Negative Potential code between versions. I will look at your examples over the next couple of days. I will provide more detailed feedback then.

The newer version has had a lot more eyes on the code. So I would suggest this as begin a better place to start. However, I will admit that the newer code is considerably larger (due to the PocketQuery Language support).

Thanks for the feedback.

Keith Rule

GeneralRe: pPot calculation
minpincsh
14:23 31 Mar '08  
Never mind - the bug outlined below fixed the inconsistency. Thanks!
GeneralRe: pPot calculation
Keith Rule
15:08 31 Mar '08  
minpincsh wrote:
Never mind - the bug outlined below fixed the inconsistency. Thanks!


A good reason to use the later version of the library. Like I said, a lot of eyes have looked at the later version of the library.

Keith Rule

GeneralIs it possible to use COM to communicate with this lib
DahdeKing
3:35 1 Dec '07  
If I wanted to use this lib from another language, how would I go about it?

Is it possible to create a COM dll for this lib.

I'm a total C newb , I don't really understand how to use the .cs files this comes with.

A link to something would be great...
GeneralGreat example thankyou
xpantsdown
19:57 23 Sep '07  
I love your work here.
I have just used a combination of all your examples to work out a solution for casino holdem.
The Video poker helper was especially useful for this.
GeneralHand.RandomHand hang
RingWeaver
18:10 6 Sep '07  
Keith, I have the following code which I took from the poker for programmers site. When running this code the opp1mask call hangs and does not return a value. This is most likely due to the fact that the board mask has the available cards from the query and no cards can be returned. I have many situations that this happens when trying to pass a query and return a hand. Also, if you use the code in section "Using Pocket Queries in Code" and move the line ulong[] player2 = PocketHands.Query("Axs",player1mask); into the for loop the PocketHands.Query takes way to long to process for each iteration. Is their something I am missing here to make this work when setting pocket queries (ranges) for opponents?



<code>using System;
using HoldemHand;

// This example calculates the win odds for a player having "As Ks" against
// five random players
namespace ConsoleApplication1
{
      class Program
      {
            static void Main(string[] args)
            {
                  // Calculate win odds using example code
                  double w1 = WinOddsFivePlayerMonteCarlo(Hand.ParseHand("as ks"), 0UL, 5.0);
                  Console.WriteLine("win: {0Red faced.00}%, time {1Red faced.0000}", w1 * 100.0, 5.0);

                  // Calcluate win odds using Hand.WinOdds()
                  w1 = Hand.WinOdds(Hand.ParseHand("as ks"), 0UL, 5, 5.0);
                  Console.WriteLine("win: {0Red faced.00}%, time {1Red faced.0000}", w1 * 100.0, 5.0);
                  Console.ReadLine();
            }

            // An example of how to calculate win odds for five players
            static double WinOddsFivePlayerMonteCarlo(ulong pocket, ulong board, double duration)
            {
                  // Keep track of stats
                  long win = 0, lose = 0, tie = 0;

                  // Loop through random boards
                  foreach (ulong boardmask in Hand.RandomHands(board, pocket, 5, duration))
                  {
                        // Get random opponent hands
                        ulong opp1mask = Hand.RandomHand("(AA|KK)",boardmask | pocket, 2);
                        ulong opp2mask = Hand.RandomHand(boardmask | pocket | opp1mask, 2);
                        ulong opp3mask = Hand.RandomHand(boardmask | pocket | opp1mask |
                                                            opp2mask, 2);
                        ulong opp4mask = Hand.RandomHand(boardmask | pocket | opp1mask |
                                                            opp2mask | opp3mask, 2);
                        ulong opp5mask = Hand.RandomHand(boardmask | pocket | opp1mask |
                                                      opp2mask | opp3mask | opp4mask, 2);
                        // Get hand value for player and opponents
                        uint playerHandVal = Hand.Evaluate(pocket | boardmask);
                        uint opp1HandVal = Hand.Evaluate(opp1mask | boardmask);
                        uint opp2HandVal = Hand.Evaluate(opp2mask | boardmask);
                        uint opp3HandVal = Hand.Evaluate(opp3mask | boardmask);
                        uint opp4HandVal = Hand.Evaluate(opp4mask | boardmask);
                        uint opp5HandVal = Hand.Evaluate(opp5mask | boardmask);

                        // Tally results
                        if (playerHandVal > opp1HandVal &&
                              playerHandVal > opp2HandVal &&
                              playerHandVal > opp3HandVal &&
                              playerHandVal > opp4HandVal &&
                              playerHandVal > opp5HandVal)
                        {
                              win++;
                        }
                        else if (playerHandVal >= opp1HandVal &&
                                    playerHandVal >= opp2HandVal &&
                                    playerHandVal >= opp3HandVal &&
                                    playerHandVal >= opp4HandVal &&
                                    playerHandVal >= opp5HandVal)
                        {
                              tie++;
                        }
                        else
                        {
                              lose++;
                        }
                  }

                  // Return stats
                  return ((double)(win + tie / 2.0)) / ((double)(win + tie + lose));
            }
      }
}
</code>
General7 Card Stud
matetee
5:22 4 Sep '07  
Hi Keith Rule,
i read our article, and i`m verry impressed. I was even more when i saw your code. I tryed to use it for my projekt to create a evaluator for 7Card Stud. I modiefied your code in some places, and after a lot of work i thought finally i´d would work. But I get %-numbers that can´t be right (they change the right way when i modifie the pokets, but they are not calibrated).
I telling you this because I hope you will help me.
Maybe you find some time to look over my (maybe more YOURWink modyfied code.

Tell me what you think about.

Greetings
Stefan Keim
(Germany)

GeneralUpdated article?
jnicol
8:00 1 Jul '07  
Hi there,

I just wanted to see if the updated article was near release. I haven't heard anything about it in a few months. Looking forward to it!
GeneralRe: Updated article?
Keith Rule
14:13 4 Jul '07  
I wrote the update as a new article and the codeproject folks asked me to break it apart because it was too large. Here is the link: http://www.codeproject.com/csharp/MoreTexasHoldemAnalysis1.asp[^]

Keith Rule

GeneralBug in HandPotential
james.wren
13:23 12 May '07  
In the functions HandPotential and HandPotentialOpp a uint is used for the card mask, for instance:

HandAnalysis.cs line 790
...
foreach (uint oppPocket in Hands(0UL, dead_cards, 2)) {
...

These need a ulong like the other card masks or else you lose a lot of important bits Smile. Otherwise great job your library has been really useful.

-James
GeneralRe: Bug in HandPotential
Keith Rule
7:10 14 May '07  
Oops, there's also another similar error on line 738. Good catch.

Thanks,

Keith Rule

QuestionProblem with enumerator
FinnDude69
12:37 13 Mar '07  
I have this code:
ulong contain = Hand.ParseHand("ac");
ulong dead = Hand.ParseHand("as kc");

foreach (uint cards in Hand.Hands(contain, dead, 2))
{
Trace.WriteLine("Cards: " + Hand.BitCount(cards) + " " + Hand.MaskToString(cards));
}

As I understand, that should print out all 2-card combinations with Ac X where X is any other card except As or Kc (and obviously Ac), right?

Well, I'm getting this kind of output:
Cards: 2 Ac 2c
Cards: 2 Ac 3c
Cards: 2 Ac 4c
Cards: 2 Ac 5c
Cards: 2 Ac 6c
Cards: 2 Ac 7c
Cards: 2 Ac 8c
Cards: 2 Ac 9c
Cards: 2 Ac Tc
Cards: 2 Ac Jc
Cards: 2 Ac Qc
Cards: 2 2d Ac
Cards: 2 3d Ac
Cards: 2 4d Ac
Cards: 2 5d Ac
Cards: 2 6d Ac
Cards: 2 7d Ac
Cards: 2 8d Ac
Cards: 2 9d Ac
Cards: 2 Td Ac
Cards: 2 Jd Ac
Cards: 2 Qd Ac
Cards: 2 Kd Ac
Cards: 2 Ad Ac
Cards: 2 2h Ac
Cards: 2 3h Ac
Cards: 2 4h Ac
Cards: 2 5h Ac
Cards: 2 6h Ac
Cards: 2 7h Ac (all good so far)
Cards: 1 Ac (where did the 8h go???)
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac
Cards: 1 Ac

What could be the problem?
AnswerRe: Problem with enumerator
FinnDude69
6:31 14 Mar '07  
Ok, I'm officially an idiot... Wink Changing the uint to ulong inside the foreach statement fixed it and I get the expected card combinations.
GeneralRe: Problem with enumerator
Keith Rule
10:59 14 Mar '07  
Not a problem. I glad you pointed out the resolution. It saves me time by not having to try to reproduce your original problem.

I agree that ulong and uint differences can be subtle and cause strange bugs.

Keith Rule

GeneralMismatch of Suits Between Card Masks and Images
utah cooler
9:48 9 Mar '07  
I really appreciate this code, and am looking forward to the update. Because an update is rumored, I want to point out a very minor nitpick. For the card masks the suit order is clubs, diamonds, hearts, spades. I believe this is defined by the CardTable array of HandEvaluator.cs. For the CardType enumeration of the images, the order is hearts, diamonds, clubs, spades.

Dave
GeneralRe: Mismatch of Suits Between Card Masks and Images
Keith Rule
11:08 14 Mar '07  
I'll look into it. I've only used the card class for the Video poker trainer example. Suits don't really matter as long as they are consistently (or consistently wrong in this case).

Sounds like a reasonable thing to fix.

I've been working on migrating the cards to WPF. Conversion to XAML has been straight forward (other than a weird problem with Expression Designer flipping the order of the last two vertices on some polygons). Would WPF cards be an interesting enhancement or does it just distract from the poker article?




Keith Rule

GeneralRe: Mismatch of Suits Between Card Masks and Images
utah cooler
14:25 19 Mar '07  
I do not know anything about WPF, so I would enjoy it. A lot of programmers/authors do not pay enough attention to the visual quality of the cards in their games/trainers. I have seen one of the poker programs on TV where I had to squint to see the cards. The existing cards scale very well and are better than anything I have seen, so I am interested if a WPF solution can compete.

Dave

GeneralRe: Mismatch of Suits Between Card Masks and Images
Keith Rule
11:32 14 Mar '07  
Here's a fix. Replace the CardType enum in UserControl.cs with the following code. Let me know if this fixes your issues.

       
// These constants match the ones in HandEvaluator.cs
// But I don't refer to HandEvaluator here to reduce the number
// of dependences required to use this control.
#region Consts
///
/// Represents the suit - Hearts
///

const int Hearts = 2;
///
/// Represents the suit - Diamonds
///

const int Diamonds = 1;
///
/// Represents the suit - Clubs
///

const int Clubs = 0;
///
/// Represents the suit - Spades
///

const int Spades = 3;
///
/// Rank of a card with a value of two.
///

const int Rank2 = 0;
///
/// Rank of a card with a value of three.
///

const int Rank3 = 1;
///
/// Rank of a card with a value of four.
///

const int Rank4 = 2;
///
/// Rank of a card with a value of five.
///

const int Rank5 = 3;
///
/// Rank of a card with a value of six.
///

const int Rank6 = 4;
///
/// Rank of a card with a value of seven.
///

const int Rank7 = 5;
///
/// Rank of a card with a value of eight.
///

const int Rank8 = 6;
///
/// Rank of a card with a value of nine.
///

const int Rank9 = 7;
///
/// Rank of a card with a value of ten.
///

const int RankTen = 8;
///
/// Rank of a card showing a Jack.
///

const int RankJack = 9;
///
/// Rank of a card showing a Queen.
///

const int RankQueen = 10;
///
/// Rank of a card showing a King.
///

const int RankKing = 11;
///
/// Rank of a card showing an Ace.
///

const int RankAce = 12;
#endregion


///
/// The enumeration value used to set which card (or card back)
/// is to be painted.
///

public enum CardType
{
///
TwoOfHearts = Rank2 + Hearts*13,
///
ThreeOfHearts = Rank3 + Hearts * 13,
///
FourOfHearts = Rank4 + Hearts * 13,
///
FiveOfHearts = Rank5 + Hearts * 13,
///
SixOfHearts = Rank6 + Hearts * 13,
///
SevenOfHearts = Rank7 + Hearts * 13,
///
EigthOfHearts = Rank8 + Hearts * 13,
///
NineOfHearts = Rank9 + Hearts * 13,
///
TenOfHearts = RankTen + Hearts * 13,
///
JackOfHearts = RankJack + Hearts * 13,
///
QueenOfHearts = RankQueen + Hearts * 13,
///
KingOfHearts = RankKing + Hearts * 13,
///
AceOfHearts = RankAce + Hearts * 13,
///
TwoOfDiamonds = Rank2 + Diamonds * 13,
///
ThreeOfDiamonds = Rank3 + Diamonds * 13,
///
FourOfDiamonds = Rank4 + Diamonds * 13,
///
FiveOfDiamonds = Rank5 + Diamonds * 13,
///
SixOfDiamonds = Rank6 + Diamonds * 13,
///
SevenOfDiamonds = Rank7 + Diamonds * 13,
///
EightOfDiamonds = Rank8 + Diamonds * 13,
///
NineOfDiamonds = Rank9 + Diamonds * 13,
///
TenOfDiamonds = RankTen + Diamonds * 13,
///
JackOfDiamonds = RankJack + Diamonds * 13,
///
QueenOfDiamonds = RankQueen + Diamonds * 13,
///
KingOfDiamonds = RankKing + Diamonds * 13,
///
AceOfDiamonds = RankAce + Diamonds * 13,
///
TwoOfClubs = Rank2 + Clubs * 13,
///
ThreeOfClubs = Rank3 + Clubs * 13,
///
FourOfClubs = Rank4 + Clubs * 13,
///
FiveOfClubs = Rank5 + Clubs * 13,
///
SixOfClubs = Rank6 + Clubs * 13,
///
SevenOfClubs = Rank7 + Clubs * 13,
///
EightOfClubs = Rank8 + Clubs * 13,
///
NineOfClubs = Rank9 + Clubs * 13,
///
TenOfClubs = RankTen + Clubs * 13,
///
JackOfClubs = RankJack + Clubs * 13,
///
QueenOfClubs = RankQueen + Clubs * 13,
///
KingOfClubs = RankKing + Clubs * 13,
///
AceOfClubs = RankAce + Clubs * 13,
///
TwoOfSpades = Rank2 + Spades * 13,
///
ThreeOfSpades = Rank3 + Spades * 13,
///
FourOfSpades = Rank4 + Spades * 13,
///
FiveOfSpades = Rank5 + Spades * 13,
///
SixOfSpades = Rank6 + Spades * 13,
///
SevenOfSpades = Rank7 + Spades * 13,
///
EightOfSpades = Rank8 + Spades * 13,
///
NineOfSpades = Rank9 + Spades * 13,
///
TenOfSpades = RankTen + Spades * 13,
///
JackOfSpades = RankJack + Spades * 13,
///
QueenOfSpades = RankQueen + Spades * 13,
///
KingOfSpades = RankKing + Spades * 13,
///
AceOfSpades = RankAce + Spades * 13,
///
JokerRed = 52,
///
JokerBlack = 53,
///
BackBlueHelix = 54,
///
BackBlueStrip = 55,
///
BackRedStrip = 56,
///
BackRedFlower = 57
}


Keith Rule

GeneralRe: Mismatch of Suits Between Card Masks and Images
utah cooler
14:41 19 Mar '07  
The change works like a charm! I read a string representation of cards from a text file per the follwing code, and now the suits are correct.

linestr = sr.ReadLine();
string playerstr = String.Concat(linestr.Substring(0, 2),
" ",linestr.Substring(3, 2));
Player.PocketCards = playerstr;
for (int i = 0; i < 52; i++)
{
if (((Player.PocketMask >> i) & 1UL) == 1UL)
{
c1.Card = (CardVectorImage.CardVector.CardType)(i);
for (int j = i + 1; j < 52; j++)
{
if (((Player.PocketMask >> j) & 1UL) == 1UL)
{
c2.Card = (CardVectorImage.CardVector.CardType)(j);
break;
}
}
break;
}
}


Thanks for the change.

Dave
GeneralMultithreaded version
Fisman
23:52 7 Mar '07  
It needs some cleanup work done on it, but on my core 2 duo, it nearly doubles the performance of the evaluator as expected. Let me know if interested. More cpus, more cores, more performance Smile
GeneralRe: Multithreaded version
Keith Rule
6:39 8 Mar '07  
That's been my experience too. I haven't tried a quadcore yet, but I should be able to get my hand on one in the next month or so.

My updated code and article will have an example of using thread pools for doing long calculations faster on a multicore processor. Stay tuned.

Keith Rule


Last Updated 18 Apr 2006 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010