|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIn 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:
Part 2 will cover these topics:
Pocket Query LanguageIf 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.
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.
I've also added some operators.
Why a Pocket Query LanguageOne 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 codeI'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
ulong player2Mask = Hand.RandomHand(player2, player1Mask, 2);
// Pick a random board
ulong boardMask
= Hand.RandomHand(player1Mask | player2Mask, 5);
// Create a hand value for each player
uint player1HandValue =
Hand.Evaluate(boardMask | player1Mask, 7);
uint player2HandValue =
Hand.Evaluate(boardMask | player2Mask, 7);
// 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 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
foreach (ulong oppmask in PocketHands.Query("Connected Suited", mask))
{
// 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"))
{
foreach (ulong oppmask in PocketHands.Query(
"Connected Offsuit", playermask))
{
foreach (ulong board in Hand.Hands(0UL, playermask | oppmask, 5))
{
// 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
ulong[] masks = PocketHands.Query("Connected Suited");
for (int trials = 0; trials < 100000; trials++)
{
// Select a random player hand from the list of possible
// Connected Suited hands.
ulong randomPlayerHandMask = Hand.RandomHand(masks, 0UL, 2);
// Get a random opponent hand
ulong randomOpponentHandMask = Hand.RandomHand(randomPlayerHandMask, 2);
// Get a random board
ulong boardMask =
Hand.RandomHand(randomPlayerHandMask | randomOpponentHandMask, 5);
// Insert evaluation here
}
Outs and drawsMost Hold'em players know what outs are. According to Wikipedia:
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:
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
ulong outsmask =
Outs(Hand.ParseHand(pocket), Hand.ParseHand(board));
Console.WriteLine("[{0}] {1} : Outs Count {2}",
pocket, board, Hand.BitCount(outsmask));
// List the cards
foreach (string card in Hand.Cards(outsmask))
{
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);
// Look ahead one card
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)
{
// Add card to outs mask
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:
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:
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
ulong outsmask =
Outs(Hand.ParseHand(pocket), Hand.ParseHand(board));
Console.WriteLine("[{0}] {1} : Outs Count {2}",
pocket, board, Hand.BitCount(outsmask));
// List the cards
foreach (string card in Hand.Cards(outsmask))
{
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);
// Look ahead one card
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)
{
// Add card to outs mask
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 static int OutsDiscounted(ulong player, ulong board, params ulong[] opponents)
The ulong OutsMaskDiscounted(ulong player, ulong board, params ulong[] opponents)
The Draw variationI'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. StraightDrawCountThe public static int StraightDrawCount(ulong player, ulong board, ulong dead)
IsOpenEndedStraightDrawThe public static bool IsOpenEndedStraightDraw(ulong pocket,
ulong board, ulong dead)
IsGutShotStraightDrawThe method public static bool IsGutShotStraightDraw(ulong pocket,
ulong board, ulong dead)
IsStraightDrawThe method public static bool IsStraightDraw(ulong pocket, ulong board, ulong dead)
IsOpenEndedStraightDrawThe method public static bool IsOpenEndedStraightDraw(ulong pocket,
ulong board, ulong dead)
FlushDrawCountThis 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)
IsFlushDrawThis method returns true if there are 4 cards of the same suit. public static bool IsFlushDraw(ulong pocket, ulong board, ulong dead)
IsBackdoorFlushDrawThis 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)
DrawCountThis method returns the number of draws that are possible for the specified public static int DrawCount(ulong player, ulong board,
ulong dead, Hand.HandTypes type)
Demo programsI'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:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|