Multiway Isometric Ranged Equity Calculation in Poker, Part 1
Monday, April 20, 2009   

Introduction

If you've turned on a TV in the past five years you've seen those nifty win percentages they throw up on the screen in televised poker:

In this multi-part series we're going to build a poker calculator (with complete source code) capable of computing these sorts of win percentages or equities for any poker situation whatsoever...

  • Any poker variant (Hold'em, Omaha, etc.)
  • Any number of players (22 players preflop vs. your pocket Aces? Fine.)
  • With any type of hand (specific hand, hand range, random hand)
  • On any street of betting
  • In the language (C#, C++, Java, VB.NET, etc.) of your choice

...in a couple lines of code:

We'll also discuss the theory that goes into such a calculator:

  • Monte Carlo
  • Exhaustive Enumeration
  • Hand Distributions
  • Lookup Tables
  • Recursion
  • Combinatorics
  • How To Speak To a Girl
  • Hand Range Notation

Now: the above televised poker win percentages shown above were inserted by the producers, after the fact, with perfect knowledge of each player's cards. In the heat of battle, we don't have perfect knowledge of anything except our own cards. And the way we typically get around this is by assigning our opponents not a specific hand, but a range or distribution of potential hands.

When confronted with a situation like this, the typical poker calculator curls up into the fetal position and begs for mercy. We want a poker calculator capable of handling these sorts of calculations—multiway ranged equity calculations—without skipping a beat. For example, let's say there are nine players, two of whom have known hands, two of whom have hand ranges, and five of whom have random hands...

  • Player 1: [Jc Jh]
  • Player 2: [8s 7s]
  • Player 3: [99+,AJs+]
  • Player 4: [QQ+,AQs+,AQo+]
  • Player 5: [random]
  • Player 6: [random]
  • Player 7: [random]
  • Player 8: [random]
  • Player 9: [random]
  • Board: [4d Ac 5d]

Normally we'd have to break open a copy of Andrew Prock's excellent PokerStove in order to perform this sort of analysis.

The purpose of today's post is to explain how to perform these calculations in a couple lines of code, with a view towards incorporating robust ranged equity calculation into our pet poker software projects: poker bots, real-time strategy assists, PokerStove clones, etc. We talked about ranged equity calculation briefly in The Great Poker Hand Evaluator Roundup and again in How I Built a Working Online Poker Bot, Part 8. Today's discussion will pick up where those posts left off.

This article is rather long-winded, so here's an overview of what we'll be covering:

  1. Introduction
  2. How a Poker Calculator Works
  3. Hand Distributions and Hand Ranges
  4. Monte Carlo vs. Exhaustive Enumeration
  5. Equity vs. Win Percentages
  6. Recursive Enumeration
  7. Generating Hand Distributions
  8. Amortizing Collisions with Round-Robin Access
  9. The XPokerEquity Calculator Source Code
  10. Testing the Evaluator
  11. Conclusion

Fair warning! If you're a grizzled veteran of poker-related code, you probably won't learn much by reading this. Save yourself! If you're a non-programmer, this material will probably be excruciatingly boring. Save yourself! If, on the other hand, you've got the poker/programming bug, if you've struggled to write robust equity calculation code before, or if you plan on one day writing such code, well, this article may come in handy.

Who knows. Anything's possible.

How a Poker Calculator Works

There are (at least) four ways to calculate equity in the game of poker. Whatever the game and whatever the specific scenario:

  • Monte Carlo simulation
  • Exhaustive Enumeration
  • Lookup Tables
  • Combinatorics

These methods are all subtly related to one another. Exhaustive enumeration is really a case of applied combinatorics. Lookup tables are a way of pre-computing equities and storing them in a file so we don't have to recalculate them. Monte Carlo is a statistical method which allows us to approximate the value we would've gotten had we taken the time to exhaustive enumerate every possible outcome. And so forth.

The point I want to make is that a poker equity calculator is a hand evaluator sitting inside a loop

Now, the "loop" in question might be a series of nested loops. It might be a recursive loop. But every calculator will impose a circular, repetitive motion across the following actions:

  1. Generate the cards. This can be done either randomly (Monte Carlo) or by choosing each possible combination in turn (Exhaustive Enumeration).
  2. Evaluate the winner. You've assembled a single "hand" or trial of poker. Figure out who wins/ties/loses.
  3. Tally the results. Keep a running tally of wins and losses for each player involved in the hand.
  4. Repeat steps 1-3 a million (or however many) times.

So no matter how convoluted the code or how advanced the algorithm, you'll generally be able to pick out the above stages. Let's take a look at some representative code.

We have a basic loop, set to run (in this case) a certain number of trials. For each trial, we'll generate a) any missing player cards and b) any missing board cards. Once we've done that, we'll pass those into the hand evaluator, which will compute the winner and tally the results.

Now, when I say a hand evaluator, I mean a component whose only job is to take the cards for a specific trial and determine the winner. Sometimes the term is used loosely, but for me it has a precise meaning. In The Great Poker Hand Evaluator Roundup, we discussed a dozen or so different evaluators. In particular:

We'll discuss how to use each of these evaluators to perform true multiway equity calculation. In this installment, we'll focus on the Pokersource evaluator. If you're not familiar with this evaluator, you might want to read A Pokersource Poker-Eval Primer before continuing.

Hand Distributions and Hand Ranges

Hand distributions and ranges are so important to poker strategy, a sort of mini-language has sprung up to describe them. This language is still evolving, but it started as a way of describing hands while stripping out unnecessary suit information. Poker players will be familiar with the following descriptions of agnostic hands.

Hand Number of Combinations Meaning
AJs 4 Any Ace with a Jack of the same suit.
77 6 Any pair of Sevens.
T9o 12 Any Ten and Nine of different suits.
54 16 Any Five and Four, suited or unsuited.

It wasn't long before players started adding a few symbols to make these notations more expressive:

Hand Number of Combinations Meaning
AJs+ 12 Any Ace with a (Jack through King) of the same suit.
77+ 48 Any pair greater than or equal to Sevens.
T9o-65o 12 Any unsuited connector between 65o and T9o.
54 16 Any Five and Four, suited or unsuited.

And of course, these notations can be glommed together with the "comma operator":

Hand Number of Combinations Meaning
QQ+,AQs+,AK 38 Any pair of Queen or better, any AQs, and any AK whether suited or not.
AhKh,7h7d 2 Ace-King of Hearts or a pair of red Sevens.

Needless to say, if we're building an evaluator that can work with hand distributions, it should be able to understand the notation used to describe them. In particular, when we say "I believe my opponent has a range of 99+,AJs+", it should understand that what we're really saying is I believe my opponent has one of 48 specific hands.

Of course, if you happen to be holding the Ace of Spades, then some of those possibilities are now blocked for your opponent, and we'll need to take this into account when performing any equity calculations:

But regardless of what distribution we assign an opponent, or how many of the specific hands contained in that distribution are available, we can always, always, always represent an opponent's distribution as a simple collection of specific hands. Even if we know the opponent's specific cards, he still has a distribution...of one hand.

In fact, the core abstraction used in our evaluator is exactly that: a class representing a single player's distribution of possible hands:

class HoldemHandDistribution
{
public:
   /* blah blah blah */

private:
   vector<StdDeck_CardMask> m_hands;
};

This is the acid test of a true isometric calculator. Isometric (we could also use the word congruent) calculators treat everything as a distribution. The isometric calculator could care less whether a player has:

  • A unary distribution containing a single specific hand
  • A random distribution containing all of the 1,326 possible starting hands
  • Any other distribution

This normalizes the underlying logic, allowing us to disregard arbitrary differences between specific hands, hand ranges, and random hands. They're all the same. And they're all treated the same.

Monte Carlo vs. Exhaustive Enumeration

Monte Carlo simulation and Exhaustive Enumeration are the two horns of the poker equity calculation beast. 

On the one hand, we have the exhaustive enumeration approach, which consists of generating each and every possible outcome. No matter if you have 22 players with random and ranged hands before the flop, exhaustive enumeration will generate every possible combination of cards for each player along with every possible combination of board cards, tally them all, and yield the precise theoretical equity for the situation with zero error.

On the other hand, we have the Monte Carlo method, darling of physicists and statisticians, which consists of generating random inputs (cards, in our case) across many trials, thereby converging a result which closely approximates the theoretical "correct" result. Monte Carlo simulation produces results which have a small margin of error, although in practice this error is low enough that we can effectively ignore it.

Now: in a perfect world, we'd always use exhaustive enumeration. Given infinite computing power, we'd analyze every possible outcome in a nanosecond and be done with it. But in the real world, exhaustive enumeration can be prohibitively expensive. The outcomes start to explode as you add more and more choices to a given scenario: 

Scenario Street Number of Outcomes
AhAd vs. KcKs Turn 44
QsJc vs. Ad2c Flop 990
44 vs. Ts7c Flop 2,970
AKs vs. TT Flop 23,760
AKs vs. TT vs. J9o  Flop 260,064
3c2s vs. 5d4c Preflop 1,712,304
AA vs. KK Preflop 61,642,944
AA vs. KK vs. QQ Preflop 296,082,864
AA vs. KK vs. QQ vs. JJ Preflop 1,407,466,368
AA vs. XX Preflop 12,585,434,400

You're already looking at on the order of 12 billion outcomes just to exhaustively enumerate an agnostic pair of Aces versus a random hand before the flop. And we'll frequently be calculating situations involving 4 or 5 players (if not more), each of whom has a distribution of N possible hands, times the 5 board cards, and so forth.

Now here's the key point: even though it would take us (say) 12 billion evaluations to exhaustively enumerate the AA-vs.-random preflop scenario, we can Monte Carlo the same scenario and get some very precise results in a fraction of that time. A million-trial Monte Carlo simulation will produce equities that are precise down to fractions of a percent; in fact, we can typically get that type of precision with far fewer than a million trials (possibly as few as 10,000 or 100,000).

This leads us to a few rules of thumb:

  • A robust calculator MUST support both Monte Carlo and Exhaustive Enumeration
  • A robust calculator SHOULD have the ability to switch between MC and EE based on the specific scenario

PokerStove actually lets the user choose how to perform the analysis: 

And sure enough, in the source code accompanying this article, you'll find a couple telltale methods:

class HoldemCalculator
{
public:
   /* blah, blah, blah */

   int Calculate(const char* hands, const char* board, const char* dead, int numberOfTrials, double* results);

private:

   int      CalculateExhaustive();
   int      CalculateMonteCarlo();

   /* blah, blah, blah */
}

The decision as to when to use Monte Carlo vs. Exhaustive Enumeration really boils down to you and the purpose of your tool. If you need to compute equities involving many players with fat distributions in fractions of a second, you'll gravitate towards Monte Carlo. If on the other hand you have a lot of processing time or if you're not dealing with ranged/random hands so much, exhaustive enumeration might be the order of the day. In general, you'll find that there exists a Monte Carlo threshhold which is ideal for your application. When the number of potential outcomes exceeds this threshhold, you'll Monte Carlo; when the number of outcomes falls short, you'll exhaustively enumerate.

But the calculator itself should support both.

Equity vs. Win Percentages

The terms equity and win percentage are often used interchangeably, in casual conversation, but these are actually two different things. As Andrew Prock explains it:

[Equity] is not the chance that a hand will win the pot. Rather it is the fraction of the pot that a hand will win on average over many repeated trials, including split pots. The equity for a hand is calculated by dividing the number of "pots" that the hand won by the number outcomes considered. Because two players can split a pot, a player can win fractional pots. Thus, it is possible for a hand to have non-zero equity despite the fact that it cannot win.

The metaphor I like to use is that of a pizza. 

In a single battle of poker, there's exactly 1.0 unit of "winningness" to go around. One pizza. The player who wins the hand gets the pizza, and everybody else goes hungry. But if two or more players tie for the win, well, they each get an equal share of the pizza. If exactly two players tie, they each get half of the pizza: 0.5 units of winningness each. If three players tie, they each get a third (0.333) of the pizza. And so forth.

In general, when N players tie in a particular game of poker, they're each awarded a fractional win equal to 1 / N.

This is not just some number we use by way of explanation. This fraction of the pizza, be it 1.0 or 0.5 or 0.333 or 0.25, is the actual amount you'll add to each player's "win tally" in the poker calculator implementation, after you determine the winner(s) of each specific trial:

// If there's no tie, then the player with the best hand gets a +1 for the win.
if (!isTie)
{
   m_wins[bestIndex]++;
}

// If there IS a tie, then the players with the tied winning hand get a +X for
// the win, based on by how many players it was split.
else
{
   double partialWin = 1.0 / ((double)numTies + 1.0);
   for (int i = 0; i <= numTies; i++)
      m_wins[ m_tiedPlayerIndexes[i] ] += partialWin;
}

That code is a little messy (see my code disclaimer, below), but it gets the point across. Equity is similar to win percentage except it normalizes the value of ties. This is hugely important in a game like Hold'em or Omaha, where ties are so frequent.

Recursive Enumeration

One of the benefits of treating each player's hand as a distribution containing one or more hands is that it gives us a precise count of each player's possible holdings and allows us to express each player's hand as a number between 0 and N. For example, in a given three-player matchup we might find that:

  • Player 1 has exactly 1 possible holding (a specific hand)
  • Player 2 has exactly 36 possible holdings
  • Player 3 has exactly 154 possible holdings

So we can exhaustively enumerate all possible combinations of player holdings using a typical nested loop structure.

HoldemHandDistribution dist1 = new HoldemHandDistribution("AsKs");
HoldemHandDistribution dist2 = new HoldemHandDistribution("QQ+,AJ+");
HoldemHandDistribution dist3 = new HoldemHandDistribution("22+,A2s+,ATo+,KTs+,QJ+");

for (int player1 = 0; player1 < dist1.GetCount(); player1++)
{
   for (int player2 = 0; player2 < dist2.GetCount(); player2++)
   {
      for (int player3 = 0; player3 < dist3.GetCount(); player3++)
      {
         // Pick a hand from each player's distribution

         StdDeck_CardMask hand1 = dist1.Get(player1);
         StdDeck_CardMask hand2 = dist2.Get(player2);
         StdDeck_CardMask hand3 = dist3.Get(player3);

         // Randomly choose or exhaustively enumerate whatever board cards.
      }
   }
}

The problem is that the number of loops has to be hard-coded per the number of players. If we had seven players, we'd need seven loops. That's a little messy, it violates DRY, and we'd prefer a normalized solution that will handle any arbitrary N number of players. In order to do that, we'll take our nested-loop structure and invert it, creating a recursive function with a depth equal to the number of players in the hand.

StdDeck_CardMask m_playerHands[10];

void ExhaustiveRecurse(int N)
{
   HoldemHandDistribution thisHand = /* Get the Nth player's distribution  */;

   for (int index = 0; index < thisHand.GetCount(); index++)
   {
      m_playerHands[N] = thisHand.Get(index);

      if (N < (numberOfPlayers - 1))
      {
         ExhaustiveRecurse(N + 1);
      }
      else
      {
         /* Each player has been assigned a single hand from his distribution.
            Now, exhaustively enumerate all board cards and for each situation,
            run the evaluator to determine the winner, and tally the results */

      }
   }
}

The above code omits some important details—such as how to handle dead cards and collisions between player distributions—but the general idea, and you'll see this in the full source code accompyaning this article, is that we can exhaustively enumerate any arbitrary situation involving N players with arbitrary distributions along with all possible board combinations using a single recursive function.

Generating Hand Distributions

So now that we know our core abstraction will be the hand distribution, and now that we're agreed on the language we use to describe hand distributions, let's talk a little bit about how to generate them programatically. In other words, how do we go from this...

22+,A2s+,ATo+,KT+,QT+,JT+,65s+

...to this?

In general, to parse a given textual hand distribution, we'll follow these steps:

  1. Tokenize the range on the comma delimiter. So we'll break "22+,A2s+,ATo+,KT+,QT+,JT+,65s+" into seven separate tokens.
  2. For each token, loop across the different suit-combinations.
  3. If the token includes a "+", add a loop to increment the ranks in conjunction.
  4. If the token includes a "-" (as in TT-88) add a ceiling to the the rank loop.

For example, to boil a given agnostic pair (such as 77) down into its six unique combinations, we might hack out this straightforward solution:

StdDeck_CardMask card1, card2, hand;
int rank = StdDeck_Rank_7;

for
(int suit1 = StdDeck_Suit_FIRST; suit1 <= StdDeck_Suit_LAST; suit1++)
{
   for (int suit2 = suit1 + 1; suit2 <= StdDeck_Suit_LAST; suit2++)
   {
      // Assemble the first and second card
      card1 = StdDeck_MASK( StdDeck_MAKE_CARD(rank, suit1) );
      card2 = StdDeck_MASK( StdDeck_MAKE_CARD(rank, suit2) );
      // Combine them
      StdDeck_CardMask_OR(hand, card1, card2);
      // TODO: Add the hand to the distribution
   }
} 

If the agnostic pair includes an incrementor, as in "77+", we can add a third loop to increment the rank:

StdDeck_CardMask card1, card2;

for (int rank = Std_Rank_7; rank <= Std_Rank_ACE; rank++)
{
   for(int suit1 = StdDeck_Suit_FIRST; suit1 <= StdDeck_Suit_LAST; suit1++)
   {
      for (int suit2 = suit1 + 1; suit2 <= StdDeck_Suit_LAST; suit2++)
      {
         card1 = StdDeck_MASK( StdDeck_MAKE_CARD(rank, suit1) );
         card2 = StdDeck_MASK( StdDeck_MAKE_CARD(rank, suit2) );
         StdDeck_CardMask_OR(hand, card1, card2);
      }
   }
} 

And if the agnostic pair specifies a range with a ceiling, as in "JJ-88", we'll just change the condition on the rank loop:

StdDeck_CardMask card1, card2;

for (int rank = lowRank; rank <= highRank; rank++)
{
   for(int suit1 = StdDeck_Suit_FIRST; suit1 <= StdDeck_Suit_LAST; suit1++)
   {
      for (int suit2 = suit1 + 1; suit2 <= StdDeck_Suit_LAST; suit2++)
      {
         card1 = StdDeck_MASK( StdDeck_MAKE_CARD(rank, suit1) );
         card2 = StdDeck_MASK( StdDeck_MAKE_CARD(rank, suit2) );
         StdDeck_CardMask_OR(hand, card1, card2);
      }
   }
} 

Do the above loop structures look familiar? They should, if you're one of the masochistic few who actually read Exhaustively Enumerating Combinations and Permutations in Code. Converting a textual hand range notation into a concrete distribution is just a special case of combination counting. The above method isn't the most elegant, but it is straightforward.

Amortizing Collisions with Round Robin Access

When Monte Carlo is used to randomly select a single hand from each opponent's distribution, the possibility of collisions can arise. That is, a situation can occur in which every hand in a player's distribution is prevented or blocked by the cards chosen for other opponents during a specific trial. Consider the following four-way matchup:

  • Player 1: [QQ+,AJs+,AQo+]
  • Player 2: [random]
  • Player 3: [A2s+,22+]
  • Player 4: [A2s+,ATo+]

Now, what happens if we (randomly) select the [AhKh] for player 1, the [Ad7c] for player 2, and the [AsAc] for player 3?

Answer: player 4's distribution is now completely blocked. Every hand in his distribution requires an Ace, but the four Aces have already been dealt out. This won't happen very often, but it can and will happen. And when it happens, the trial is invalid and should be thrown out. Furthermore, it doesn't take a complete blockage to introduce bias into the results. Even partial collision can bias the results. The reason is simple: the first player to be "dealt" cards always has his entire distribution to choose from; but many of the hands in the last player's distribution will no longer be available.

It's unfair.

So in order to reduce the likelihood of this happening, we change the player pecking order between trials. That is, instead of choosing Player 1's hand followed by Player 2's hand followed by Player 3's, each and every trial, we want to alter the order in which we choose cards for opponents across many trials. Instead of starting with Player 1 every time, we want to start with Player 1 on one trial, Player 2 on the next trial, Player 3 on the trial after that, and so forth.

By choosing the "starting player" in this round-robin fashion, we amortize potential collisions across all players, reducing the likelihood of a) bogus trials that we have to discard and b) bias due to partial collision. And in order to do this cleanly, we'll use a circular linked list to address these players, and increment our "head" with each new trial.

I hope it's becoming clear that when I said in A Circular Dilemma Concluded:

The circle is the fundamental geometry of poker.

This wasn't some theoretical pie-in-the-sky notion! Of course, you don't have to use a circularly-linked list, but it makes the code cleaner.

The XPokerEquity Calculator Source Code

Today's installment includes source code for a full-fledged isometric calculator capable of computing equity for any Texas Hold'em scenario, including weird scenarios such as pocket Aces versus twenty-two random hands before the flop. Using the evaluator is simple:

Just put in your opponent hands/ranges separated by | and go. That version of the function will switch between Monte Carlo and Exhaustive Enumeration based on the MC threshhold. In addition, there are a couple overloads which you can use to force Monte Carlo or Exhaustive Enumeration:

  • HoldemCalculator::CalculateMC
  • HoldemCalculator::CalculateEE

This evaluator is implemented without optimizations of any kind in order to make the code as transparent as possible.

  • No lookup tables. We'll add these in a future installment, but for now, I wanted to focus on the calculation, not the lookup-ation.
  • No fancy combinatorics shortcuts. We use classical Monte Carlo and exhaustive enumeration for everything.
  • No code refactoring/compaction. You'll find many, many places where the code can be improved. Many.

Despite the lack of optimization, this evaluator is fast enough for typical use, including real-time poker botting in a multi-table scenario.

Download the XPokerEquity source code (751K)

Testing the Evaluator

The XPokerEquity evaluator solution includes a simple test project (console app) which will run various matchups and test the returned equities against PokerStove results:

When it comes to testing your poker calculator, there are three key sanity checks you'll want to employ, especially if you're using the evaluator for something important (a commercial product, a real-money poker bot, etc.):

  • Spot-testing against established evaluators like PokerStove.
  • Testing preflop equities against the various preflop lookup tables that are available.
  • Comparing Monte Carlo results for a given matchup against exhaustive enumeration results for the same matchup.

In practice you'll find that isometric evaluators tend to be either "right" or "wrong". That's not to say they aren't susceptible to obscure bugs, but generally speaking, if an isometric evaluator calculates any multiway matchup correctly, it'll calculate them all correctly. That's not a substitute for robust testing, but it's another benefit of the isometric approach to equity calculation.

Conclusion

I apologize for the length of this post (well, not really) but I wanted to do a (fairly) comprehensive overview of the subject rather than just throwing a brick of source code at you. Needless to say, there are better and more clever ways of doing pretty much everything we've discussed here today.

Anytime you flirt with the boundary between computer science and hard math (or software development and computer science) things start to get a little crazy. Maybe you can use N-ary decomposition across a diagnate infinity of infinities of choice with matrix-based combinations to derive the asymptotic convergence curve and thereby somehow back yourself into a massively parallel exploration of the poker game tree, and you know what? That's fascinating. It really is. But for now let's just deal out a bunch of hands and tally the results. It's easy and it works.

And for those of you who've been wondering what happened to the poker botting series?...stay tuned. Because once you've built your isometric equity calculator, your poker bot can start to do all sorts of nifty things in real time:

  • Estimate core EV
  • Create EV action trees
  • Calculate ICM
  • Establish empirical opponent distributions in conjunction with tools like Hold'em Manager (etc.)

So I hope you'll stay with me as we watch our formerly naive poker bots start to grow some teeth. Until then, may the equities always be in your favor.


Posted by James Devlin   61 comment(s)

Awesome! I've only skimmed this so far. Will DL the code and give it a twirl later today.

I have been futzing around with code like this for literally about a year. Ended up using Keith Rule, which is decent. But I wanted it in C++...

Edward G. on 4/20/2009 7:26 AM (323 days ago)

Sweet Jesus. Downloaded. Tested. Love the simplicity of the HoldemCalculator::Calculate method. Have been looking for something like this for a looooong time. Just let me pass in a string damnit!!

That said, I wish you'd included a LUT in the implementation. At least for heads-up PF matchups. Then again, with the code here, I can build my own. But that's work.

Kenny on 4/20/2009 8:31 AM (323 days ago)

thats it. i said it last year. you teach a poker bot how to perform these kinds of calculations and you blow the lid right off this game. you and your stupid poker botting BS, encouraging people to do something you know damn well not 1 in 1000 would've done without you holding their hand like some demented asshole.

"poker bots growing teeth..."

you better hope i never catch you at the tables.

Anonymous and pissed on 4/20/2009 8:49 AM (323 days ago)

@anonymous and pissed: He's just leveling the playing field here. More and more people use this kinda crap. And every single thinking human being can find half of this crap all over the internet anyway. He's just condensing it. And I love it.

I think you should be hating less and start working on your poker game. Probably it sucks. And that's the reason you're so pissed. Ass-wipe.

Anonymous and loving it... on 4/20/2009 8:56 AM (323 days ago)

James please ignore this dork and keep 'em coming!

-Ed

Ed K. on 4/20/2009 9:38 AM (323 days ago)

Shortcuts like the "88+" system are good at first, but I feel like it is worth it from the start to recognize that in a real poker "88+" situation, it is significantly ignorant to treat the distribution as having an equal chance for aces or eights, as well as no chance for sevens. Great work and keep it coming, but I feel like there has to be a relatively simple fix to allow for each hand to have a different likelihood of being played by a certain player instead of assigning him only a few hands and treating them as equally likely.

Mac on 4/20/2009 11:53 AM (323 days ago)

@Anonymous and pissed:

any computer science major can figure this stuff out in ~2 days' research, depending on what their skill level is; he's just presenting it in a coherent fashion.

I suggest you read about Security by Obscurity.

@codingthewheel:

do you introduce minor fairness issues by maintaining the order of the players between hands? Wouldn't it be better to shuffle the list of players rather than rotate it when generating their hands in the MC simulation?

Bill Mill (not at all anonymous) on 4/20/2009 12:21 PM (323 days ago)

@anonymouse Some people like playing, but many computer scientists (or simply geeks) prefer to build programs able to play in a smart way, rather than playing themselves. For me, playing (almost any card game) is plain boring, but designing algorithms is so fascinating! There is nothing wrong with that, because we are not all playing with bots on websites where bot are disallowed. A few years ago, when IRC poker was still popular there was even poker room for bots only!

Laurent on 4/20/2009 12:35 PM (323 days ago)

The hand evaluation stuff was interesting enough. However, I didn't find word one about "How To Speak To a Girl". Disappointing.

Rob Fagen on 4/20/2009 1:10 PM (323 days ago)

As usual, it seems this guy is copying what is already available in OpenHoldem and has been for a while. OH has had MonteCarlo iterators based on Poker-Eval for years. Has had ICM calculators for only slightly shorter.

I guess if you are looking for a complete pokerbot before the article series are complete, you know where to go...

Anonymous on 4/20/2009 1:11 PM (323 days ago)

awesome article

Anonymous on 4/20/2009 1:12 PM (323 days ago)

James could probably give out the source code for a mostly-built poker bot, and most readers of this series wouldn't be able to do anything worthwhile with it. He's only covered (fairly lightly at that) 15% of the topics needed to produce a bot that makes money. AI alone is probably 50%. Then there's the meta-implementation meta-details, like setting up bank accounts, getting money out in a non-conspicuous way, table selection, etc.
If someone were to hand you the blueprints for a stealth bomber, would you be able to build it? And even if you could build it, would you then be able to fly it?
As a matter of fact, if there were a sudden influx of people using bots, most people who actually make money off of online poker (be it playing themselves or operating a successful bot) would likely see a spike in profits.

Marc on 4/20/2009 1:53 PM (323 days ago)

@anonymous and pissed: Instead of getting angry, how about using the info yourself and work on improving your own game? I have been a professional programmer for a decade and playing around with code calculators of various types the entire time. I may or may not ever actually build a working bot (likely not, it's a HUGE time investment), however just by studying in-depth the concepts presented here, both my online and live games have improved immensely. Online, I can now spot a winholdem bot in about 5 seconds flat, slightly longer for a few other varieties. I'm sure there are many, many, many programs out there that I can't spot yet, but I've started working on ways to try. (Anyone working on datamining algorithms for Pokertracker or Holdem Manager? Wink )

Point is, information itself is neither good nor evil. It's just information. And knowledge is power, in whichever way you choose to use it.

The concepts I've learned about odds, hand ranges, and equity estimation have turned me from a break-even into a winning player across the board, both online and live play. I read about programming because it's fascinating. I play poker because it's fun. I learn everything I can about things that interest me because if I don't ... then, well there's no point to life, then is there?

Anonymous on 4/20/2009 7:47 PM (322 days ago)

Where can I find the pizza in the picture?

D on 4/20/2009 8:12 PM (322 days ago)

In the poker AI I made for my senior project, I represented the hands for each opponent as a (52*51) length array containing a double that initially started as 1, representing the weighted chance of that happened. Then as cards were dealt, set them to 0. As player actions occurred, the weights would be updated. For instance, if the player raised twice pre-flop, then there was a lookup table that had high chances for AA/AKs/AK/KK etc, and low chances for 72 etc, that would then be multiplied in. In the end, it would multiply the EV for each hand and the estimated probability of that hand and get a weighted EV for each of betting/folding/checking and play based on that. It was designed for heads-up play and extremely rudimentary, but it was fairly good (It regularly beat me, even though I knew how it worked).

Another thing is you can exploit symmetry to speed up the calculations. There are a lot of cases when you can get the EV for two or three hands that the opponent might have by doing only one calculation. For instance, if you are doing AhKh vs XX, then the EV against all of 9sTs, 9dTd, and 9cTc are the same, so you could calculate it for only one and multiply it by 3. Doing this you can turn the 12 billion into less.

Also, there is another option for evaluating hands that doesn't follow your model of Generate -> Evaluate -> Tally, and more accurately describes how a human would evaluate a hand, in that given N hands, find the set of cards that would improve any of the hands that aren't the highest. In turn, deal each of those sets of cards to the table. Evaluate these partial hands, then recursively repeat. Using some combinatronics, make the return of the function equal to the weighted sum of those cards being dealt over the chance that the card would be dealt. I haven't actually implemented this, and it seems really hard to get right, for little gains when you could be simulating, but it is definitely another way to do it.

Kevin H on 4/20/2009 11:37 PM (322 days ago)

Pokerstove can easily do three-way range vs range vs range calculation without monte carlo so it's possible!

Thomas on 4/21/2009 2:55 AM (322 days ago)

@Laurent... what sites do not disallow pokerbots and are still reputable (so you can be sure you get your money, rakeback or maybe even increase in status, like on pokerstars). Is "absolute poker" such a site? And their related network of poker sites.

Nick on 4/21/2009 7:38 AM (322 days ago)

to the poster that claims this functionality is present in Open Holdem. The answer is yes and no. OpenHoldem does have weighted prwin, and enhanced prwin. Enhanced prwin is better than the example listed here in the fact that you can add or remove hands from a players lists based on handrank1326. ie I can remove the ace of hearts from a player specified hand list, instead of just specifying JTs+ or 88+. But I have to admit, I like the pokerstove-like functionality here.

Also, the OpenHoldem prw1326 is a pure Monte-Carlo simulator, not exhaustion enumeration which is faster. So, the coding the wheel is better in that regaurd, but Monte Carlo is accurate to the second demical place over 10,000 iterations. Having both will be nice, Thank you Mr. Devlin I am enjoying your series. And for anyone that is interested join us at http://botclub.ning.com for AI financial prediction.

gman on 4/22/2009 3:59 PM (321 days ago)

would be interesting to combine something like the OH scaper to this code so that it auto scrapes the screen and can access something like PT database to give you an idea of where your hand stands vs your opponents range.

would be interesting to have playing yourself or a bot playing.

Anonymous on 4/22/2009 5:21 PM (321 days ago)

You are crazy man. I really wish I knew what exactly were talking about, but I don't feel like devoting a year of my life to learn coding.

Players Only Rakeback on 4/22/2009 11:02 PM (320 days ago)

function WhenToTalkToGirl(t_time Time) {
     return (inf/0)
}

function HowToTalkToGirl(long procastination) {
     return HowToTalkToGirl(procastination++)
}

Orwellophile on 4/23/2009 6:57 PM (319 days ago)

DO NOT RUN ORWELLOPHILE'S CODE FOR SPEAKING TO A GIRL!!

Boom. Stack overflow on line 5.

JeremyX on 4/25/2009 12:49 PM (318 days ago)

Hi James,

I would really appreciate it if you made an odds calculator availiable for purchase (or even better, open source Laughing). My scripting just isn't good enough yet to follow this blog post.

Would be great to hear back from you!
Nick

Poker Forum on 4/26/2009 5:52 PM (317 days ago)

@James,
He did make the calculator available under open source, and there is two links to download it. It's very nice too, I've imported it into a DLL that can communicate with Perl, very nice and makes development a lot faster.

gman on 4/28/2009 11:04 AM (315 days ago)

@Mac... Weighted ranges are a great idea. Something like a degree of truth value for each hand, or for multiple ranges,

i.e {AA-QQ, AKs} 1.0, {JJ, AKo, AQs} : .8, ... junk: .01; etc.

Has anyone implemented something like that publicly?

LetsFocus on 4/28/2009 1:06 PM (315 days ago)

@Jeremy - stack overflow? pfff. dude is trying to end the universe in line 2! >:O

@James - Awesome series man. I won't be writing a poker-bot, but I WILL be rewriting my old Absolute Poker Helper app. no screen scraping or OCR this time. lol ;)

Keep 'em coming - I love this series! Laughing

aw on 4/29/2009 6:33 PM (314 days ago)

@ew - it happens, and I now know why bots love to fold ace high flushes...

function calcBreakEvenBet($pwin, $pot, $ev = 1.00) {
     return (float)($pwin * $pot) / ($ev - $pwin);
}

Bot with Ace high flush: "The most I can bet is $0.00? Holy crap Batman, I'd better fold this ace high flush on the river!"

World might as well have imploded.

@kevin - the hole card estimation is naïve bayesian, and has potential... during the evaluation process it's possible to generate a list of "winning-ish" hole cards... and in an ABC game, your opponents hole cards will be the intersection between these values and their betting.

Orwellophile on 4/30/2009 11:06 AM (313 days ago)

James,

I think there is a bug in AgnosticHand.cpp, namely in AgnosticHand::IsInclusive. For a hand like "T9-87", the function will always return false. Shouldn't


return !IsPair(handText) && ((textlen == 2) || (textlen == 3 && handText[2] == '+'));


be expanded to cover ranges? (it does get messy with all the different possibilities)
Also, as Bill Mill pointed out above, isn't there still a small bias by rotating the order in which you assign hands to players with ranges? Wouldn't shuffling the order of players be unbiased?

Thanks,
Marc.

Marc on 5/1/2009 5:21 PM (312 days ago)

With regard to the naive bayesian hand estimation, I threw together a PHP version of the 2+2 evaluation engine, and wrote a few quick classes in order to ask it the question:

What hole cards would beat me?

The results were ... encouraging.... here's a typical random board:

                                                 Board
                       9c 5d Th Ks 3h

                                                   Pair
                 22 23 25 29 2K 2T 34 36 37 38 3A 3J
                 3Q 44 45 49 4K 4T 56 57 58 5A 5J 5Q
                 66 69 6K 6T 77 79 7K 7T 88 89 8K 8T
                 9A 9J 9Q AA JJ JK KA QK QQ TA TJ TQ

                                               Two Pair
                 35 39 3K 3T 59 5K 5T 9K 9T TK

                                          Three of a Kind
                                        33 55 99 KK TT

                                                 Straight
                                                     JQ


So basically, you're good to go... unless you think that the guy in late position who raised the flop might possibly have a 99, KK, TT, or QJs ..... and what are the chances of that right? :-p

The way I see it, if someone bets into you, they have either one of the hands on that list, or they have a bluff. Using an adaptable evaluator like James has presented in this article, coupled with with a list of EV+ starting hands for each position, some PT3 magic, and a bit of special naive bayesian betting sauce ... and magic should happen.

Email me at your favourite place (no, not empornium) if you would like a copy of the PHP that drives it. Yes, HR[] lookup is in PHP. No it's not in memory.

Orwellophile on 5/3/2009 6:14 AM (310 days ago)

I found the work awesome, just my poor programming skills prevent me from doing anything useful as i wasn't able to import the DLL into a C# project.

I tried whatever is possible with p/invoke but without success. Can you please help me providing a DLL wrapper or whatever is possible to use it in a VStudio project?

Thanks A LOT

Alex

Alex_none on 5/17/2009 5:33 AM (296 days ago)

Great post. Lot of content of course, but I love your choice of graphics.

Born4Holdem on 6/9/2009 12:02 PM (273 days ago)

Hi guys!

"Testing preflop equities against the various preflop lookup tables that are available."

I'm currently searching for 2 and 3 player preflop lookup tables. Any idea where I can find some?

blak

Anonymous on 6/11/2009 7:36 AM (271 days ago)

Thanks for making this very useful code available. Is there any possibility that the Visual Studio project can be converted to a fileformat that can be opened with the Visual C++ 2005 Express Edition? Both the Express edition and the platform SDK is available for download from Microsoft, so the code examples can then be used by anyone, even people without a commercial licence of Visual Studio.

Anonymous on 6/11/2009 12:57 PM (271 days ago)

Oh ... 2008 has come out. ups

Anonymous on 6/12/2009 3:07 AM (270 days ago)

Please help. Am I the only one having this issue ?

When using
[code]
HoldemCalculator calc;
calc.Calculate(hd, bd, "", 0, result);
[/code]

With u full board (Holdem), that is 5 cards, the result is... unexpected.
[code]
hd=[AcAs|KsKc]
bd=[Ad9s3s4c7h]
result[0]=-1.#IND00
result[1]=-1.#IND00
[/code]
Here, you can see what this command :
printf("result[%d]=%f\n", i, result[i]);
... produces.

? Help pleaz.
If board is not on the river (preflop, flop and turn), it's ok. That works fine. I get equity numbers.

Grognon on 6/14/2009 5:50 AM (268 days ago)

Sorry, it seems like my BBCode did not work.

Grognon on 6/14/2009 5:51 AM (268 days ago)

Seems I found a bug in XPokerEquity Monte Carlo.
PokerStove tells AA has 48.21% vs 4 opps with JJ+.
HoldemCalculator().CalculateMC("AA|JJ+|JJ+|JJ+|JJ+", "", "", 500000 * 4, results) told that AA has 41.32% vs JJ+ (the more opponents the more the deviation).
I tried to increase the number of iterations but it didn't help.

I modified HoldemHandDistribution::Choose method near the place where 10 attempts are made to pick random hand without collission. I suspect there was an error there. We should perform only a single attempt. Though I can't explain why. I tried to change that line to "for (int attempt = 0; attempt < 1; attempt++)" and CalculateMC returned 48.20%

zecode on 6/19/2009 8:03 AM (263 days ago)

It's a damn shame that you didn't bother to separate the Windows specific stuff out so that OSX/Linux people can also enjoy your work. Your example is a "console app" and should be able to be built w/ g++ on Unix in a more straight forward manner.

Now I'm sitting here removing things that shouldn't be there in the first place Frown

Anonymous on 7/9/2009 7:31 PM (242 days ago)

>It's a damn shame that you didn't bother to separate the Windows specific stuff out so that OSX/Linux people can also enjoy your work. Your example is a "console app" and should be able to be built w/ g++ on Unix in a more straight forward manner.

>With u full board (Holdem), that is 5 cards, the result is... unexpected.

>Seems I found a bug in XPokerEquity Monte Carlo.

Thanks guys for noticing those value-added features. Will post the updated version shortly. Keep in mind this is quick and dirty code, when the repository is set up we'll look at some production stuff.

James Devlin on 7/10/2009 9:39 AM (242 days ago)

Lol

Alan Canes on 7/11/2009 9:02 AM (241 days ago)

Kudos! Damn good articles. I'd be very interested in learning how to optimize it, e.g. what kinds of isomorphisms exist apart from suit equivalence isomorphisms, any worthwhile checks to detect them, any worthwhile cheap checks for pruning, and (drum roll...) how to implement the GameShrink algorithm to detect any remaining isomorphisms. Also, I'm not sure what the best way is to store the probabilities for which player(s) win/tie in the case of a split pot or multiple split pots (this data would be necessary for GameShrink to work properly, as far as I understand).

Quatari on 7/12/2009 9:13 AM (240 days ago)

Thank you in advance!
Could someone help with the VB6
for Poker.Equity.dll

VB Declare statement
How to read the results structure

Thank you very much....

RMB on 7/27/2009 3:10 PM (225 days ago)

somewhere in the header "holdemcalculator.h" this comment is post:

// Write code similar to the following...
//
// double results[10];
// HoldemEquityCalculator calc;
// calc.Calculate("AhKh|Td9s|QQ+,AQs+,AQo+|JJ-88|XxXx|XxXx|XxXx", "Ks7d4d", "", 100000, results);
//
// Use the | symbol to separate players. Use the comman (,) to separate parts of a single player's
// range.

"HoldemEquityCalculator calc;" needs to be: "HoldemCalculator calc;"

I almost committed suicide not finding this error, but that's mainly because I'm a newbie at C++. I hope this post will save some lives. Nevertheless thanks for the calculator!

kasparov on 8/5/2009 5:30 PM (216 days ago)

I've been trying to alter the iterator to make it more easy compatible with my bot (and to avoid strings & chars in cpp, which I guess, you can all understand), but I'm finding it all very confusing and I believe I'm altering a lot more than allowed. Anyway my opponent model is of this format:
int Opponent_model[52][52][2][10][2];// //Opponent_model[card1][card2][on/off][player][playing/notplaying]
int board_cards[5][2];// board_cards[x][0] == cardx
//rank board_cards[x][1] == cardx suit


transforming this into a string and then into the struct used for the iteration gives me a lot of trouble, so I was hoping someone could help me out altering the converters for my model?

please reply trough this post,

Regards,

Kasparov

kasparov on 8/15/2009 9:05 AM (206 days ago)

Equity calculation when using 5 cards on board returns -1s.

But if last of 5 card is distribution, it's ok.

BOARD: 2h7s9h4hTh - bug
BOARD: 2h7s9h4hT - ok
BOARD: 2h7s9h4hXx - ok

Anon on 9/4/2009 6:39 PM (186 days ago)

update: Bug is only in exhaustive mode

Anon on 9/5/2009 8:33 AM (185 days ago)

Could someone tell me how to get this code working in VC++ 2008 I have opened the Project, but what now. How can I make my own console testfunction, compile and then uses it? I would just like something like the test Poker.Equity.Test.

Thanks in advance

dk on 9/8/2009 9:02 AM (182 days ago)

I was actually working on a web based fully enumerated multiway hand range evaluator back when you wrote this entry. I didn't read this entry till a couple weeks ago. When I realized that I couldn't realistically enumerate everything on a server, I put the project on hold and eventually just made a limited 2/3 player version.

After reading this entry, I decided to go back and add Monte Carlo simulation for a full 9 player hand range evaluator. It works pretty well on my web server.

So now I have 4 cases:
1) Where I can fully enumerate and have no board/discard cards.
(Can reuse more enumerations. I have some cool hand type compression code.)
2) Where I can fully enumerate and have some board/discard cards.
3) Where I need to use Monte Carlo but can still precompute unique hand combinations.
4) Where I need to use Monte Carlo and can't precompute unique hand combinations.

Thanks for giving me the green light to finish this.

Greg on 9/25/2009 12:00 AM (165 days ago)

Oh, I forgot the fastest case:
5) If just 2 players and no board/dead cards, just sum up some pre-enumerated hands.

Greg on 9/25/2009 9:08 AM (165 days ago)

@Greg why not use case 5 for 3 players (one with a known hand)?

use a 169 hand format with two tables, the first showing equity for each scenario and the second showing the number of possible occurances of each scenario.

This way you can do an exhaustive 3-way preflop equity calculation with only 28561 iterations.

I pity your CPU making these tables though.


Paul on 9/30/2009 10:59 PM (159 days ago)

sorry a maximum of 28561 iterations

Paul on 9/30/2009 11:01 PM (159 days ago)

aşk bir hayal izle

aşk bir hayal izle on 10/4/2009 4:39 PM (156 days ago)

If anyone is interested in doing multi-way analysis using Keith Rule's Evaluator I posted a method that calculates the trial results for a full table of players. It is written in IronPython, but should be pretty simple to convert to C# or VB.NET. I haven't tackled the pocket card selection algorithm yet, but this should be a good start for anyone that need more than just the heads up analysis that is built into the library.

Excellent article by the way... Keep up the great work!

Greg Bray on 11/1/2009 8:17 PM (127 days ago)

Can I use 64s+ to represent 1 gappers -> 64s, 75s, 86s, 97s, T8s, J9s, QTs, KJs, AQs

Dude on 11/5/2009 3:13 PM (124 days ago)

Also, how would I represent say, any TWO cards 7 or higher? (T7, 97, A8, K7, etc)

Dude on 11/5/2009 3:40 PM (124 days ago)

I use Exhaustive Enumeration mode to calc, but it's very slow.
Why PokerStove can so fast?

Defei Zhu on 1/19/2010 9:00 AM (49 days ago)

Thank you so much James for distributing this code. I have greatly enjoyed working with it on my first HE simulation project. It is very easy to use and quite intuitive and reasonable fast. But after running some lengthy sims, my computer has run out of memory (4Gb)! I have found the memory leak! In HoldemCalculator::CreateHandDistributions(const char* hands) you assign a pointer pCur to a "new" memory location, but never delete it. You can quickly remove this memory leak by adding a function to delete all the elements of m_dists[].

declare the new function in HoldemCalculator.h under public:
void delete_m_dists();

in HoldemCalculator.cpp:
void HoldemCalculator::delete_m_dists()
{
for (int hand = 0; hand < m_dists.size(); hand++)
{
delete m_dists[hand];
}
}

And then in your main code, whenever you call calc.MCcalculate or calc.EEcalculate, just call calc.delete_m_dists() afterwords to free up the assigned memory location.

Now the program runs at a steady 980K and can run days on end! Thanks again for the great code and article and I hope this memory leak catch helps someone else out!



Drew on 1/29/2010 2:00 AM (39 days ago)

I've found that hands notation differs from PokerStove.

XPokerEquity KJs+ means KJs,AQs
PokerStove KJs+ means KJs,KQs

Am I right? Is it bug or feature?
Anyway this can lead to unexpected findings when comparing PokerStove with XPokerEquity results.

snow_max on 2/9/2010 8:49 AM (28 days ago)

Nevertheless how to use library Poker.Equity.dll? To include in the project on C# not possible. I at all do not know С++.

Michael

Anonymous on 3/4/2010 2:25 PM (5 days ago)

Hello james, i have been trying to read all your posts last two weeks and finally.....i mean finally understood some stuff to say and code you put!!!
Im really thankful to you...you are a very smart and funny person. I was wondering why u didnt update this amazing blog..please...please update it!!!

On other hand...i have a question about your equitycalculator. Why do you think that the already built equitytest.exe run faster than the one I build with the same files you uploaded? I mean...if i run the .exe it runs smooth...but if I rebuild the .exe on my visual studio enviroment...it runs a lot slower....

How can it be possible??? It is a really importat thing to me because I want to do some coding on your source...and when the time to building come..its gonna be a slow program.

Thank you in advance! and again..update you blog. You are a genius man...or at least a very crazy man!!!

Alberto on 3/6/2010 1:21 AM (3 days ago)

>I use Exhaustive Enumeration mode to calc, but it's very slow. Why PokerStove can so fast?

PokerStove is heavily optimized. The source code in this example is a naive approach.

>I've found that hands notation differs from PokerStove.

Yep. Hand range notation hasn't quite standardized yet, though a little calculator like this should probably follow PokerStove's example. Interestingly though, people have vastly different ideas about what a given hand range signifier actually signifies. Smile There was a 2p2 thread on this a while back...

>I have found the memory leak! In HoldemCalculator...

Awesome! I have had zero time to look at this since I posted it. Thanks to all who contributed code or suggestions, I will bundle these updates and update the source.

>Why do you think that the already built equitytest.exe run faster than the one I build with the same files you uploaded?

Are you building in Release mode? (rather than Debug?)

James Devlin on 3/6/2010 1:37 PM (3 days ago)

Comment on this post:

Thanks for your interest in Coding the Wheel. All fields are optional.