|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIn Part 1 of this article, you will find the following topics covered:
In this article, we will cover the following topics.
Monte Carlo analysisMonte Carlo analysis can be used in analyzing poker hands. There are many reasons people choose to use Monte Carlo Analysis. In poker, the most common reason is to get a quick answer for something that otherwise would take a prohibitively long time to calculate. I resisted using Monte Carlo analysis for quite some time. I clung to my wish that a sufficiently fast Poker Hand Evaluator Library would eliminate the need for woo-woo techniques like this. To convince myself of the value of Monte Carlo analysis, I had to work through the analysis of win odds for one or more players. The following two sections go through my logic for accepting Monte Carlo analysis. Hopefully you will see the value of it and how easy it is to do.Calculating win oddsConsider the following code. It calculates the win odds for a player being dealt AKs against a random opponent. This program takes about 299 seconds (nearly 5 minutes) on my laptop to calculate with no board cards. That means the code is evaluating 14,084,025 hands/sec and a total of 2,097,572,400 (yes, billions) of hand match-ups. With three cards on the board, it takes 0.263 seconds and a total of 1,070,190 hand match-ups. When there are cards on the board, this function is very usable. However, if you are calculating hole card win probabilities with this, then the algorithm is far too slow to be usable. using System;
using System.Collections.Generic;
using HoldemHand;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// This code calculates the probablity of As Ks winning against
// another random hand.
ulong pocketmask = Hand.ParseHand("As Ks"); // Hole hand
ulong board = Hand.ParseHand(""); // No board cards yet
long wins = 0, ties = 0, loses = 0, count = 0;
// Iterate through all possible opponent hole cards
foreach (ulong oppmask in Hand.Hands(0UL, board | pocketmask, 2))
{
// Iterate through all board cards
foreach (ulong boardmask in Hand.Hands(
board, pocketmask | oppmask, 5))
{
// Evaluate the player and opponent hands and
// tally the results
uint pocketHandVal =
Hand.Evaluate(pocketmask | boardmask, 7);
uint oppHandVal = Hand.Evaluate(oppmask | boardmask, 7);
if (pocketHandVal > oppHandVal)
{
wins++;
}
else if (pocketHandVal == oppHandVal)
{
ties++;
}
else
{
loses++;
}
count++;
}
}
// Prints: Win 67.0446323092352%
Console.WriteLine("Win {0}%",
(((double)wins) + ((double)ties) / 2.0) / (
(double)count) * 100.0);
}
}
}
I've gotten around problems like this in the past by pre-calculating the exact values and then using lookup tables. However, there many situations in Texas Holdem where the computation time required is very long and lookup tables aren't feasible, such as calculating odds win odds against multiple players. Because of this, other techniques need to be considered for some problems. That's why I first looked into Monte Carlo analysis. Getting "good enough" results fastI'm not a perfectionist and apparently neither are many of the successful professional Texas Holdem Players. In Phil Gordon's Little Green Book -- a very good book, I might add -- he describes a very simple heuristic that can be calculated in your head and that gives the approximate odds of hitting your "outs." He calls this "the rule of 4 and 2." The way this heuristic works is that you count your outs. If you've just seen the flop and not the turn card, then multiply the outs by 4 and you will have the approximate percentage of hitting your outs. If you've seen the turn but not the river, you multiply the outs by 2 to get the approximate odds of hitting your outs. These types of simple estimates are used by many of the professional players to help evaluate their situation. If an estimate is good enough for them, then it's probably good enough for the rest of us if used correctly. Monte Carlo Analysis is just a method for a computer to quickly estimate the odds in a specific situation. Consider our previous example of calculating the win odds for AKs. It would take 299 seconds to iterate through all the possible situations and give us an exact answer. The following table was calculated using Monte Carlo analysis. The number on the left is the number of trials and the second number is the estimated win odds. The exact odds were 67.0446323092352%. The third column is the difference between the exact answer and the estimate answer. The fourth column is the time taken in seconds.
Notice that at 0.00071 seconds (0.7 milliseconds) we have 2 good digits. At 0.02197 (22 milliseconds) we have about 3 good digits. At 0.79401 (794 milliseconds) we have 4 good digits. It's easy to see that we can get very good estimates in well less than a second. The following code was used to generate the previous table. using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using HoldemHand;
namespace WinOddsMonteCarlo
{
///
/// This program shows the increase in accuracy of the Monte Carlo method
/// when increasing the number of samples.
///
class Program
{
static void Main(string[] args)
{
// This code calculates the probablity of As Ks winning against
// another random hand.
ulong pocketmask = Hand.ParseHand("As Ks"); // Hole hand
ulong board = Hand.ParseHand(""); // No board cards yet
// Trial numbers
int[] trialsTable = {
10, 50, 100, 500, 1000, 5000, 10000, 15000, 20000,
25000, 30000, 40000, 50000, 100000, 150000, 200000,
500000, 1000000, 2000000, 5000000, 10000000, 20000000
};
// timer values
double start = 0.0;
Console.WriteLine("Trials,Wins,Difference,Duration");
foreach (int trials in trialsTable)
{
long wins = 0, ties = 0, count = 0;
// Get start time
start = Hand.CurrentTime;
// Iterate through a series board cards
foreach (ulong boardmask in Hand.RandomHands(board,
pocketmask, 5, trials))
{
// Get a random opponent hand
ulong oppmask =
Hand.RandomHand(boardmask | pocketmask, 2);
// Evaluate the player and opponent hands
uint pocketHandVal =
Hand.Evaluate(pocketmask | boardmask, 7);
uint oppHandVal = Hand.Evaluate(oppmask | boardmask, 7;
// Calculate Statistics
if (pocketHandVal > oppHandVal)
{
wins++;
}
else if (pocketHandVal == oppHandVal)
{
ties++;
}
count++;
}
double duration = Hand.CurrentTime - start;
// Correct answer is 67.0446323092352%
Console.WriteLine("{0},{1:0.00}%,{2:0.0000},{3:0.00000}",
trials,
(((double)wins) + (
(double)ties) / 2.0) / ((double)count) *100.0,
Math.Abs(67.0446323092352 - ((((double)wins) +
((double)ties) / 2.0) / ((double)count) * 100)),
duration);
}
}
}
}
Monte Carlo analysis helper methodsThe following method is intentionally similar to the public static IEnumerable RandomHands(
ulong shared, ulong dead, int ncards, int trials)
This public static IEnumerable RandomHands(
ulong shared, ulong dead, int ncards, double duration)
This method is similar to the previous method. However, rather than specifying the number of trials, a time duration is specified. This allows a time budget to be specified for getting an answer. I have a mild preference to using this version of these methods. Calculating win odds for multiple playersIt's interesting to calculate odds against one player. However, more often than not, you are playing against multiple players. The following example shows how to calculate the win odds for the same situation with 1 through 9 players. using System;
using HoldemHand;
// This example calculates the win odds for a player having "As Ks" against
// 1-9 random players
namespace WinOddsMultipleOpponents
{
class Program
{
// Expected output (approximate values)
// 1: win 67.08%
// 2: win 50.82%
// 3: win 41.53%
// 4: win 35.46%
// 5: win 31.13%
// 6: win 27.82%
// 7: win 24.99%
// 8: win 22.77%
// 9: win 20.77%
static void Main(string[] args)
{
const double time = 5.0;
ulong pocket = Hand.ParseHand("As Ks");
ulong board = Hand.ParseHand("");
ulong dead = Hand.ParseHand("");
for (int opponents = 1; opponents <= 9; opponents++)
{
Console.WriteLine("{0}: win {1:0.00}%",
opponents, WinOddsMonteCarlo(
pocket, board, dead, opponents, time) * 100.0);
}
}
// An example of how to calculate win odds for multiple players
static double WinOddsMonteCarlo(
ulong pocket, ulong board, ulong dead, int nopponents,
double duration)
{
System.Diagnostics.Debug.Assert(
nopponents > 0 && nopponents <= 9);
System.Diagnostics.Debug.Assert(
duration > 0.0);
System.Diagnostics.Debug.Assert(
Hand.BitCount(pocket) == 2);
System.Diagnostics.Debug.Assert(
Hand.BitCount(board) >= 0 && Hand.BitCount(board) <= 5);
// Keep track of stats
double win = 0.0, count = 0.0;
// Keep track of time
double start = Hand.CurrentTime;
// Loop for specified time duration
while ((Hand.CurrentTime-start) < duration)
{
// Player and board info
ulong boardmask = Hand.RandomHand(board, dead | pocket, 5);
uint playerHandVal = Hand.Evaluate(pocket | boardmask);
// Ensure that dead, board, and pocket cards are not
// available to opponent hands.
ulong deadmask = dead | boardmask | pocket;
// Comparison Results
bool greaterthan = true;
bool greaterthanequal = true;
// Get random opponent hand values
for (int i = 0; i < nopponents; i++)
{
// Get Opponent hand info
ulong oppmask = Hand.RandomHand(deadmask, 2);
uint oppHandVal = Hand.Evaluate(oppmask | boardmask);
// Remove these opponent cards from future opponents
deadmask |= oppmask;
// Determine compare status
if (playerHandVal < oppHandVal)
{
greaterthan = greaterthanequal = false;
break;
}
else if (playerHandVal <= oppHandVal)
{
greaterthan = false;
}
}
// Calculate stats
if (greaterthan)
win += 1.0;
else if (greaterthanequal)
win += 0.5;
count += 1.0;
}
// Return stats
return (count == 0.0 ? 0.0 : win/count);
}
}
}
The method Calculating win odds for all pocket handsA simple variation of the above program can be used to calculate the approximate win odds for each of the 169 possible starting pocket hands for 1-9 opponents. Note that I've include the resulting table that gives approximate odds for a representative example of every possible starting hand for 1 though 9 opponents. I haven't run across the equivalent information online and thought that might be interesting for some players. using System;
using HoldemHand;
// This example calculates the win odds for a player having "As Ks" against
// 1-9 random players. The results is comma separated so that
// it can be imported
// into Excel using the .csv file type.
namespace WinOddsMultipleOpponentsTable
{
class Program
{
static void Main(string[] args)
{
const double time = 5.0;
ulong board = Hand.ParseHand("");
ulong dead = Hand.ParseHand("");
// Table Header
Console.Write(",");
for (int i = 1; i <= 9; i++)
{
Console.Write("{0},", i);
}
Console.WriteLine();
// Iterates through one representative hand of each of the
// 169 possible
// pocket hand types
foreach (ulong pocket in PocketHands.Hands169())
{
// Show Pocker Hand
Console.Write("\"{0}\",", Hand.MaskToString(pocket));
// Calculate and Display the Approximate odds for 1-9
// opponents
for (int opponents = 1; opponents <= 9; opponents++)
{
Console.Write("{0}%,", WinOddsMonteCarlo(
pocket, board, dead, opponents, time) * 100.0);
}
Console.WriteLine();
}
}
// An example of how to calculate win odds for multiple players
static double WinOddsMonteCarlo(ulong pocket, ulong board, ulong dead,
int nopponents, double duration)
{
System.Diagnostics.Debug.Assert(
nopponents > 0 && nopponents <= 9);
System.Diagnostics.Debug.Assert(duration > 0.0);
System.Diagnostics.Debug.Assert(Hand.BitCount(pocket) == 2);
System.Diagnostics.Debug.Assert(
Hand.BitCount(board) >= 0 && Hand.BitCount(board) <= 5);
// Keep track of stats
double win = 0.0, count = 0.0;
// Keep track of time
double start = Hand.CurrentTime;
// Loop for specified time duration
while ((Hand.CurrentTime-start) < duration)
{
// Player and board info
ulong boardmask = Hand.RandomHand(board, dead | pocket, 5);
uint playerHandVal = Hand.Evaluate(pocket | boardmask);
// Ensure that dead, board, and pocket cards are not
// available to opponent hands.
ulong deadmask = dead | boardmask | pocket;
// Comparison Results
bool greaterthan = true;
bool greaterthanequal = true;
// Get random opponent hand values
for (int i = 0; i < nopponents; i++)
{
// Get Opponent hand info
ulong oppmask = Hand.RandomHand(deadmask, 2);
uint oppHandVal = Hand.Evaluate(oppmask | boardmask);
// Remove these opponent cards from future opponents
deadmask |= oppmask;
// Determine compare status
if (playerHandVal < oppHandVal)
{
greaterthan = greaterthanequal = false;
break;
}
else if (playerHandVal <= oppHandVal)
{
greaterthan = false;
}
}
// Calculate stats
if (greaterthan)
win += 1.0;
else if (greaterthanequal)
win += 0.5;
count += 1.0;
}
// Return stats
return (count == 0.0 ? 0.0 : win/count);
}
}
}
The resulting output is below. This table gives the approximate odds of winning, given the specified pocket hand against the number of specified opponents. Each cell was given 30 seconds of calculation time, so the results are probably good to several digits. However, I haven't verified that for opponent numbers greater then 1.
Using multiple coresIn the "Calculating win odds" section, we showed that the conventional method for "As Ks" takes about 5 minutes on my laptop computer. However, modifying the code to support multiple cores is straightforward and yields some spectacular results.
The following code was used to benchmark the "Multiple Threads" column in the previous table. This example uses a thread pool to keep all of the cores "active." This technique is fairly straightforward to implement and the result is a roughly linear increase in speed based on the number of cores. I believe that the following example is fairly self-explanatory, but here are some of the highlights.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using HoldemHand;
namespace WinOddsMultipleThreads
{
class Program
{
// Holds Calculated Results
public struct Results
{
public long win, ties, count;
}
// Delegate used in threadpool
delegate Results CalculateOddsDelegate(
ulong pocket, ulong opp, ulong board, ulong dead);
// Method called and inserted in the threadpool
static Results CalculateOdds(
ulong pocket, ulong opp, ulong board, ulong dead)
{
// Initialize local results
Results results = new Results();
results.win = results.ties = results.count = 0;
// Loop through all possible boards and tally the results
foreach (
ulong boardmask in Hand.Hands(board, dead | opp | pocket, 5))
{
uint playerHandval = Hand.Evaluate(pocket | boardmask, 7);
uint oppHandval = Hand.Evaluate(opp | boardmask, 7);
if (playerHandval > oppHandval)
results.win++;
else if (playerHandval == oppHandval)
results.ties++;
results.count++;
}
// Return tally
return results;
}
// Converts a hand iteration specification into an array of hands.
static ulong [] HandList(ulong shared, ulong dead, int ncards)
{
List result = new List();
foreach (ulong mask in Hand.Hands(shared, dead, ncards))
{
result.Add(mask);
}
return result.ToArray();
}
static void Main(string[] args)
{
// This code calculates the probablity of As Ks winning against
// another random hand.
ulong pocketmask = Hand.ParseHand("As Ks"); // Hole hand
ulong board = Hand.ParseHand(""); // No board cards yet
long wins = 0, ties = 0, count = 0;
// Iterate through all possible opponent hole cards
ulong [] opphands = HandList(0UL, board | pocketmask, 2);
// Create Array of Opponent Hands
// Create delegate
CalculateOddsDelegate d =
new CalculateOddsDelegate(CalculateOdds);
// Create results array
IAsyncResult[] results = new IAsyncResult[opphands.Length];
// start time
double start = Hand.CurrentTime;
// Put calculation requests into the threadpool
for (int i = 0; i < opphands.Length; i++)
{
results[i] = d.BeginInvoke(
pocketmask, opphands[i], 0UL, 0UL, null, null);
}
// Collect results once the threads have completed
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||