The Data Science Lab

Just for Fun: A Five-Card Poker Library Using C#

The second Hand constructor accepts five individual Card objects. The third Hand constructor accepts a string argument like "Js3h7d7cAd":

public Hand(string s) // s like "Js3h7d7cAd"
{
  this.cards = new List<Card>();
  this.cards.Add(new Card(s.Substring(0, 2)));
  this.cards.Add(new Card(s.Substring(2, 2)));
  this.cards.Add(new Card(s.Substring(4, 2)));
  this.cards.Add(new Card(s.Substring(6, 2)));
  this.cards.Add(new Card(s.Substring(8, 2)));
  this.cards.Sort();
}

Comparing Two Poker Hands
At first thought, comparing two poker hands seems like a relatively easy problem. You could just compute the integer hand types for the two hands and then compare those values. For example, a hand that has type FullHouse (6) is greater than a hand that has type ThreeKind (3). This works for most pairs of random hands. But when the two hand types are equal, such as TwoPair (integer type = 2), then the tie must be broken. This is surprisingly difficult.

The demo code has nine helper functions with names like BreakTieFlush() and BreakTieTwoPair(). There is no need for a BreakTieRoyalFlush() because all royal flush hands are equivalent in ranking. To break a tie between two hands that are both TwoPair, you must determine what the two pairs are in each hand, and also the kicker card in case both hands have the same two pairs. The logic of the various break-tie functions is very tricky.

A Hand object that is sorted and is type TwoPair can have one of three patterns: LLHHK, LLKHH or KLLHH where L is the lower-rank pair, H is the higher rank pair, and K is the kicker. The first pattern can be identified in several ways but the clearest approach looks like cards[0].rank == cards[1].rank && cards[1].rank != cards[2].rank && cards[2].rank == cards[3].rank && cards[3].rank != cards[4].rank. But you can get cute and observe that the first pattern can be uniquely identified by just the last condition, cards[3].rank != cards[4].rank.

An inexperienced programmer will lean toward the cute approach, thinking it's more efficient. An experienced programmer will lean toward the brute force approach, thinking it's safer. A very experienced programmer will ask if the code will be called only a few times or inside a loop and called millions of times.


After the TwoPair pattern has been determined, you can compute the ranks of the low-rank pair, the high-rank pair, and the kicker. If you have this information for both Hand objects, the comparison takes the form of if h1HighPairRank < h2HighPairRank return -1 else if h1HighPairRank > h2highPairRank return +1 else if . . . And you have to remember that a return value of 0 is possible.

Using the Poker Library
The poker library can be used in many ways. One example is computing probabilities using a simulation. Mechanical and electromechanical poker games have been popular for well over 100 years. The image in Figure 2 shows a fascinating machine manufactured by the Charles Fey Company at the turn of the last century.

Figure 2: An Antique Mechanical Five-Card Poker Machine (image courtesy of Bill Petrochuk, from the Coin Operated Collectors Association)
[Click on image for larger view.] Figure 2: An Antique Mechanical Five-Card Poker Machine (image courtesy of Bill Petrochuk, from the Coin Operated Collectors Association)

The machine has five reels:

reels[0] = "2d 3c 4s 6h 7d 8c 9s Qd Kc As"
reels[1] = "2h 3d 4c 5h 7h 8d 9c Qh Kd Ac"
reels[2] = "3h 4d 5d 6s 8h 9d Tc Js Kh Ad"
reels[3] = "2s 4h 5c 6c 7s 9h Td Jc Qs Ah"
reels[4] = "2c 3s 5s 6d 7c 8s Th Jd Qc Ks"

Although it's possible to compute the probability of each of the 10 possible hand types using standard mathematical probability, using that approach would be extremely difficult. Instead, writing a simple simulation is quite easy. In pseudo-code:

loop n trials times
  randomly select one card from each reel
  construct hand from the five cards
  determine the hand type
  increment counter for the hand type
end-loop
divide each counter by n

I ran such a simulation with 10,000,000 trials and got these results:

                machine     standard deck
HighCard        0.43135600  0.50117700
OnePair         0.45633230  0.42256900
TwoPair         0.06447860  0.04753900
ThreeKind       0.03819820  0.02112800
Straight        0.00337820  0.00392500
Flush           0.00177490  0.00196500
FullHouse       0.00328960  0.00144100
FourKind        0.00108450  0.00024010
StraightFlush   0.00008790  0.00001390
RoyalFlush      0.00001980  0.00000154

Interestingly, the antique machine has a better chance of giving OnePair, TwoPair, and ThreeKind (better than the worst HighCard hand type but hands with low payouts) than random hands from a standard deck of 52 cards, but a lower chance of giving hands that deliver a significant payout. This configuration provides users with positive feedback with small payouts to encourage them to keep playing, but avoids large payouts.

Wrapping Up
Several of the teams I have worked with over the years used a hypothetical poker library as the basis for job interview questions. There are dozens of design alternatives that trade off performance, maintainability, efficiency, and other factors. This characteristic allows the interviewers to evaluate the job candidate's software design ability, and communication skills. Interestingly, in all the years I saw poker hands used as the basis of job interview questions, I don't recall any job candidate not being familiar with poker. I suspect there's a connection between people who like mathematical-based games and people who like to write computer programs.


About the Author

Dr. James McCaffrey works for Microsoft Research in Redmond, Wash. He has worked on several Microsoft products including Azure and Bing. James can be reached at [email protected].

comments powered by Disqus

Featured

Subscribe on YouTube