Building a Poker Engine From Scratch in JavaScript
Introduction
When it comes to card games, nothing is as exciting (or frustrating!) as poker, in particular the popular variation No Limit Hold’em. Poker differentiates itself from many other games in one critical aspect: incomplete information. If you look into the game theory of tic-tac-toe, connect-four, or even chess, all information is available to all players; furthermore, all moves can actually be calculated; this allows for computer bots to compete very aggressively against human players: after all, calculations is what computers do best!
But what about a game where you don’t know what the other players are holding? Can you build a probabilistic model to determine the best winning hands based on bet sizes? Or maybe use a big data model that adaptively learns each player’s ranges based on previous hands?
Herein lies my motivation for this project: I want to build a poker engine that allows me to run simulations and different models. But what does it take to build a poker engine? Let’s find out!
If you need a refresher on the rules of No Limit Hold’Em, click here.
Follow along
If you would like to see the full source code of this project, click here.
Choosing a programming stack
I want to have a visual aspect for this engine; while a simple console would be simple enough to see the information we need, being able to see the cards, money, players, and GUI would make the whole project much more interesting.
For this application, I will be using a JavaScript library known as P5.js, a virtual sketchbook that allows us to make simple shapes, draw to the browser, and use event listeners to interact with the canvas.
Let’s create a simple poker table we can use for the purposes of displaying some functionality of our different components:
It’s not much, but this is the starting point for our simulation.
Building a deck of cards
In order to build a poker engine, a few essential components will need to be created. The first thing we will need is a deck of cards; this also means we need to create an individual card.
A card is a simple concept; it needs a suit and rank, and that rank holds some value. There are 52 cards in the deck: 2-10, Jack, Queen, King, and Ace for each of the four suits (Hearts, Clubs, Diamonds, and Spades).
Let’s implement a Card class that allows us to do this (note: there are other functions in these classes for drawing/interacting with the objects in P5.js, but for the sake of concept the implementation will not be shown):
A look at our card class.
Now that we have cards, it’s time to implement an entire deck that can be used for a game of poker (or any other 52-card game in the future!). Our deck has some additional functionality that will need to be implemented. Before we create some simple functionality, we need to have 52 cards held in an array for our use. We can instantiate all our cards in the constructor.
So we have 52 cards in this Deck object, now what? It would be nice to be able to pick a card from the deck. Ideally, we can pick a specific or random card (pulling a random card from the deck is the same as shuffling the deck and picking the top card, but we don’t need to implement a shuffle algorithm).
That’s it! Our Deck class has enough functionality to be used in our engine now:
Implementation of the Deck class.
Building players
In order to play poker, at least two players are required. But what is a player? In our case, a player will be an object that holds two cards (hole cards) and their stack (the money that belongs to the player). At a very low level, that is all that that a Player class really needs; of course there are many functions that are needed to display the player, give them cards, set the blinds, give/take money, etc. But for the sake of simplicity, here is all the data we need to display the player(s):
A (very) basic look at our Player class.
Implementing stacks
We need a simple way to transfer money between players, and to give/take money from the pot. In order to do that, we will implement a Stack class that allows us to implement this functionality:
Full implementation of the Stack class; not too complicated, but we use Stack objects often.
Determining the winning hand
With the implementation of a deck, players, and stacks, we can create a simulated round that allows a player to play against some bots, or a simulation to run hand probabilities and see the strength of particular hands against others (for example, how does AKs play against 22?). In order to run any kind of simulation, we need a way to determine the winning hand. In order to determine a winner, we need two things: all the players in the hand (so we can access their hole cards) and the community cards. With that information, we can determine the best 5-card hand.
As a review, the rankings of poker hands are shown below:
All 10 No Limit Hold’em Poker hand rankings.
Using this information, we need to determine two things: 1) what is the ranking of a five card hand? and 2) given the community cards and 2 hole cards, what is the best hand that can be created? If we can give a score to each players hand, we can determine the best hand.
Let’s look into this a little deeper, since this is the first algorithm we will need to implement for the engine. How are we going to score a hand? Well, we know that some hands are better than others on ranking alone; for example, a full house beats two pair 100% of the time, but what if we have two players with the same hand ranking? Below is an example we will use to explain the basics of our algorithm:
Three players at showdown. Who has the winning hand?
We see three players and their hole cards, as well as the five community cards (we are simulating a showdown). Player 1 has one pair, while Player 2 and Player 3 have two pair. How are we to determine which player has a better two pair (or, if it applies, did the two players tie the round)?
The following pseudo-rules will be implemented depending on the hand ranking:
Junk: Highest junk card wins, followed by subsequent junk cards. If same five cards, it’s a tie.
One pair: Higher pair wins. If players have same pair, compare first kicker, followed by second kicker, followed by third kicker. If players have same pair and same three kickers, it’s a tie.
Two pair: Highest top pair wins. If same first pair, highest second pair wins. If same second pair, highest kicker wins. If same kicker, it’s a tie.
Three of a kind: Highest “trips” wins. If same trips, highest first kicker wins. If same highest first kicker, highest second kicker wins. If same second kicker, it’s a tie.
Straight: Highest top card wins. If same top card, it’s a tie.
Flush: Highest top card wins. If same top card, use subsequent cards. If same five cards, it’s a tie.
Full house: Highest trips wins. If same trips, highest pair wins. If same pair, it’s a tie.
Four of a kind: Highest quads wins. If same quads, highest kicker wins. If same kicker, it’s a tie.
Straight flush: Highest top card wins. If same top card, it’s a tie.
Royal flush: Always a tie if multiple players have royal flush.
That’s it! Using our pseudo-rules, we know that we should expect ATs to win here because it is the best two pair; let’s implement it and run the previous scenario to see what results our engine gives:
Our log shows that aces, threes, and a ten kicker is the best hand. We can also see the score given to this particular hand (this quantitatively shows how one hand of some rank can beat another with the same rank).
Conclusion
That’s it! We have built a platform that will allow us to simulate poker hands for whatever our needs, whether it be simulating a poker game to test different poker bots, or testing hand strengths and running probabilistic models to learn more about starting hands.
So what’s next? There is much left to do, and I intend to use this poker engine for a multitude of purposes (maybe starting with a poker bot!). For now, we have successfully built a foundation for further use.