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 programming 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 criticism. 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 requires 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:
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:
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 repository. The command to run tests using Elixir’s built in ExUnit module is
mix test. Your output should look something like:
The output should look familiar to those of you who have 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:
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:
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 matched 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 return value of the item on the left is piped (passed to) the function on the right as its first parameter.
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
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
consecutive_ranks?/1. Open up
lib/pokerwars/helpers/hand.ex and add code similar to below to set up the
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
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 I can live with that for the sake of this tutorial). Let’s create some unit tests for the
Helper module containing checks for the return values of
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.
mix test should show even more failures, as expected. We haven’t implemented the logic for the functions yet which we will do now.
Let’s first tackle
same_suit?/1. Given a collection of suits, we want to know if every suit 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 straightforward. 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
We can therefore write a function that checks if every element is equal to 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.
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:
We want to work out the current and next element pairs in the collection
If the next element subtract the current element is 1 then the current pair is consecutive.
Repeat for the each 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:
Also, remember what I said about Lists being a recursive data structure (i.e. a list of lists)?
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 make 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.
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!