12,632,113 members (26,507 online)
alternative version

179.3K views
114 bookmarked
Posted

More Texas Holdem Analysis in C#: Part 1

, 20 May 2008 GPL3
 Rate this:
Using C# to do sophisticated analysis of Texas Holdem

Introduction

In December 2005, I published "Fast, Texas Holdem Hand Evaluation and Analysis" which was my first Texas Holdem Analysis article. Since then, I've done a lot of enhancements and extensions to the code. I've also had several folks more skilled in poker analysis offer their advice. This update to the poker library offers many new and unique features that make analysis of Texas Holdem much easier. In this article, you will find the following topics covered:

• Pocket Query Language - This offers a straightforward set of methods that allow you experiment with different pocket hand combinations without rewriting your code.
• Outs and draw methods - Determining outs and draws is key to analyzing a situation. This section discusses techniques for doing just that.

Part 2 will cover these topics:

• Monte Carlo analysis - Sometimes quick estimates are good enough. Monte Carlo Analysis makes it possible to make high quality estimates in a very short time.
• Multi-player hand analysis - More often than not, you are playing against multiple players. Here's how to calculate win odds against multiple players in a reasonable amount of time.
• Multiple core support - Using multiple cores to decrease calculation time. The speed ups using this technique are nearly linear when adding cores.

Pocket Query Language

If you are reading this article, I'm sure you've read a book or two on Holdem. One of the things I've noticed while reading poker books is that there is a de facto standard for describing pocket cards. Most books use some variant of the following to describe pocket hands.

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 2.25pt solid; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 6.65in; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="638" colspan="3">

Pocket Hand Description Language

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 159.6pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-bottom-alt: solid windowtext .5pt" valign=removed" width="213">

Example

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; FONT-SIZE: 11pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-bottom-alt: solid windowtext .5pt" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; FONT-SIZE: 11pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-bottom-alt: solid windowtext .5pt" valign=removed" width="391">

Description

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 159.6pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent; mso-border-top-alt: solid windowtext .5pt" valign=removed" width="213">

Ac Kd

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent; mso-border-top-alt: solid windowtext .5pt" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent; mso-border-top-alt: solid windowtext .5pt" valign=removed" width="391">

Specific cards are specified using letters and numbers for ranks such 'A'-ace, 'K'-king, 'Q'-queen, 'J'-jack, 'T'-10, '9'-nine and so on. Suits are described usually in lower case as 'c'-clubs, 's'-spades, 'h'-hearts and 'd'-diamonds.

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 159.6pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="213">

AKs, 78s

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="391">

Card combinations where only the card ranks are specified and the 's' indicates they are suited.

QJ, T8

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="391">

Card combinations where the card ranks are specified and the cards suits are unsuited or offsuit.

Kx, Ax

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 2.25pt solid; BACKGROUND-COLOR: transparent" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 2.25pt solid; BACKGROUND-COLOR: transparent" valign=removed" width="391">

This notation indicates a card rank and another unknown card which is less than 9 and doesn't form a pair.

Most poker software that parses string representations of pocket hands only supports the first item in the de facto standard. I've implemented a much richer query language. I support all of the common poker book syntax, plus some extensions.

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 2.25pt solid; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 6.65in; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="638" colspan="3">

The Extended Pocket Hand Description Language

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 159.6pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-bottom-alt: solid windowtext .5pt" valign=removed" width="213">

Example

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; FONT-SIZE: 11pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-bottom-alt: solid windowtext .5pt" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; FONT-SIZE: 11pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-bottom-alt: solid windowtext .5pt" valign=removed" width="391">

Description

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 159.6pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent; mso-border-top-alt: solid windowtext .5pt" valign=removed" width="213">

Group1

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent; mso-border-top-alt: solid windowtext .5pt" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent; mso-border-top-alt: solid windowtext .5pt" valign=removed" width="391">

Any starting hand in Sklansky group1. Group1 through Group8 are supported.

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 159.6pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="213">

Suited

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="391">

Any set of pocket cards that are of the same suit.

Offsuit

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="391">

Any set of pocket cards that are of different suits.

Connected

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="391">

Any pocket card set, such as AK, that are adjacent in rank.

Gap1

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="391">

Any pocket card set, such as AQ which have a 1 card.

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 159.6pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="213">

Gap2

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="391">

Any pocket card set, such as AJ which have a 2 card.

Gap3

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 2.25pt solid; BACKGROUND-COLOR: transparent" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 2.25pt solid; BACKGROUND-COLOR: transparent" valign=removed" width="391">

Any pocket card set, such as AT which have a 3 card.

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 2.25pt solid; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 6.65in; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="638" colspan="3">

Pocket Hand Description Language Basic Operators

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 159.6pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-bottom-alt: solid windowtext .5pt" valign=removed" width="213">

Example

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; FONT-SIZE: 11pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-bottom-alt: solid windowtext .5pt" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; FONT-SIZE: 11pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 1pt solid; BACKGROUND-COLOR: transparent; mso-border-bottom-alt: solid windowtext .5pt" valign=removed" width="391">

Description

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 159.6pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent; mso-border-top-alt: solid windowtext .5pt" valign=removed" width="213">

Group2 To Group5

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent; mso-border-top-alt: solid windowtext .5pt" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent; mso-border-top-alt: solid windowtext .5pt" valign=removed" width="391">

Sklansky groups can be specified as a range. Ranges use the '-' character or the word 'to' or the word 'Through'

Group3 And Offset

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="391">

The intersection operator can be used with any pair of expressions. The resulting set is the intersection of the two expressions. The keywords: 'And', '&' and 'Intersection' can all be used to represent this operation.

Group1 Or Group3

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="391">

The union operator can be used with any pair of expressions. The resulting set is the union of the two expressions. The keywords 'Or', '|' and 'Union' can all represent this operation.

Not Offset

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: #f0f0f0; BACKGROUND-COLOR: transparent" valign=removed" width="391">

The 'Not' operator returns the 1326 set of possible pocket cards minus the items defined in the expression on the right.

(AK* | AA) & Offset

<td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 25.8pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 2.25pt solid; BACKGROUND-COLOR: transparent" valign=removed" width="34"> <td style="BORDER-RIGHT: #f0f0f0; PADDING-RIGHT: 5.4pt; BORDER-TOP: #f0f0f0; PADDING-LEFT: 5.4pt; PADDING-BOTTOM: 0in; BORDER-LEFT: #f0f0f0; WIDTH: 293.4pt; PADDING-TOP: 0in; BORDER-BOTTOM: windowtext 2.25pt solid; BACKGROUND-COLOR: transparent" valign=removed" width="391">

Parenthesis may be used to group operations.

Why a Pocket Query Language

One of the things I've done too many times has been to write code to analyze specific match-ups. For example, have you ever wondered what the advantage is to having a suited connector versus a non-suited connector? I'd guess that you've probably wondered, but weren't enough of a masochist to write the code. On the other hand, I am enough of a masochist to write the code for many, many match-ups. After awhile, I decided I'd had enough of that and wrote a query language so that I could write my match-up analysis once and just put in query strings. The following is an example of the result of using query strings rather than hard coded match-ups. Oh, and there is about a 5% advantage for suited connectors.

Using Pocket Queries in code

I've attempted to make it trivial to write analysis code that utilized Pocket Queries. Here's an example.

```using System;
using System.Collections.Generic;
using System.Text;
using HoldemHand;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// A Pocket Query Returns an array of all
// hands that meet the criterion.
ulong[] player1 = PocketHands.Query("Connected Suited");
ulong[] player2 = PocketHands.Query("Connected Offsuit");

// Holds stats
long player1Wins = 0, player2Wins = 0,
ties = 0, count = 0;

// Iterate through 10000 trials.
for (int trials = 0; trials < 10000; trials++)
{
// Pick a random pocket hand out of
// player1's query set
ulong player1Mask = Hand.RandomHand(player1, 0UL, 2);

// Pick a random pocket hand for player2

// Pick a random board

// Create a hand value for each player
uint player1HandValue =
uint player2HandValue =

// Calculate Winners
if (player1HandValue > player2HandValue)
{
player1Wins++;
}
else if (player1HandValue < player2HandValue)
{
player2Wins++;
}
else
{
ties++;
}
count++;
}
// Print results
Console.WriteLine("Player1: {0:0.0}%",
(player1Wins + ties / 2.0) / ((double)count) * 100.0);
Console.WriteLine("Player2: {0:0.0}%",
(player2Wins + ties / 2.0) / ((double)count) * 100.0);
}
}
}```

Notice that I've added a new class to the Holdem library. It's called `PocketHands`. Here are some simple examples of how to use this class:

The simplest way to use this class is to iterate through all possible pocket hands for the specified Pocket Query.

```// This will iterate through all the possible "connected suited" pocket hands
foreach (ulong pocketmask in PocketHands.Query("Connected Suited"))
{
// Insert calculation here.
}```

Another way to use this class is to iterate through a Pocket Query given a specific hand match-up.

```// Looks at an AKs match up (specifically As Ks) against all possible
// opponents hands that are connected and suited.
ulong mask = Hand.Evaluate("As Ks"); // AKs
{
// Insert calculation here.
}```

This example loops exhaustively through two specific Pocket Query match-ups.

```// Iterates through all possible "Connected Suited" versus
// "Connected Offsuit" match ups.
foreach (ulong playermask in PocketHands.Query("Connected Suited"))
{
{
{
// Insert Calculation Here
}
}
}
```

It's also possible to use pocket queries while doing random samples of match-ups.

```// Randomly selects 100000 possible hands when player starts with a
// suited connector
for (int trials = 0; trials < 100000; trials++)
{
// Select a random player hand from the list of possible
// Connected Suited hands.
// Get a random opponent hand
// Get a random board
// Insert evaluation here
}
```

Outs and draws

Most Hold'em players know what outs are. According to Wikipedia:

[A]n out is any unseen card that, if drawn, will improve a player's hand to one that is likely to win

The question for the programmer is, "Is this definition sufficient to write a function that returns the cards that are outs?" Let's look at the two key points made by Wikipedia. They are:

1. The hand must improve.
2. The new hand is likely to win.

Let's start by writing a function that meets the first criterion.

```using System;
using HoldemHand;

// A first try at calculating outs
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string pocket = "As Ac";
string board = "Kd 8h 9c";
// Calcuate the outs
Outs(Hand.ParseHand(pocket), Hand.ParseHand(board));

Console.WriteLine("[{0}] {1} : Outs Count {2}",

// List the cards
{
Console.Write("{0} ", card);
}
Console.WriteLine();
}

// Return a hand mask of the cards that improve our hand
static ulong Outs(ulong pocket, ulong board)
{
ulong retval = 0UL;
ulong hand = pocket  board;

// Get original hand value
uint playerOrigHandVal = Hand.Evaluate(hand);

foreach (ulong card in Hand.Hands(0UL, hand, 1))
{
// Get new hand value
uint playerNewHandVal = Hand.Evaluate(hand  card);

// If the hand improved then we have an out
if (playerNewHandVal > playerOrigHandVal)
{
retval = card;
}
}

// return outs as a hand mask
return retval;
}
}
}```

Passing this starting hand A♠ A♣, K♦ 8♥ 9♣ into our new method returns the following outs:

1. K♠, 9♠, 9♥, 8♠, K♥, 9♦, 8♦, K♣, 8♣ - Two pair
2. A♥, A♦ - Trips
3. Q♠, J♠, T♠, Q♥, J♥, T♥, Q♦, J♦, T♦, Q♣, J♣, T♣ - Improves Kicker

I think most people would agree that super-sizing your kicker probably doesn't help much here. I think most people would also agree that improving the board doesn't help either. So, let's add two more rules:

1. The hand must improve.
2. The new hand improvement must be better than an improved kicker.
3. The new combined hand must be stronger than the just the board.
4. The new hand is likely to win.

The following example handles these new rules and allows opponent hands to be added.

```using System;
using HoldemHand;

// A first try at calculating outs
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string pocket = "As Ac";
string board = "Kd 8h 9c";
// Calcuate the outs
Outs(Hand.ParseHand(pocket), Hand.ParseHand(board));

Console.WriteLine("[{0}] {1} : Outs Count {2}",

// List the cards
{
Console.Write("{0} ", card);
}
Console.WriteLine();
}

// Return a hand mask of the cards that improve our hand
static ulong Outs(
ulong pocket, ulong board, params ulong [] opponents)
{
ulong retval = 0UL;

// Get original hand value
uint playerOrigHandVal = Hand.Evaluate(pocket  board);

foreach (ulong card in Hand.Hands(0UL, board  pocket, 1))
{
// Get new hand value
uint playerNewHandVal = Hand.Evaluate(pocket  board  card);

// Get new board value
uint boardHandVal = Hand.Evaluate(board  card);

// Is the new hand better than the old one?
bool handImproved =
playerNewHandVal > playerOrigHandVal &&
Hand.HandType(playerNewHandVal) > Hand.HandType(
playerOrigHandVal);

// This compare ensures we move up in hand type.
bool handStrongerThanBoard =
Hand.HandType(playerNewHandVal) > Hand.HandType(
boardHandVal);

// Check against opponents cards
bool handBeatAllOpponents = true;
if (handImproved && handStrongerThanBoard &&
opponents != null && opponents.Length > 0)
{
foreach (ulong opponent in opponents)
{
uint opponentHandVal =
Hand.Evaluate(opponent  board  card);
if (opponentHandVal > playerNewHandVal)
{
handBeatAllOpponents = false;
break;
}
}
}

// If the hand improved then we have an out
if (handImproved && handStrongerThanBoard &&
handBeatAllOpponents)
{
retval = card;
}
}

// return outs as a hand mask
return retval;
}
}
}```

The problem with outs calculation is that you often don't know the opponent cards you are up against. That makes this calculation a bit subjective. I've had many discussions with different folks about this. One of the more interesting discussions was with Matt Baker. He rewrote my outs function (shown above) to include a heuristic that tries to more accurately account for opponent cards. I won't go into that here, but you can use his code. He generously provided `OutsDiscounted` and `OutsMaskDiscounted`.

`static int OutsDiscounted(ulong player, ulong board, params ulong[] opponents)`

The `OutsDiscounted` function returns the number of outs for the specified player's pocket hand, the current board and optionally the opponent pocket hands.

`ulong OutsMaskDiscounted(ulong player, ulong board, params ulong[] opponents)`

The `OutsMaskDiscounted` function returns a card mask of all of the cards that are outs. You can turn this mask into a string by calling `MaskToString()`.

Draw variation

I've had several folks contact me about other functions that focus on specific outs calculations. These are referred to as "draw" calculations. Wesley Tansey requested several variants of draw functions and offered to test them for me. The result is the following set of functions.

StraightDrawCount

The `StraightDrawCount` method returns the number of straight draws that are possible for the player, board and dead card configuration. It also filters the results so only player hand improvements are counted.

```public static int StraightDrawCount(ulong player, ulong board, ulong dead)
```

IsOpenEndedStraightDraw

The `IsOpenEndedStraightDraw` function returns true if the combined mask is an open-ended straight draw. Only straight possibilities that improve the player's mask are considered in this method.

```public static bool IsOpenEndedStraightDraw(ulong pocket,

IsGutShotStraightDraw

The method `IsGutShotStraightDraw` returns true if the combined cards contain a gut shot straight draw.

```public static bool IsGutShotStraightDraw(ulong pocket,

IsStraightDraw

The method `IsStraightDraw` returns true if the combined cards contain a straight draw.

`public static bool IsStraightDraw(ulong pocket, ulong board, ulong dead)`

IsOpenEndedStraightDraw

The method `IsOpenEndedStraightDraw` returns true if the combined cards contain an open-ended straight draw.

```public static bool IsOpenEndedStraightDraw(ulong pocket,

FlushDrawCount

This method counts the number of hands that are a flush with one more drawn card. However, only flush hands that improve the board are considered.

`public static int FlushDrawCount(ulong player, ulong board, ulong dead)`

IsFlushDraw

This method returns true if there are 4 cards of the same suit.

`public static bool IsFlushDraw(ulong pocket, ulong board, ulong dead)`

IsBackdoorFlushDraw

This method returns true if there are three cards of the same suit. The pocket cards must have at least one card in that suit.

`public static bool IsBackdoorFlushDraw(ulong pocket, ulong board, ulong dead)`

DrawCount

This method returns the number of draws that are possible for the specified `HandType`. It only returns the counts that improve the player's mask, rather than just the board.

```public static int DrawCount(ulong player, ulong board,

Demo programs

I've included several demo programs and examples in the downloadable project. This is a quick summary of each of the demo applications:

MultiOddsApp

This application graphs several interesting values to allow hand/board scenarios to be scenarios against 1 through 9 opponents. This demo application accepts Pocket Query descriptions for the Player Pocket field. These values include:

• Win - These are the odds that the given specified hand will end up the winning hand.
• Hand Strength - These are the odds that the given hand -- not including any future dealt cards -- is currently the best hand.
• Positive Potential - These are the odds that a currently losing hand will improve to be a winner.
• Negative Potential - These are the odds that a current winning hand will become a loser.

Hand Odds

This application allows a player pocket hand and an opponent pocket hand definition -- using the Pocket Query Language -- to be entered along with a board definition. The resulting odds are returned for a single random opponent. Since this takes the Pocket Query Language as input, it makes it very easy to try all kinds of pocket hand scenarios that would otherwise require a fair amount of programming to determine the answer.

Acknowledgements

• Wesley Tansey - For a lot of help with functions determining different variations of outs.
• Matt Baker - For many discussions on outs and for providing the `DiscountedOuts` functions.
• Scott Turner - For using and providing feedback on enhancements.
• poker-eval - The core to my evaluation engine.
• ZedGraph - I've used ZedGraph in several of the examples that graph results.
• Malcolm Crowe - Author of the lexer generator/parser generator tools for C# that I used for the Pocket Query Language.

Disclaimer

I could easily have spent another 6 months editing and tweaking the code and examples. I decided that I had spent enough time on this and had a "critical mass" of features available. So, here is my current code, warts and all.

I don't claim to be a great poker player or even a good one, for that matter. To paraphrase: those who can do, those who can't code. I don't recommend taking any poker advice I may have given (or implied to have given) in this article without independent verification.

I'd also like to encourage feedback. Poker programmers appear to be a somewhat secretive lot, for good reason I'm sure. I've learned a lot from the feedback I've been given over the last year or so. Please keep it coming.

History

First released May 2007

Share

 Software Developer (Senior) United States
I work at Tektronix in Beaverton OR. I've been programming for fun since 1975 (I started while in a Computer Explorer Scout group in Spokane WA). I've been programming in C since 1979 and I've been working professionally since 1983.

I really enjoy www.codeproject.com. It has saved me an incredible amount of time. I only hope my small contributions have given back some of what I've taken.

You may also be interested in...

 Pro Pro

 View All Threads First Prev Next
 Hand Distance Discounted Function mjb4-Dec-09 12:28 mjb 4-Dec-09 12:28
 Hand Distance Discounted Function Test Cases mjb4-Dec-09 12:34 mjb 4-Dec-09 12:34
 Last Visit: 31-Dec-99 19:00     Last Update: 8-Dec-16 8:40 Refresh 1