The building blocks of a poker application
Last week Thursday, carwow hosted a dojo for the Elixir community to eat, drink and get down and dirty with the language. The challenge for this first dojo was to build a parser for poker hands (of the Texas Hold’em variety). The parser would be able to tell what kind of hand you have and what they’re worth.
Future dojos will build on this by comparing different hands, implementing rounds, all with the aim to build a fully working poker application in our favourite functional language.
I would like to take the opportunity to thank all the lovely members of the Elixir community who showed up and made the dojo a lot of fun. It was great meeting you all, I really enjoyed collaborating, chatting and getting to know you all. I hope to see you soon in future dojos and other Elixir events.
This post is primarily about my approach to getting the building blocks built, and my thought process throughout building the application. I primarily try to outline the architecture of my tests and modules, as well as the thought process behind them, so those who may struggle with how to approach building applications using a functional paradigm may gain one or more insights.
In the same vain, Elixir veterans reading this: if you spot something that I’m doing that you regard as bad practice, I would love your constructive critisicm. Leave a comment below, and I shall amend this so I don’t give those with less experience bad advice.
For those like me who vaguely remember playing one form of Texas Hold’em poker on family vacations, and can’t call on the rules regularly. The first part of the application require an understanding that during a game of Texas Hold’em, the players each have a hand (5 cards dealt from a deck), and each hand has a score associated. A short summary of scores associated with each hand can be found here, with more information available on Wikipedia if you would like to read more.
In the coming sections, I shall guide you on my strategy to parse the straight flush hand, and hopefully set you up with a starting point to approach building the full parser for all hand types.
Setting up the application
You will need the following dependencies on your development computer:
- Elixir ≥ 1.4.0
- A text editor of your choice
- Git
- A Github account
Once all of these are installed on your machine, fork the Pokerwars repository and clone the forked repository onto your machine.
Add the original repository as an upstream remote:
git remote add upstream git@github.com:carwow/pokerwars.git
Note: I use ssh for my git remote activities. If you use https, make sure to substitute the urls accordingly.
The reason I advice forking the repository instead of cloning it is so that you can rebase any future upstream changes back into your fork and keep things in sync.
Keeping with good software development practices, we’re going to be writing and running tests first and, using the feedback from tests, creating our implementations.
Let’s run the tests that come with the skeleton repo. The command to run tests using Elixir’s built in ExUnit
module is mix test
. Your output should look something like:
Compiling 3 files (.ex)
warning: unused alias Card
lib/pokerwars/hand.ex:2Generated pokerwars app1) test evaluates royal flush of spades (Pokerwars.HandTest)
test/pokerwars/hand_test.exs:19
Assertion with == failed
code: Hand.score(cards) == :royal_flush
left: :high_card
right: :royal_flush
stacktrace:
test/pokerwars/hand_test.exs:28: (test)2) test evaluates royal flush of hearts (Pokerwars.HandTest)
test/pokerwars/hand_test.exs:7
Assertion with == failed
code: Hand.score(cards) == :royal_flush
left: :high_card
right: :royal_flush
stacktrace:
test/pokerwars/hand_test.exs:16: (test)....Finished in 0.04 seconds
6 tests, 2 failures
The output should look familiar to those of you who’ve used test frameworks in other languages before. As you can see, two out of the six tests fail when run.
The default test file test/pokerwars/hand_test.exs
isn’t comprehensive on purpose. The idea is to introduce the layout and syntax of the tests, and you should extend it and write tests for the cases that you write implementations for. If you remember from above, we are going to tackle the parsing for the straight flush, therefore our test suite needs to have tests for the hand, and can skip the other tests since we’re not going to create implementations for those hands at this time.
First, let’s tell ExUnit
to skip the existing tests when we run the suite. You do this by annotating a test
or describe
block with @tag :skip
. For example, skipping the first test looks like:
If we save this and run mix test
you should see the following output
1) test evaluates royal flush of spades (Pokerwars.HandTest)
test/pokerwars/hand_test.exs:20
Assertion with == failed
code: Hand.score(cards) == :royal_flush
left: :high_card
right: :royal_flush
stacktrace:
test/pokerwars/hand_test.exs:29: (test)....Finished in 0.05 seconds
6 tests, 1 failure, 1 skipped
Notice the 1 skipped instead of the old 2 failures?
Add the skip annotation to all of the tests, and we can start to build out the tests for the straight flush.
Testing the straight flush
According to the previously linked summary of hand types, a straight flush is: “5 cards of the same suit with consecutive values. Ranked by the highest card in the hand”. For example, a 7, 8, 9, 10, and Jack — all of clubs — is a straight flush. Lets write a test for it:
Note: I’ve denoted the Jack, King, Queen and Ace as 11, 12, 13, and 14 (ace is high).
If we run our test suite, we should get 1 failure as expected. Lets make it pass, by thinking about our implementation of a straight flush.
If we read the description again, I’ll highlight the two parts that stood out to me: “5 cards of the same suit with consecutive values. Ranked by the highest card in the hand”. From this description, if we can know if a hand has the same suit, and consecutive values, then that hand is a straight flush.
Lets program by wishful thinking a little, and design what we would like our API to be. Open up the code file for the Hand
module, located at lib/pokerwars/hand.ex
and inspect it. The skeleton file has two functions score/1
and evaluate/1
. score/1
calls evaluate/1
which in turn returns :high_card
every time. Let’s update evaluate/1
with a more readable structure to declare our logic:
What does the/1
after function names mean?
The first two lines maps over the list of cards given to obtain their suits and ranks respectively.
Line 4 declares a cond
statement, one of my favourite statements to use in Elixir when multiple conditions are needed in a function. true
is called when no other conditions match.
Line 5 declares the logic for working out the straight flush. We’re checking if the suits are the same, and the ranks are consecutive.
The pipe symbol
If you’ve never seen the pipe (|>
) symbol used before, it’s a syntatic idiom to more eloquently pass parameters into a function. The item on the left is piped (passed to) the function on the right as its first parameter.
For example a |> Enum.count
is the same as Enum.count(a)
. Where the pipe symbol becomes useful is when multiple functions manipulate the same data structure. For example: Enum.count(Enum.sort(a))
can be written as a |> Enum.sort |> Enum.count
which, arguably is more readable.
Line 5 without the pipe symbol will look like Helper.same_suit?(suits) && Helper.consecutive_ranks?(ranks)
. I really like the pipe symbol, and use it where it makes things easier to read for me. If you don’t like it, make sure to substitute the normal function call where appropriate.
Line 7 is the true
clause of the statement, which defaults to a null value that I’ve named :not_matched
. This custom null value make sense within the domain of the hand matching algorithm, and can be checked against as a special case.
The eagle-eyed of you may also have noticed that the functions same_suit?/1
and consecutive_ranks?/1
are invoked via a Helper
module. When planning out the responsibilities of each module, I decided that the Hand
module would represent the logic pertaining to actions related directly to the hand, while any logic that can be applied to Hand
but isn’t specific to it should be abstracted out into its own module, where it can be re- used in multiple places — hence the Helper
module.
With all of that done, lets build out the skeleton for same_suit?/1
and consecutive_ranks?/1
. Open up lib/pokerwars/helpers/hand.ex
and add code similar to below to set up the Helper
module.
We’ve added the skeleton for both functions, and returned false
for now. To access the module as Helper
instead of Pokerwars.Helpers.Hand
in the Hand
module, add the line alias Pokerwars.Helpers.Hand, as: Helper
in the module amongst the existing alias for the Card
module.
Running mix test
should show the previous failure output caused by the logic, and not by imports or unavailable modules. If the latter is the case, double check your directory structure, module names, and aliases.
Good software development practice states that we should write our tests before implementing any logic (technically, I should have written the tests before even defining the module, but this is still fine). Let’s create some unit tests for the Helper
module containing checks for the return values of same_suit?/1
and consecutive_ranks?/1
.
Create a new test file within test/pokerwars/helpers/hand_test.exs
and add the following code:
The tests above should be pretty self-explanatory. Those of you reading this screaming “these aren’t enough tests” — I hear you and I absolutely agree. These are not comprehensive, however they are enough to get started. I urge you to go through all tests and add more edge-cases as you see fit to make the system more bulletproof. For brevity’s sake, however, these should suffice.
Running mix test
should show even more failures, as expected. We haven’t implemented the logic for the functions yet. Let’s do that now.
Same Suit
Let’s first tackle same_suit?/1
. Given a collection of suits, we want to know if every suite in the collection are the same. There are a number of ways of solving this, here’s how I would go about it:
- Get the first suit in the collection.
- If that suit matches every other suit in the collection, then it’s the same, otherwise we can conclude that there are different suits in the collection.
The first part is pretty straight forward. Given a collection named, we can get the first suit using Elixir’s Enum.fetch! function.
The second part is a little more involved, but handled elegantly by Elixir’s functional paradigm. There exists a method in Elixir’s Enum
module called Enum.all? which performs a function for every element in an Enum
and checks if the function returns true
every time. If it does, then Enum.all?
also returns true
.
We can therefore write a function that checks if every element equals the first element that we obtained in the first part, and if it does, then every element is the same.
Here’s an example implementation:
Run the tests and our unit test for same_suit?/1
should now pass and we’re halfway to solving the straight flush.
Consecutive Ranks
This is a lot more interesting and involved than same suit. Given a collection of ranks, we would like to know if they are consecutive — that is, the previous rank is one less than the next rank each time.
I would like to invite you to take a few minutes to try and solve this yourself, using a functional style.
Okay, done? Awesome, share your answer with me below! I would love to see how others attempted it. Here’s how I thought about solving it:
- For each pair in the collection, we want to work out the current element and the next element.
- If the next element subtract the current element is 1 then the current pair is consecutive.
- Repeat for the next pair.
Achieving the first part in Elixir requires knowledge of pattern matching and list splitting. List splitting is taking a list and splitting it into its head (first element), and the tail (a list representing all other elements). The cool thing is that you can also take the corresponding tail, and split that into its head and tail, and so on. If this sounds like recursion to you, that’s because it is. Lists in Elixir are a recursive data structure, and list splitting demonstrates that recursion perfectly.
To split a list, simply use the vertical bar (|
) symbol. For example:
iex(8)> [head| tail] = [1 | [2, 3, 4]]
[1, 2, 3, 4]
iex(9)> head
1
iex(10)> tail
[2, 3, 4]
Also, remember what I said about Lists being a recursive data structive (i.e. a list of lists)?
iex(7)> [1 | [2 | [3 |[4]]]] == [1, 2, 3, 4]
true
The comma-separated syntax is just visual sugar over the underlying recursive monster. Thank you programming language designers! ❤
Using this knowledge, therefore, we can split the list of ranks and use pattern matching to figure out the current element and the next element. We can the do the subtraction to check the difference is one, and recursively call the same function with the rest of the data — the tail, and the next element.
An implementation is below:
Recursion, pattern matching and list splitting makes this extremely simple, the only thing to remember is that we need to prepend the next_number
to the remaining_numbers
list so that the check happens for each pair of numbers, rather than every other pair of numbers. Without this prepending, 1, 2 then 3, 4 then 5, 6 will be checked, instead of 1, 2 then 2, 3 then 3, 4, etc.
Once we get to the second overload of consecutive_ranks?/1
, we know we’ve gotten a collection with only one value, and therefore exhausted the list. We can just return true
at this point.
This should give us the implementation of consecutive_ranks?/1
that we need.
Go ahead and run your tests, and they should all be green. Our unit tests have led to our integration tests passing.
Scattered Straight Flush
One more thing. What happens if you add the following test to test/pokerwars/hand_test.exs
?
It fails, but if you think about it — it shouldn’t! It doesn’t actually matter what order the cards are in your hands, it’s still a straight flush. This means that we probably need to sort our hand before evaluating it — which corresponds to how we play in real life. Once out hand is dealt, we take some time to sort the cards, and then we evaluate it to see what we’ve got. Let’s do the same in code, update the score?/1
function to look like the following
Run your tests, and you should be all green again. A repository representing the current progress can be found here.
An elixir is your good luck charm
Hopefully this has given you a good starting point to approach solving the problem. Elixir as a functional language is very much at home for these sort of algorithmic problems and allows you to deal with them beautifully and succinctly.
Take the consecutive_values?/1
function as an example. Lack of pattern matching in other language paradigms would mean that the problem wouldn’t have been able to be handled so elegantly.
With my current progress, my evaluate/1
function looks like this:
And I think the syntax just looks gorgeous, and the intent of the logic is very clear. This is the kind of code that Elixir nudges the programmer to write, and I love it.
As with everything in the world of software engineering — context is key. Pick the right tool for the right job first and foremost, however when the job is right Elixir really flexes its muscles.
I hope this article has managed to at least show you an alternative approach into solving problems, and how I weave it with a functional paradigm.
I also very much hope to see you all at the next Elixir dojo and future events at the carwow offices.
As per, feedback on all aspects of the piece is much appreciated. Drop it in the comments section, or tweet me. If you liked it, learnt something new, or want more — drop it a ❤.
Hope you all have a wonderful day!
Interested in making an Impact? Join the carwow-team!
Feeling social? Connect with us on Twitter and LinkedIn :-)