F# Card Game Part 1 - Modeling the Domain

This is the first post in a series where we'll walk through creating the card game Crazy Eights in F#. Playing cards are a fairly well known domain and, despite the name, Crazy Eights is quite easy to understand. In this post we'll walk through modeling the domain.

Modeling our Domain

Crazy Eights is played with a standard 52 card deck which means we have Deuce through Ace and the four suits but no Jokers. To model these cases we'll use two Discriminated Unions, one for Suit and one for Rank.

type Suit = Spades | Hearts | Clubs | Diamonds

type Rank = Two | Three | Four  | Five  
            | Six | Seven | Eight | Nine
            | Ten | Jack  | Queen | King | Ace

The Discriminated Union is an excellent choice in this case because there are a known set of acceptable values for each of the two types. We will also be able to pattern match on specific Rank and Suit values with ease and completeness which we'll see when we get to adding the game logic.

Now that we have our Rank and Suit types we can easily create a Card type by creating a Tuple, or Product Type.

type Card = Rank * Suit  

This type definition says that a Card is a combination of Rank and Suit. Said another way, a card can only be any combination of Rank and Suit.

A record type (which we'll see in a minute) would also be a good choice here and I may end up changing this definition before the game is complete, but in the interest of showing some of the choices you have within F# we'll use a tuple for now.

Finally we'll need something to represent the Deck.

type Deck = Card list  

Here we simply alias Card list as a Deck.

Card list is the same as List<Card>. From my experience writing it as Card list makes it easier for non technical people to understand which is very helpful when modeling a domain.

Here is all of the code used to model our Deck.

type Suit = Spades | Hearts | Clubs | Diamonds

type Rank = Two | Three | Four  | Five |  
            Six | Seven | Eight | Nine | 
            Ten | Jack  | Queen | King | Ace

type Card = Rank * Suit  
type Deck = Card list  

Modeling the Game

A game of Crazy Eights has at least 2 players and each of them has their own private cards. To model a player we'll use a record type.

type Player = { Name : string; Hand : Card list }  

Using a record type allows us to name the type's properties and is generally easier to destructure. For now we'll say that a Player has a Name which is a string and a Hand which is a Card list.

To model the current state of our game we'll create another record type.

type Game = { Deck : Card list  
              Players : Player list 
              CurrentPlayer : Player
              DiscardPile : Card list }

You may notice that this record type doesn't have semicolons between properties like our Player type did. If you place properties on their own line you can omit the semicolons.

The Result

In very few lines we've defined some basic types that we'll use moving forward. Some of these may change, but for now we've seen use cases for Discriminated Unions, Product Types (Tuples), Type Aliases, and Record Types.

type Suit = Spades | Hearts | Clubs | Diamonds

type Rank = Two | Three | Four  | Five |  
            Six | Seven | Eight | Nine | 
            Ten | Jack  | Queen | King | Ace

type Card = Rank * Suit  
type Deck = Card list

type Player = { Name : string; Hand : Card list }

type Game = { Deck : Card list  
              Players : Player list 
              CurrentPlayer : Player
              DiscardPile : Card list }

In the next part we'll start modeling the various states that the game can be in.

Further Reading

Related Posts

Proudly published with Ghost