F#: Domain Driven Design

Intro

I must admit. I’m not an expert at Domain-Driven Design. However, I do find it interesting that most people actually design their domain upfront instead of test driving their design. Honestly, I prefer TDD over DDD. Hence, I feel that TDD encourages me to write the bare minimum to flush out my domain logic. Thus, I will argue that the less code exposed to solve a problem, the less room for bugs to surface.

The Game of Thirty-Three

I recently decided to build the domain model for the basketball game “Thirty-three”. On the mean streets of Cleveland, we play Thirty-Three. However, in softer cities such as Charlotte, it is common for people to play Twenty-One.

Regardless of how hardened the culture is of a city, I decided to practice DDD by writing the popular basketball game of “Thirty-Three”.

The following reflects the actors in the game:

type Player = | Player1 | Player2

The following reflects the state of a game:

Game = 
    | Underway of int
    | AlmostGame of int
    | GameTime of int

A game has three major stages:

1. It starts (i.e. underway).
2. It nears completion (i.e. AlmostGame)
3. It ends (i.e. GameTime).

The reason why I wrote the case, AlmostGameTime, is because a player only needs to score 33 points when a he/she shoots from the foul line in order to win. Hence, there is no reason for a player to score 32 points and then shoot two or more foul shots. All the player needs is just one foul shot regardless of the foul shots awarded. Thus, I implemented some logic to account for this specific rule.

The type below serves as a container for a player’s state within the game:

type PlayerScore = {Player:Player; Score:Game}

The following reflects how a player can score:

type Shot = 
     | TwoPointer| ThreePointer
     | FoulShot  | TwoFoulShots | ThreeFoulShots

In the code above, I identified the various methods in which points can be scored by a player. Specifically, a player can score a two-pointer, a three-pointer, or foul shots. In addition, a payer can shoot one to three foul shots.

Defining Literals

I decided to declare the following literals for my basketball game:

[<Literal>]
let Objective = 33

[<Literal>]
let MaxFoulShots = 3

The above literals serves as the game’s parameters. In the functions that I implemented, I rely on these literals to enforce the rules of the game.

Functions

After defining types and literals, I then had to implement the functions for the game.

The following functions were implemented:

let (|Underway|AlmostGame|Game|) (score,shot) =

    let shotValue = match shot with
                    | FoulShot                      -> 1
                    | TwoPointer   | TwoFoulShots   -> 2
                    | ThreePointer | ThreeFoulShots -> 3

    match score, shotValue with
    | Underway   s,v -> if (s + v) <= (Objective - MaxFoulShots)
                        then Underway   (s + v)
                        else AlmostGame (s + v)

    | AlmostGame s,v -> if ( (s+v) < Objective )
                        then AlmostGame (s + v)
                        else Game Objective

    | GameTime   s,v -> Game s

let makeShot shot (shooter, defender) =

    match (shooter.Game, shot) with
    | Underway p   -> { shooter with Game=Underway p },   defender
    | AlmostGame p -> { shooter with Game=AlmostGame p }, defender
    | Game p       -> { shooter with Game=GameTime p },   defender

let startGame = 

    let player1Score = { Player=Player1; Game=Underway 0 }
    let player2Score = { Player=Player2; Game=Underway 0 }

    (player1Score, player2Score)

Examining functions:

The following function is an active pattern:

let (|Underway|AlmostGame|Game|) (score,shot) =

    let shotValue = match shot with
                    | FoulShot                      -> 1
                    | TwoPointer   | TwoFoulShots   -> 2
                    | ThreePointer | ThreeFoulShots -> 3

    match score, shotValue with
    | Underway   s,v -> if (s + v) <= (Objective - MaxFoulShots)
                        then Underway   (s + v)
                        else AlmostGame (s + v)

    | AlmostGame s,v -> if ( (s+v) < Objective )
                        then AlmostGame (s + v)
                        else Game Objective

    | GameTime   s,v -> Game s

This active pattern yields a specific stage of the game to the client. The function takes the player’s score and his/her most recent shot. Thus, the values of those parameters are used to identify the player’s state within the game and that state will be returned to the client caller as a math to their pattern matching construct.

Starting the Game

I wrote the following function to initiate the game:

let startGame = 

    let player1Score = { Player=Player1; Score=Underway 0 }
    let player2Score = { Player=Player2; Score=Underway 0 }

    (player1Score, player2Score)

In the above code, I set each player’s points to zero and set the stage of the game to underway.

The following function serves as the core function to the model:

let makeShot shot (shooter, defender) =

    match (shooter.Score, shot) with
    | Underway p   -> { shooter with Score=Underway p },   defender
    | AlmostGame p -> { shooter with Score=AlmostGame p }, defender
    | Game p       -> { shooter with Score=GameTime p },   defender

The above function leverages the active pattern that I discussed earlier. This function takes the most recent shot as well as a tuple reflecting the scores of the shooter and defender. Ultimately, this function generates the latest state of the game based on the shot made by the shooter.

The entire domain model is as follows:

module Game
// ************************************************************
(*Types*)
// ************************************************************
type Player = | Player1 | Player2

type PlayerScore = {Player:Player; Score:Game}

and Shot = 
     | TwoPointer| ThreePointer
     | FoulShot  | TwoFoulShots | ThreeFoulShots

and Game = 
    | Underway of int
    | AlmostGame of int
    | GameTime of int

[<Literal>]
let Objective = 33

[<Literal>]
let MaxFoulShots = 3

// ************************************************************
(*Functions*)
// ************************************************************
let (|Underway|AlmostGame|Game|) (score,shot) =

    let shotValue = match shot with
                    | FoulShot                      -> 1
                    | TwoPointer   | TwoFoulShots   -> 2
                    | ThreePointer | ThreeFoulShots -> 3

    match score, shotValue with
    | Underway   s,v -> if (s + v) <= (Objective - MaxFoulShots)
                        then Underway   (s + v)
                        else AlmostGame (s + v)

    | AlmostGame s,v -> if ( (s+v) < Objective )
                        then AlmostGame (s + v)
                        else Game Objective

    | GameTime   s,v -> Game s

let makeShot shot (shooter, defender) =

    match (shooter.Score, shot) with
    | Underway p   -> { shooter with Score=Underway p },   defender
    | AlmostGame p -> { shooter with Score=AlmostGame p }, defender
    | Game p       -> { shooter with Score=GameTime p },   defender

let startGame = 

    let player1Score = { Player=Player1; Score=Underway 0 }
    let player2Score = { Player=Player2; Score=Underway 0 }

    (player1Score, player2Score)

The Client

I wrote the following client to consume the domain model:

// ************************************************************
(*Client*)
// ************************************************************
let player1Score, player2Score = startGame

let player1, player2 = (player1Score, player2Score) |> makeShot TwoPointer
let shooter, defender = player2, player1

let final = (shooter, defender) |> makeShot ThreePointer
                                |> makeShot FoulShot

                                |> makeShot ThreePointer
                                |> makeShot TwoFoulShots

                                |> makeShot ThreePointer
                                |> makeShot ThreeFoulShots

                                |> makeShot ThreePointer
                                |> makeShot ThreePointer
                                |> makeShot ThreePointer
                                |> makeShot ThreePointer
                                |> makeShot ThreePointer

                                |> makeShot FoulShot
                                |> makeShot FoulShot
                                |> makeShot ThreeFoulShots

Conclusion

In conclusion, I provided an example of how to implement a domain model for the Game of Thirty-Three.

Advertisements
1 comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: