top of page
Search

Chromatic Polynomials 16 – Video Poker

Sep 7
10 min read

In a video poker game, once the cards have been dealt, the autohold feature determines what cards should be held or discarded to yield the best average payback. Some jurisdictions require this feature to be enabled for the player’s use and others let casinos decide whether to enable it (spoiler: they never do because they make more money when players make mistakes).


Per gaming regulations, the payback percentage for a game must be calculated based on optimal play by the player. So even if the player doesn’t have access to the autohold feature, poker game developers must implement the autohold feature to determine what “optimal play” is for that game.


During my time working for a gaming company, I worked on a few different implementations of video poker logic, mainly on the “autohold” feature.  I supported the autohold feature for almost 20 years, adapting and updating it as new types of poker games were devised.


When I learned about CPs, one of my first interests was in applying CPs to video poker. Just like the slot game exercise in the previous post, I found that I could use CPs in every stage of video poker. I want to be very clear – this is not how video poker is implemented in the gaming platforms I worked on. This is, as is almost everything on my site, a Solution Looking for a Problem.


Definitions

First, I have to define a deck of cards. I found it simplest to represent a single card with two vertices, one for the suit and one for the face. As usual, I prefer say-what-you-mean names, so my poker definitions file starts with this:


Two = 0

Three = 1

Four = 2

Five = 3

Six = 4

Seven = 5

Eight = 6

Nine = 7

Ten = 8

Jack = 9

Queen = 10

King = 11

Ace = 12

 

Faces = 13

Heart = 0

Diamond = 1

Club = 2

Spade = 3

 

Suits = 4

The vertices for the cards are defined as:

// Face values for Cards 1-5

C1F = σ1

C2F = σ2

C3F = σ3

C4F = σ4

C5F = σ5

FaceVertices = σ1-σ5

// Suit values for Cards 1-5

C1S = σ6

C2S = σ7

C3S = σ8

C4S = σ9

C5S = σ10

SuitVertices = σ6-σ10

 

When evaluating poker CPs, I will sum over vertices representing suits from 1 to Suits and vertices representing faces from 1 to Faces:



A traditional video poker hand consists of 5 unique cards, drawn without replacement. In CPs “without replacement” must be encoded as expressions that prevent any two cards from matching. In this case, I need to specify that for each pair of cards, the face and suit cannot both match.


To specify that card 1 cannot be the same as card 2, I specify that both the suits and the faces of cards 1 and 2 cannot match:


(1 - δC1F,C2F δC1S,C2S)

The full expression for drawing a poker hand must include 10 such terms, to compare every selection of two cards in the hand:


Hand = (1 - δC1F,C2F δC1S,C2S) * (1 - δC1F,C3F δC1S,C3S)
       * (1 - δC1F,C4F δC1S,C4S) * (1 - δC1F,C5F δC1S,C5S)
       * (1 – δC2F,C3F δC2S,C3S) * (1 – δC2F,C4F δC2S,C4S)
       * (1 – δC2F,C5F δC2S,C5S) * (1 – δC3F,C4F δC3S,C4S)
       * (1 – δC3F,C5F δC3S,C5S) * (1 – δC4F,C5F δC4S,C5S)

I am now ready to define some winning hands. I found it easier to break out the straight and flush definitions first:

// 10 possible straights

OnlyAto5 = (δC1F{Ace,Two-Five} * δC2F{Ace,Two-Five} * δC3F{Ace,Two-Five}
            * δC4F{Ace,Two-Five} * δ C5F{Ace,Two-Five})

Only2to6  = (δC1F{Two-Six} * δC2F{Two-Six} * δC3F{Two-Six} * δC4F{Two-Six}
             * δC5F{Two-Six})

...

OnlyTtoA = (δC1F{Ten-Ace} * δC2F{Ten-Ace} * δC3F{Ten-Ace} * δC4F{Ten-Ace
            * δC5F{Ten-Ace})

I hope that by this point it is clear that:


δC1F{Nine-King} δC2F{Nine-King}

is not the same as:


δC1F,C2F{Nine-King}

While both require that the two face values fall within the same range, the first expression does not require those two face values to match and the second expression does.


I then used these to define a straight:


UniqueFaces = ((1 - δC1F,C2F) * (1 - δC1F,C3F) * (1 - δC1F,C4F)
               * (1 - δC1F,C5F) * (1 - δC2F,C3F) * (1 - δC2F,C4F)
               * (1 - δC2F,C5F) * (1 - δ C3F,C4F) * (1 - δC3F,C5F)
               * (1 - δC4F,C5F))

// An actual straight meets one of the conditions above, and has 5 unique faces.

Straight = ((OnlyAto5 + Only2to6 + Only3to7 + Only4to8 + Only5to9
             + Only6toT + Only7toJ + Only8toQ + Only9toK + OnlyTtoA)
            * UniqueFaces)

Note that, while I try to avoid using the addition as an OR operation in CPs, it is safe in this expression because requiring unique faces makes the different straight terms mutually exclusive.


Here are the rest of the win categories for a basic Jacks-or-Better video poker game:


// A flush has all cards of the same suit:
Flush = (δ C1S C2S C3S C4S C5S)
RoyalFlush = (Only9to13 * Flush)
StraightFlush = (Straight * Flush)

These hands expose a weakness of CPs: I have to explicitly specify every possible order. Fortunately, I only have to write these definitions once.


FourOfAKind = (δC1F,C2F,C3F,C4F + δC1F,C2F,C3F,C5F + δC1F,C2F,C4F,C5F
               + δC1F,C3F,C4F,C5F + δC2F,C3F,C4F,C5F)

For four-of-a-kind, I can get away with using + to add terms because matching two different four-of-a-kind terms implies a five-of-a-kind, which will be impossible when the entire expression is multiplied by the HAND expression. The same is true for defining a full house:


FullHouse = (δC1F,C2F,C3F δC4F,C5F + δC1F,C2F,C4F δC3F,C5F
             + δC1F,C2F,C5F δC3F,C4F + δC1F,C3F,C4F δC2F,C5F
             + δC1F,C3F,C5F δC2F,C4F + δC1F,C4F,C5F δC2F,C3F
             + δC2F,C3F,C4F δC1F,C5F + δC2F,C3F,C5F δC1F,C4F
             + δC2F,C4F,C5F δC1F,C3F + δC3F,C4F,C5F δC1F,C2F)

For the rest of the win categories, I have to ensure that overlapping matches are not counted twice. If I were to add two different ways to match a three-of-a-kind…


δC1F,C2F,C3F + δC1F,C2F,C4F

…This would double-count the ways to get four-of-a-kind:


δC1F,C2F,C3F,C3F

I can manually subtract the ways to get more than three of a kind:


ThreeOfAKind = (δC1F,C2F,C3F + δC1F,C2F,C4F + δC1F,C2F,C5F
                + δC1F,C3F,C4F + δC1F,C3F,C5F + δC1F,C4F,C5F
                + δC2F,C3F,C4F + δC2F,C3F,C5F + δC2F,C4F,C5F
                + δC3F,C4F,C5F - 3δC1F,C2F,C3F,C4F
                - 3δC1F,C2F,C3F,C5F - 3δC1F,C2F,C4F,C5F
                - 3δC1F,C3F,C4F,C5F - 3δC2F,C3F,C4F,C5F)

That approach is, in my opinion, a little error-prone since I have to consciously work through every possibility. I prefer to use De Morgan’s laws to ensure that my definitions are both robust and extensible:


ThreeOfAKind = (1 - (1 - δC1F,C2F,C3F) * (1 - δC1F,C2F,C4F)
                  * (1 - δC1F,C2F,C5F) * (1 - δC1F,C3F,C4F)
                  * (1 - δC1F,C3F,C5F) * (1 - δC1F,C4F,C5F)
                  * (1 – δC2F,C3F,C4F) * (1 - δC2F,C3F,C5F)
                  * (1 - δC2F,C4F,C5F) * (1 - δC3F,C4F,C5F))
 
TwoPair = (1 - (1 - δC1F,C2F δC3F,C4F) * (1 - δC1F,C3F δC2F,C4F)
             * (1 - δC1F,C4F δC2F,C3F) * (1 - δC1F,C2F δC3F,C5F)
             * (1 - δC1F,C3F δC2F,C5F) * (1 - δC1F,C5F δC2F,C3F)
             * (1 - δC1F,C2F δC4F,C5F) * (1 - δC1F,C4F δC2F,C5F)
             * (1 - δC1F,C5F δC2F,C4F) * (1 - δC1F,C3F δC4F,C5F)
             * (1 - δC1F,C4F δC3F,C5F) * (1 - δC1F,C5F δC3F,C4F)
             * (1 - δC2F,C3F δC4F,C5F) * (1 - δC2F,C4F δC3F,C5F)
             * (1 - δC2F,C5F δC3F,C4F))       

JacksOrBetter = (1 - (1 - δC1F,C2F {Jack-King,Ace})
                   * (1 - δC1F,C3F {Jack-King,Ace})
                   * (1 - δC1F,C4F {Jack-King,Ace})
                   * (1 - δC1F,C5F {Jack-King,Ace})
                   * (1 - δC2F,C3F {Jack-King,Ace})
                   * (1 - δC2F,C4F {Jack-King,Ace})
                   * (1 - δC2F,C5F {Jack-King,Ace})
                   * (1 - δC3F,C4F {Jack-King,Ace})
                   * (1 - δC3F,C5F {Jack-King,Ace})
                   * (1 - δC4F,C5F {Jack-King,Ace}))

 

Paying the Highest Win Only


Video poker games typically only pay for the highest win. If you hit a Royal Flush, you get paid for the Royal Flush only – you don’t get paid separately for also hitting a straight, a flush, and a straight flush. To capture this logic, I use the same approach as the slot paytable exercise:


If excluding a higher win results in fewer hits for the current win, then the higher win must be excluded:


This is something that can easily be automated. For a Jacks-or-Better game, this results in the following exclusions:

PayRoyalFlush = RoyalFlush
PayStraightFlush = StraightFlush * (1 – RoyalFlush)
PayFourOfAKind = FourOfAKind
PayFullHouse = FullHouse
PayFlush = Flush * (1 – StraightFlush)
PayStraight = Straight * (1 – Flush)
PayThreeOfAKind = ThreeOfAKind * (1 – FourOfAKind) * (1 – FullHouse)
PayTwoPair = TwoPair * (1 – FullHouse)
PayJacksOrBetter = JacksOrBetter * (1 – TwoPair) * (1 - ThreeOfAKind)

Wins On the Deal


The CP Counting function gives us the number of ways to hit each win on the deal (when five cards are initially dealt):

Win Category

Ways to Hit, including higher wins

Ways to Hit, excluding higher wins

Royal Flushes

4

4

Straight Flushes

40

36

Four of a Kinds

624

624

Full Houses

3,744

3,744

Flushes

5,148

5,108

Straights

10,240

10,200

Three of a Kinds

59,280

54,912

Two Pairs

127,920

123,552

Jacks Or Betters

423,552

337,920

Total Hands

2,598,960

2,598,960

 

The Checking function of CPs tells if the player has won anything on the deal. This is useful because there are some video poker games that pay more for hitting a Royal Flush (or other high win) on the deal than if the player drew into it.


Autohold


The next stage of a video poker game is to allow the player to select which cards to hold and discard. The CP Completion function is ideal for calculating the best choice. I need two additional steps from previous completion functions:


First, I need to exclude the possibility of redrawing a card that has already been dealt. For this, I define an expression for each set of cards discarded. For example, to discard cards 1 and 4, I define the following:


// Let F1 – F5 = the actual face values dealt 
// Let S1 – S5 = the actual suit values dealt
RedrawC1 = (1 – δC1F{F1}δC1S{S1}) * (1 – δC1F{F2}δC1S{S2})
         * (1 – δC1F{F3}δC1S{S2}) * (1 – δC1F{F4}δC1S{S4})
         * (1 – δC1F{F5}δC1S{S5})
RedrawC2 = (1 – δC2F{F1}δC2S{S1}) * (1 – δC2F{F2}δC2S{S2})
         * (1 – δC2F{F3}δC2S{S2}) * (1 – δC2F{F4}δC2S{S4})
         * (1 – δC2F{F5}δC2S{S5})
RedrawC3 = (1 – δC3F{F1}δC3S{S1}) * (1 – δC3F{F2}δC3S{S2})
         * (1 – δC3F{F3}δC3S{S2}) * (1 – δC3F{F4}δC3S{S4})
         * (1 – δC3F{F5}δC3S{S5})
RedrawC4 = (1 – δC4F{F1}δC4S{S1}) * (1 – δC4F{F2}δC4S{S2})
         * (1 – δC4F{F3}δC4S{S2}) * (1 – δC4F{F4}δC4S{S4})
         * (1 – δC4F{F5}δC4S{S5})
RedrawC5 = (1 – δC5F{F1}δC5S{S1}) * (1 – δC5F{F2}δC5S{S2})
         * (1 – δC5F{F3}δC5S{S2}) * (1 – δC5F{F4}δC5S{S4})
         * (1 – δC5F{F5}δC5S{S5})

Next, because CPs are so position dependent, I need a separate expression for each way to discard 1 or more cards. For example, to discard cards 1 and 4, and hold the rest, I define this expression:

DiscardC1C4 = (RedrawC1 * RedrawC4 * δC2F{F2}δC2S{S2} * δC3F{F3}δC3S{S3}
               * δC5F{F5}δC5S{S5})

This affixes the values for cards 2, 3, and 5 to the values already drawn. It prevents cards 1 and 4 from being assigned a card already drawn. There are 30 other such definitions to cover all the ways to discard cards from a five=card hand. I’m not writing them all here because if I had to do this for an actual game, I would automate it (and also because I don’t think anyone would read it if I did include them).


All of the win category definitions above can be multiplied by these additional conditions to represent the draw:


DrawRoyalFlush = PayRoyalFlush * DiscardC1C4
DrawStraightFlush = PayStraightFlush * DiscardC1C
...

Summing these will tell us the number of ways to hit each win category, given the specific cards held and discarded. I can multiply each count by the amount paid and divide by the total ways to redraw the discarded cards to get the Expected Value (EV) for that specific hold. After running the same process for each of the 32 ways to hold and discard cards in a 5-card hand, the autohold feature recommends the hold selection that produces the highest EV.


Win Evaluation


Win evaluation is as simple as applying the Checking Function of CPs to each of the win category CPs in order of greatest payout to least. The first CP to return 1 is the win the player has hit.


Paytable Development


We can compute the payback percentage of the entire paytable by running every possible dealt hand through the autohold step to calculate the expected value for that hand.

This means that we can define one set of CPs for a particular video poker game and use them for every stage of the game: paytable development, evaluating wins on the deal and on the draw, and determining the best cards to hold.


Variations


More suits, and faces, or decks

With the win categories build on definitions of a suit, face, and unique card, it’s easy to change the definitions to model a deck with a different number of suits or faces simply by changing a few definitions.


We could change a poker game to use two decks of cards by changing the hand definition from excluding any two identical cards to excluding any three identical cards:


Hand = (1 - δC1F,C2F,C3F δC1S,C2S,C3S) * (1 - δC1F,C2F,C4F δC1S,C2S,C4S)
     * (1 - δC1F,C2F,C5F δC1S,C2S,C5S) * (1 - δC1F,C3F,C4S δC1S,C3S,C4S)
     * (1 – δC1F,C3F,C5F δC1S,C3S,C5S) * (1 – δC1F,C4F,C5F δC1S,C4S,c5S)
     * (1 – δC2F,C3F,C4F δC2S,C3S,C4S) * (1 – δC2F,C3F,C5F δC2S,C3S,C5S)
     * (1 – δC2F,C4F,C5F δC2S,C4S,C5S) * (1 – δC3F,C4F,C5F δC3S,C4S,C5S)

Jokers

To add a joker to the deck, I added it as an extra face value:

Two = 0
Three = 1
…
Ace = 12
Joker = 13
Faces = 14

This actually adds four jokers, one for each suit. Inside the HAND definition, I add the constraint that only a Joker of one specific suit is allowed:


JHandwithJoker = Hand * (1 - δC1F,Joker δC1S{Heart,Diamond,Club})
               * (1 - δC2F,Joker δC2S{Heart,Diamond,Club})
               * (1 - δC3F,Joker δC3S{Heart,Diamond,Club})
               * (1 - δC4F,Joker δC4S{Heart,Diamond,Club})
               * (1 - δC5F,Joker δC5S{Heart,Diamond,Club})

Lastly, win categories need to allow for jokers:

// Straights
OnlyAto5withJoker = (δC1F{Ace,Two-Five,Joker} * δC2F{Ace,Two-Five,Joker
                   * δC3F{Ace,Two-Five,Joker} * δC4F{Ace,Two-Five,Joker}
                   * δ C5F{Ace,Two-Five,Joker})
…
FourOfAKindwithJoker = (1 - (1 - FourOfAKind)
                          * (1 - (δC1F,Joker + δC2F,Joker + δC3F,Joker
                                + δC4F,Joker + δC1F,Joker)
                          * ThreeOfAKind))

The definitions get more complex as the hands go on, but this should suffice to illustrate the concept.


Ordered hands


A few poker games pay more for ordered hands. For example, a royal flush in order from lowest face value to highest pays more than a royal flush. As shown above, I had to do a little extra work to handle unordered hands. Ordered hands are trivial. Here are a few definitions to illustrate that:


OrderedStraightTtoA = δC1F,Ten * δC2F,Jack * δC3F,Queen
                    * δC4F,King * δC5F,Ace
OrderedStraight9toK = δC9F,Nine * δC2F,Ten * δC3F,Jack
                    * δC4F,Queen * δC5F,King

There are more versions and variations of poker than I think any one person can enumerate. But anything that can be reduced to assigning a set of variables to a set of finite values can be expressed using Chromatic Polynomials.

 

This should suffice to show that, just like the slot exercise, we can use CPs at every stage of both developing and playing of a video poker game

 

Previous Post:



 
 
 

Comments


Drop Me a Line, Let Me Know What You Think

Thanks for submitting!

© 2024 by Bryan Wolf. Powered and secured by Wix

bottom of page