F# Card Game Part 2 - Dealing Cards
Friday, May 20, 2016
In part one of this series we walked through modeling the domain of our card game. In this post we'll use that domain as a guide to help us create a deck, shuffle the deck, and deal cards from the deck.
Creating the deck
There may be more elegant ways to do this, but for now we'll simply define a value and populate it with the cards.
let newDeck = [(Two, Spades); (Three, Spades); (Four, Spades); (Five, Spades); (Six, Spades); (Seven, Spades); (Eight, Spades); (Nine, Spades); (Ten, Spades); (Jack, Spades); (Queen, Spades); (King, Spades); (Ace, Spades); (Two, Hearts); (Three, Hearts); (Four, Hearts); (Five, Hearts); (Six, Hearts); (Seven, Hearts); (Eight, Hearts); (Nine, Hearts); (Ten, Hearts); (Jack, Hearts); (Queen, Hearts); (King, Hearts); (Ace, Hearts); (Two, Clubs); (Three, Clubs); (Four, Clubs); (Five, Clubs); (Six, Clubs); (Seven, Clubs); (Eight, Clubs); (Nine, Clubs); (Ten, Clubs); (Jack, Clubs); (Queen, Clubs); (King, Clubs); (Ace, Clubs); (Two, Diamonds); (Three, Diamonds); (Four, Diamonds); (Five, Diamonds); (Six, Diamonds); (Seven, Diamonds); (Eight, Diamonds); (Nine, Diamonds); (Ten, Diamonds); (Jack, Diamonds); (Queen, Diamonds); (King, Diamonds); (Ace, Diamonds)]
Because this value is immutable (the default in F#), we don't have to make a function that returns a new
Card list. No function can modify our
newDeck value in any way; it will always be exactly the same value.
Notice I'm continuing to use the term "value" rather than "variable". These values cannot vary once they are assigned. This concept is paramount to the entire series.
Shuffling the deck
Now that we have the concept of a full deck we need to introduce some way to shuffle the deck to produce a random order of cards so that each game is different. Of course, our
newDeck value is immutable so what we'll actually do is return a new
Card list that is shuffled rather than actually modifying the
let shuffle deck = let random = new System.Random() deck |> List.sortBy (fun card -> random.Next())
If we hover over
shuffle we'll see that it is a function that takes a
'a list and returns an
'a list. The default for F#'s type inference is to be generic as possible. In the anonymous function we passed into
List.sortBy we didn't use the
card argument at all so we now have a function that could actually be used to randomly sort any kind of list.
I'm aware that System.Random() is probably not the best choice for generating "true randomness" but that's not the point of this post.
To test it out we can easily run it in our REPL
newDeck |> shuffle //val it : (Rank * Suit) list = // [(King, Spades); (Four, Clubs); (Ace, Clubs); (Four, Hearts); (Ten, Clubs); ...
And for proof that no values were harmed during the shuffle process you could evaluate
newDeck again and see that it is still the same value it was before.
newDeck //val it : (Rank * Suit) list = // [(Two, Spades); (Three, Spades); (Four, Spades); (Five, Spades);
Dealing a card
pop which would return the first element in the array and mutate the array so that the returned element is no longer part of the array.
The immutable way of dealing a card is to do what's known as a
head::tail pattern match. We destructure the list into a
head, which is the first element of the list, and a
tail which is the rest of the list. That allows us to easily return the drawn card and a new list of cards that can represent the remaining deck.
let deal deck = match deck with | head::tail -> (head, tail) |  -> //handle empty list
match statement F# forces you to be complete; you must explicitly handle every case that can exist. If I hadn't handled the empty list scenario the compiler would tell me that I had an incomplete pattern match and suggest cases that I hadn't handled.
In the case that we have a non empty list our implementation is quite easy. We return a tuple of the top card and the remaining cards. But what should we do in the case of an empty list?
A functional approach to handling the error state would be to introduce the
let deal deck = match deck with | head::tail -> (Some head, tail) |  -> (None, )
Looking at the
deal function's signature we can see that it takes an
'a list and returns a tuple of an
'a option and an
'a list. This is honest code. It says, I'll try to return you the top card, but I can't guarantee it because you could hand me an empty list.
newDeck |> shuffle |> deal;; val it : (Rank * Suit) option * (Rank * Suit) list = (Some (Seven, Clubs), [(Two, Hearts); (King, Spades); (Five, Clubs); (Eight, Spades); ...
52 card pick up
An easy way to prove that our deal function does what we'd expect is to draw all the cards in the deck. In a functional sense what that means is that we want to use a recursive function to loop through the deck.
let rec dealAllCards deck = let (card, remainingDeck) = deck |> deal match card with | None -> printfn "Cards out" | Some c -> printfn "%A" c remainingDeck |> dealAllCards
rec directive tells F# that the function is a recursive function and is allowed to call itself. The name of our function is
dealAllCards and it takes one argument which is a
In the first line
let (card, remainingDeck) = deck |> deal we're destructuring the tuple returned from our
deal function into a
card value and a
card value is an option type our next step is to match on it to determine our next step.
deal function was not able to return a card (the
None case) we print "Cards out" to the console.
deal function returned a card (the
Some c case) we print the card to the console and then recursively call the function with the
Running our function in the REPL proves our code works
newDeck |> shuffle |> dealAllCards //(Five, Diamonds) //(Six, Hearts) //(Six, Clubs) //... //Cards out