I have updated my reference application with code that evaluates Texas Hold ‘Em poker hands.
I had written the poker logic a few months ago. Recently, I moved it into a class library and service in order to validate my ASP.NET Core MVC + SOA Architecture. This project is a long way from a functioning poker application that enables users to play Texas Hold ‘Em against artificial intelligence “bots”- my ultimate goal. But it’s a fun start. Evaluating poker hands certainly is more entertaining than confirming a “Hello World” message sent through an SOA stack appears at the other end of the pipe- on a web page in the user’s browser.
Evaluating Poker Hands
The MVC page located at /poker/holdem/cash/dealtoriver/10 (not publicly accessible) displays the results of dealing a full table of 10 players:
|Player 10||T♦8♣||Straight Queen High||Winner|
|Player 9||4♠4♥||Three Fours||QhJd|
|Player 3||J♣T♠||Pair of Jacks||QhTs9s|
|Player 7||6♦A♣||Pair of Sixes||AcQhJd|
|Player 5||K♠6♠||Pair of Sixes||KsQhJd|
|Player 6||7♦6♥||Pair of Sixes||QhJd9s|
|Player 8||5♦5♥||Pair of Fives||QhJd9s|
|Player 4||K♣A♦||Ace High||KcQhJd9s|
|Player 1||7♣A♥||Ace High||QhJd9s7c|
|Player 2||5♣2♥||Queen High||Jd9s6c5c|
The MVC page located at /poker/holdem/cash/dealtowinninghand/10?handname=straightflush (not publicly accessible) displays the results of dealing a full table of 10 players until a specific winning hand appears:
StraightFlush encountered on the 2,972nd deal.
Dealt, scored, and sorted 68,656 hands per second.
|Player 7||5♥Q♦||Straight Flush Queen High||Winner|
|Player 3||Q♥K♦||Flush King High||JdTd9d8d|
|Player 6||7♦T♠||Flush Jack High||Td9d8d7d|
|Player 10||T♥4♦||Flush Jack High||Td9d8d4d|
|Player 2||3♠7♣||Straight Jack High|
|Player 5||A♥6♥||Pair of Aces||JdTd9d|
|Player 1||J♠2♥||Pair of Jacks||AcTd9d|
|Player 8||Q♠T♣||Pair of Tens||AcQsJd|
|Player 9||8♠3♥||Pair of Eights||AcJdTd|
|Player 4||7♥K♠||Ace High||
The card shuffling and poker hand evaluation code is sufficiently fast. I know I can make it faster using bitwise tricks I’ve picked up over the years (there’s 64 bits in a ulong, only 52 cards in a full deck), but I’ll leave that as a future optimization after I write robust unit tests.
The code performs these tasks…
- Website controller calls Web API service method via Refit proxy.
- Refit proxy in website controller serializes entities as JSON via Newtonsoft Json.NET.
- Web API method deserializes JSON to entity classes.
- Web API method instantiates domain class to create table of 10 players.
- Shuffles 52 card deck.
- Deals two private cards to each of 10 players.
- Deals flop (3), turn (1), and river (1) community cards.
- Scores and sorts player hands, finding the best five card hand from the seven cards available.
- Web API calls .ToEntity() extension method to convert domain class to entity class.
- ASP.NET Core runtime serializes entity to JSON.
- Refit proxy in website controller deserializes JSON to entity class.
- Website controller passes entity to Razor view.
- Razor view iterates over messages and cards, writing HTML to response stream sent to user’s web browser.
… in five milliseconds. Not bad for code executing across two processes (website and service).
The code runs in five milliseconds. Not bad for code executing across two processes.
Speaking of unit tests, I have written a few. One of the most important is a test that validates the deck shuffling method produces an even distribution of cards. Meaning, over the course of many deals, each card has an equal chance of appearing in each of the 52 positions in a deck. When writing the Deck class, I referenced Microsoft’s RNGCryptoServiceProvider, then wrote the following code which implements the Fischer-Yates algorithm to randomly shuffle cards. Actually, I wrote the
Shuffle method with generics so I may use it in other projects (not only for the Deck class).
I wrote the following code to validate
Deck.Shuffle() is evenly distributed.
The distribution test passes. I also wrote two performance tests:
Shuffle and deal rate = 153,708 full decks per second. Hand scoring rate = 681,244 per second.
Data Tier Architecture
In addition, I worked on data tier architecture. I wrote a class library with code that…
- Maps domain classes to and from entity DTO classes.
- Implements CRUD operations using a repository pattern.
So far I’m liking how the data tier code is separated from the domain and entity code. This design keeps the domain class library focused on business logic and the Contract library focused on defining service interfaces and entity DTOs. But I have more testing to do. I’ll write about my data tier architecture in a future blog post.
Next steps are to move the web service call from the website controller to AngularJS code running in the user’s web browser. Then perhaps write more poker logic. The code I’m most concerned about (other than artificial intelligence bot code) is how to correctly separate bets into multiple pots when players call all-in bets with short stacks. An entire table of players calling all-in with various stack sizes is a nightmare scenario. What is the correct algorithm and how do I write unit tests that exercise all the edge cases?