The maths of Texas Hold ’em with R
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Please before continue reading, make sure to read the disclaimer at the bottom of this article.
Every time I watch on tv some game of Texas Hold’em I am always curious about the small percentages which appear in the bottom corners of the screen and tell us the chances of win for each player. There must be some kind of algorithm which implements and refreshes those numbers at each draw.
Today I am going to write about one of the first simulations I put down as code and wrapped my head around. Now that I am a little smarter at coding than I was when I coded this, I believe this whole simulation could be done much better and with A LOT less code… Anyway I guess that is a good sign since the fact I found “mistakes” in my code should mean I improved at least on the very things I made mistakes on.
The code I am about to show estimates the probabilities of drawing a pair, a three of a kind (tris) and a poker.
Let’s proceed, first of all: we need to define the deck and the drawing function.
# A deck of 52 cards (Jokers are excluded) | |
deck = c("A","A","A","A","K","K","K","K","Q","Q","Q","Q","J","J","J","J",10,10,10,10,9,9,9,9,8,8,8,8,7,7,7,7,6,6,6,6,5,5,5,5,4,4,4,4,3,3,3,3,2,2,2,2) | |
# This function simulates a hand at Texas Hold'em poker for 2 players and the dealer | |
hand <- function() | |
{ | |
player1Hand <- sample(deck,2,F) | |
for(i in player1Hand) | |
{ | |
# is.element(i,deck) checks if the element i is in the vector deck | |
# match gets the position of the i element in the vector deck and deck[-v] deletes the vth element of the vector deck | |
if(is.element(i,deck) | |
){ | |
deck<-deck[-match(i,deck)] | |
} | |
} | |
player2Hand <- sample(deck,2,F) | |
for(i in player2Hand) | |
{ | |
if(is.element(i,deck)) | |
{ | |
deck<-deck[-match(i,deck)] | |
} | |
} | |
# The flop | |
dealt_cards <- sample(deck,3,F) | |
for(i in dealt_cards) | |
{ | |
if(is.element(i,deck)) | |
{ | |
deck<-deck[-match(i,deck)] | |
} | |
} | |
# The turn | |
second_dealt <- sample(deck,1,F) | |
for(i in second_dealt) | |
{ | |
if(is.element(i,deck)) | |
{ | |
deck<-deck[-match(i,deck)] | |
dealt_cards = append(dealt_cards,second_dealt) | |
} | |
} | |
# The river | |
third_dealt <- sample(deck,1,F) | |
for(i in third_dealt) | |
{ | |
if(is.element(i,deck)) | |
{ | |
deck<-deck[-match(i,deck)] | |
dealt_cards = append(dealt_cards,third_dealt) | |
} | |
} | |
# Eventually, a list of the results is returned by the function | |
results = list(player1Hand,player2Hand,dealt_cards) | |
return(results) | |
} |
Let’s try our drawing function hand() and check the result:
Not that good draw we were looking for huh? Anyway that’s a complete game of Texas Hold’em (provided neither of the two player folded).
Why don’t we create 100 of these games? Here they are:
# This code builds three arrays where it stores the results of 100 poker hands | |
player1Hands <- c(0,0) | |
player1 <- matrix(player1Hands,ncol=2) | |
player2Hands <- c(0,0) | |
player2 <- matrix(player2Hands,ncol=2) | |
dealerst <- c(0,0,0,0,0) | |
dealer <- matrix(dealerst,ncol=5) | |
for(i in 1:100) | |
{ | |
first_round <- hand() | |
p1_h <- c(first_round[[1]][1],first_round[[1]][2]) | |
player1 <- rbind(player1,p1_h) | |
p2_h <- c(first_round[[2]][1],first_round[[2]][2]) | |
player2 <- rbind(player2,p2_h) | |
deal_h <- c(first_round[[3]][1],first_round[[3]][2],first_round[[3]][3],first_round[[3]][4],first_round[[3]][5]) | |
dealer <- rbind(dealer,deal_h) | |
} | |
player1 <- player1[-1,] | |
player2 <- player2[-1,] | |
dealer <- dealer[-1,] | |
rm(p1_h,p2_h,deal_h,first_round,player1Hands,player2Hands,dealerst,i) |
At this stage, if we run the code, R will generate three tables (or matrix) with the results of each one of the 100 simulate games. Something like this:
Now we need only to look for pairs, tris and pokers. We need to define 3 functions as follows:
# This function runs through all the 100 hands and searches for pairs | |
find_pairs <- function() | |
{ | |
pair = 0 | |
for(i in 1:100) | |
{ | |
if((player1[i,1] == player1[i,2]) | (is.element(player1[i,1],dealer[i,])) | (is.element(player1[i,2],dealer[i,]))) | |
{ | |
pair = pair + 1 | |
} | |
} | |
return(pair) | |
} | |
# This function runs through all the 100 hands and searches for tris | |
find_tris <- function() | |
{ | |
tris = 0 | |
for(i in 1:100) | |
{ | |
test = 0 | |
if((is.element(player1[i,1],dealer[i,]) | is.element(player1[i,2],dealer[i,])) && (player1[i,1] == player1[i,2])) | |
{ | |
tris = tris + 1 | |
} | |
for(b in 2 : 5) | |
{ | |
if(dealer[i,b] == dealer[i,b-1]) | |
{ | |
dealer_pair = T | |
test = dealer[i,b] | |
if(is.element(test,player1[i,]) && dealer_pair) | |
{ | |
tris = tris + 1 | |
} | |
} | |
} | |
} | |
return(tris) | |
} | |
# This function runs through all the 100 hands and searches for pokers | |
find_poker <- function() | |
poker = 0 | |
for(i in 1:100) | |
{ | |
test = 0 | |
dealer_pair = 0 | |
test2 = 0 | |
dealer_tris = 0 | |
for(b in 2 : 5) | |
{ | |
test = 0 | |
if(dealer[i,b] == dealer[i,b-1]) | |
{ | |
dealer_pair = T | |
test = dealer[i,b] | |
}else | |
{ | |
dealer_pair = F | |
} | |
if((player1[i,1] == player1[i,2]) && (dealer_pair) && (player1[i,1] == test)) | |
{ | |
poker = poker + 1 | |
#------------------- | |
print(player1[i,]) | |
print(dealer[i,]) | |
} | |
} | |
for(b in 3 : 5) | |
{ | |
test2 = 0 | |
dealer_tris = 0 | |
if((dealer[i,b] == dealer[i,b-1]) && (dealer[i,b-1] == dealer[i,b-2])) | |
{ | |
dealer_tris = T | |
test2 = dealer[i,b] | |
}else | |
{ | |
dealer_tris = F | |
} | |
if(dealer_tris && ((player1[i,1] == test2) | (player1[i,2] == test2))) | |
{ | |
poker = poker + 1 | |
#------------------- | |
print(player1[i,]) | |
print(dealer[i,]) | |
} | |
} | |
} | |
return(poker) | |
} | |
find_pairs() | |
find_tris() | |
find_poker() |
The result should look like this:
In 100 games, we have got 47 pairs, 2 three of a kind and no pokers! Interesting data! However, this might be a mere case! We need to run this a LOT of times to be sure the odds we obtained are at least near the real ones. Let’s build a function that can do this. For instance, the function below runs n times 100 games and then collect the results. Note that It outputs the probabilities as the mean of the probabilities occurred. Much of the code here is replicated from the functions above. I guess this could have been done a lot better! If you have any idea please let me know or leave a comment!
# The following function calculates the probabilities of drawing a pair or a tris in a Texas Hold'em game with 2 players and the dealer | |
pairs_and_tris <- function(n) | |
{ | |
# This function searches for all the pairs in 100 hands and returns the number of pairs found | |
find_pairs <- function() | |
{ | |
pair = 0 | |
for(i in 1:100){ | |
if((player1[i,1] == player1[i,2]) | (is.element(player1[i,1],dealer[i,])) | (is.element(player1[i,2],dealer[i,]))) | |
{ | |
pair = pair + 1 | |
} | |
} | |
return(pair) | |
} | |
# This function searches for all the tris in 100 hands and returns the number of tris found | |
find_tris <- function() | |
{ | |
tris = 0 | |
for(i in 1:100) | |
{ | |
test = 0 | |
if((is.element(player1[i,1],dealer[i,]) | is.element(player1[i,2],dealer[i,])) && (player1[i,1] == player1[i,2])) | |
{ | |
tris = tris + 1 | |
} | |
for(b in 2 : 5){ | |
if(dealer[i,b] == dealer[i,b-1]) | |
{ | |
dealer_pair = T | |
test = dealer[i,b] | |
if(is.element(test,player1[i,]) && dealer_pair) | |
{ | |
tris = tris + 1 | |
} | |
} | |
} | |
} | |
return(tris) | |
} | |
# This function searches for all the pokers in 100 hands and returns the number of pokers found | |
find_poker <- function() | |
{ | |
poker = 0 | |
for(i in 1:100) | |
{ | |
test = 0 | |
dealer_pair = 0 | |
test2 = 0 | |
dealer_tris = 0 | |
for(b in 2 : 5) | |
{ | |
test = 0 | |
if(dealer[i,b] == dealer[i,b-1]) | |
{ | |
dealer_pair = T | |
test = dealer[i,b] | |
}else | |
{ | |
dealer_pair = F | |
} | |
if((player1[i,1] == player1[i,2]) && (dealer_pair) && (player1[i,1] == test)) | |
{ | |
poker = poker + 1 | |
#------------------- | |
print(player1[i,]) | |
print(dealer[i,]) | |
} | |
} | |
for(b in 3 : 5) | |
{ | |
test2 = 0 | |
dealer_tris = 0 | |
if((dealer[i,b] == dealer[i,b-1]) && (dealer[i,b-1] == dealer[i,b-2])) | |
{ | |
dealer_tris = T | |
test2 = dealer[i,b] | |
}else | |
{ | |
dealer_tris = F | |
} | |
if(dealer_tris && ((player1[i,1] == test2) | (player1[i,2] == test2))) | |
{ | |
poker = poker + 1 | |
#------------------- | |
print(player1[i,]) | |
print(dealer[i,]) | |
} | |
} | |
} | |
return(poker) | |
} | |
# The code below calculates probabilties of pairs and tris for each of the n 100 texas holdem hands simulation | |
# The vector help_vector is used to build the matrix risultati_p1 where all the pairs and tris will be stored | |
help_vector <- c(0,0,0) | |
results_p1 <- matrix(help_vector,ncol=3) | |
colnames(results_p1) = c("Pairs per 100 hands","Tris per 100 hands","Poker per 100 hands") | |
for(i in 1:n) | |
{ | |
player1Hands <- c(0,0) | |
player1 <- matrix(player1Hands,ncol=2) | |
player2Hands <- c(0,0) | |
player2 <- matrix(player2Hands,ncol=2) | |
dealerst <- c(0,0,0,0,0) | |
dealer <- matrix(dealerst,ncol=5) | |
# The code below simulates 100 texas hold'em hands | |
for(i in 1:100) | |
{ | |
first_round <- hand() | |
p1_h <- c(first_round[[1]][1],first_round[[1]][2]) | |
player1 <- rbind(player1,p1_h) | |
p2_h <- c(first_round[[2]][1],first_round[[2]][2]) | |
player2 <- rbind(player2,p2_h) | |
deal_h <- c(first_round[[3]][1],first_round[[3]][2],first_round[[3]][3],first_round[[3]][4],first_round[[3]][5]) | |
dealer <- rbind(dealer,deal_h) | |
} | |
player1 <- player1[-1,] | |
player2 <- player2[-1,] | |
dealer <- dealer[-1,] | |
pairs <- find_pairs() | |
tris <- find_tris() | |
#---------------- | |
poker <- find_poker() | |
#---------------- | |
results__p1 <- c(pairs,tris,poker) | |
results_p1 <- rbind(results_p1,results__p1) | |
} | |
results_p1 <- results_p1[-1,] | |
# Results for each 100 hand are shown | |
View(results_p1) | |
# Final result is printed | |
string_to_print <- paste("Given a Texas Hold'em game and two players, here are probabilities for p1 in",n*100,"hands:",sep=" ") | |
print(string_to_print) | |
# Please note that probabilites are calculated as average of pairs and tris found | |
mean_scores = c(mean(results_p1[,1]),mean(results_p1[,2]),mean(results_p1[,3])) | |
names(mean_scores) = c("Probability of a pair,","Probability of a tris","Probabilty of a poker") | |
return(mean_scores) | |
} |
Let’s try for instance to run this 10.000 times, with n = 100. Here are the results:
For debugging purposes our function outputs each poker it finds. Since usually pokers are not that frequent it should be fine. 10.000 times seems not to be enough…
Let’s do 100.000 times!!!
This looks better! By simulating in less than 2 minutes 100.000 games of Texas Hold’em with 2 players we concluded that drawing a poker two times in a row is a very unlikely event, drawing a pair is a relative common event while three of a kind is rare, but not as much as poker!
I should mention that, it looks like the probability of a poker is overestimated according to the formal calculation, I believe that is either because it is an “outlier” or because I did not run the simulation a big enough number of times. Anyway the other two probabilities seem fine (you can check for more information on http://en.wikipedia.org/wiki/Poker_probability)
Hope you enjoyed!
Disclaimer: This article is for educational purpose ONLY. Odds generated by this code are calculated by a random simulation. As such the odds will represent an approximation of the true odds. They might even be completely wrong or misleading. This code must NOT be used for anything other than educational purpose. The provider of this code does not guarantee the accuracy of the results and accepts no liability for any loss or damage that may occur as a result of the use of this code. Understanding and agreeing to the terms of this disclaimer is a condition of use of this code. By reading the article you confirm you have understood and will comply with this disclaimer.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.