When I first attempted to build a poker bot, I tried long and hard to come up with a single algorithm that given your two hole cards and the situation would spit out call, raise, or fold for every situation that the bot could face. Turns out its not a trivial task. There are a lot of factors that should affect your decision: your hole cards, your image, your opponent’s style, your position, the stack sizes relative to the blinds, the recent hand history, the stack to pot ratio once the flop hits and, and the history so far in the hand (in the case of three and four bets). To quantify all of this information – let alone weigh it in an intelligent way – is difficult, to say the least. After several weeks of fruitless attempts, I abandoned it in favor of a simpler, albeit less elegant solution.
At its core, my solution boils down to conditional statements using a simplified version of the current situation. Consider this:
“If your effective stack sizes is 55 big blinds and you’re the small blind and you’re first to act and your opponent is loose aggressive and you have 8h 6h then you should raise three times the big blind.”
It would be impossible to enumerate every possible combination this way, but if you take some shortcuts, you reduce it to a manageable number of factors.
In order to work with stack sizes, I grouped them together based on the effective stack size (effective stack size uses the smaller of your and your opponent’s stack size because the rest is not in play):
0 – 10bb: Shortstacked
10 – 15bb: Danger Zone
15 – 22bb: Red
22 – 35bb: Orange
35 – 50bb: Yellow
50 – 75bb: Green
(The maximum effective stack size you can have on PokerStars’ Heads Up SNGs is 1500/20 = 75 bb.)
These groups were based on my observations watching the bot and on the postflop stack to pot ratios.
Like stack sizes, it would be impossible to list out every possible combination of actions leading up to current situation, so I used a shorthand:
1 = You’re small blind
2 = You’re big blind
All previous actions are reduced to C (call) or R (raise) and the size of the raises are ignored completely.
– You’re small blind and just dealt: 1
– You’re big blind and your opponent raises: 2R
– You’re small blind, you limp and your opponent raises: 1CR
– You’re big blind, your opponent raises, you three bet, and he four bets: 2RRR
Every preflop decision can be represented this way.
It’s generally not a good idea to group holecards beyond the standard 9s 9h = 99, 9s 8h = 98o, etc. There are times when 88, 99, and TT should be treated the same and other times when TT should be treated differently, so it won’t do you much good to create a “Mid Pocket Pair” group and treat everything in it the same way.
That being said, specifying an action for all 169 simplified hole card combinations for every situation would would be a tedious task to do by hand. In an effort to manage it, I created a seperate program specifically for creating ranges and assigning actions to the holecards. I called it Range Maker:
(If you’ve seen ALL IN Expert, the poker calculator that I developed after the poker bot, Range Maker is where the idea came from.)
There are two versions of Range Maker shown in the screenshot above. Below the grid on the left is the older version; on the left is newer version. The grid serves both.
The older version lets me assign a fold, call, raise, or push action to every hole card combination in the selected range.
In the example below, orange represents call, maroon a 3x raise, cyan push, and gray/pink fold:
As time went on I expanded it to include additional options:
The numbers on the right represent the distribution of raise/call/fold for that color. The randomness was necessary to fool observant opponents and PokerStars security, in case that was something they looked at. The decision to push or raise 3x was later delegated to software, which figured out what the stack to pot ratio would be postflop if the opponent called and pushed if it was too high.
Here’s an example of what the range looks like for the small blind opening range vs an aggressive opponent when the stack size was in the red zone (15 – 22 bb):
And here’s the same spot vs a call station:
It took a long time to get these ranges down. I would sit there and watch the bot play for hours, making pages of notes on what I wanted to adjust when the session was over.
For those of you looking for a bit more, here are the range files I used with each version:
These include virtually every range I ever worked with, so if you want to use these you’ll have to spend some time analyzing them to pick out the gems.
Putting it All Together
Here’s roughly how it went in an actual game:
1. Analyze the current situation: hole cards, stack size, previous action, and opponent style and group them.
2. Look up the range based on these factors
Example: PokerStars 25/50 blinds, you are small blind with 2100 chips, call station opponent is big blind with 900 and you’re dealt Ks 5h.
Your effective size is 900/50 = 18 big blinds, putting you in the red zone and since you’re small blind and first to act, the previous action is simply “1”. Now with three layers of conditional statements (stack size, previous action, and opponent style) you can look up the range (see the screenshot above) and determine that K5o is 80% raise, 20% call, and 0% fold. Roll a die (in this case the random number generator) and pick an action based on that distribution.
Here’s the corresponding code for step 2:
'------------------------------------------------------------ ' Name : PreflopActionFromFile ' Purpose : Given a specific situation, this'll look up my range in ' the Hero Ranges.txt file and see what I want to do with this hand '------------------------------------------------------------ ' Public Function PreflopActionFromFile(sHoleCards As String, sRangeName As String) As String Dim sRangeFile_Old As String ' old range file Dim sRangeFile_New As String ' advanced ranging (mixed ranges) Dim sNewINIData As String ' data from the advanced file Dim iDataPos As Integer ' start of the distribution Dim sDistribution As String ' data from that file Dim sDistributionSplit() As String ' split it up Dim sSimpleHand As String ' AA, AQs... Dim dRndNum As Double ' a random # Dim dRaisePerc As Double Dim dCallPerc As Double Dim sINIVal As String sSimpleHand = SimpleHand(sHoleCards) sRangeFile_Old = App.Path & "ResourcesHero Ranges.ini" sRangeFile_New = App.Path & "ResourcesHero Advanced.txt" sNewINIData = GetFromINI("Range Data", sRangeName, sRangeFile_New) If sNewINIData <> vbNullString Then ' There is an entry in the advanced range file iDataPos = InStr(sNewINIData, sSimpleHand) sDistribution = Mid(sNewINIData, iDataPos + Len(sSimpleHand) + Len(" ("), InStr(iDataPos, sNewINIData, ")") - iDataPos - Len(sSimpleHand) - Len(" (")) sDistributionSplit = Split(sDistribution, "/") ' Make an update Update "Mixed Distribution: " & sSimpleHand & " (" & sDistribution & ")", 2, eDecision dRndNum = Rnd dRaisePerc = Val(sDistributionSplit(0)) / 100 dCallPerc = Val(sDistributionSplit(1)) / 100 ' Make a decision... If dRndNum <= dRaisePerc Then PreflopActionFromFile = "R" ElseIf dRndNum <= (dRaisePerc + dCallPerc) Then PreflopActionFromFile = "C" Else PreflopActionFromFile = "F" End If Update "Action Chosen: " & PreflopActionFromFile, 3, eDecision Else Update "Hero Range: " & sRangeName, 1 sINIVal = GetFromINI("Ranges", sRangeName & " (R)", sRangeFile_Old) If InStr(sINIVal, SimpleHand(sHoleCards)) > 0 Then PreflopActionFromFile = "R" Exit Function End If sINIVal = GetFromINI("Ranges", sRangeName & " (C)", sRangeFile_Old) If InStr(sINIVal, SimpleHand(sHoleCards)) > 0 Then PreflopActionFromFile = "C" Exit Function End If sINIVal = GetFromINI("Ranges", sRangeName & " (P)", sRangeFile_Old) If InStr(sINIVal, SimpleHand(sHoleCards)) > 0 Then PreflopActionFromFile = "P" Exit Function End If PreflopActionFromFile = "F" End If End Function
This approach covered about 90% of all the preflop situations the bot faced in the course of a game, but about 10% had to be handled with special cases. Consider, for example, an opponent who pushes every time with 75bb. You can’t just treat this as a 2R situation and call with the standard range. There are also a lot of situations where you should shove simply based on its expected value, which you have to figure out in spots that warrant it (which require you to estimate your opponent’s raising and calling range).
If you’re an aspiring botter and have questions about any of this, feel free to shoot me an email. And good luck.