Learning F#: The Game of Life (Vol.2)

Intro

I posted my thoughts several articles ago regarding my journey of learning F# via the implementation of The Game of Life. I had initially attempted (through my ignorance) Domain-Driven Design and quickly ran into some design issues. I received some feedback regarding my initial design on StackOverflow and decided to try an alternative design. Thus, I decided to use TDD.

One of the feedback items that I received was to consider using a grid instead of an array. But what really is a grid? As of right now, I would define a grid as a container of X,Y coordinates. Then I was confronted with the question, “how do I represent an x,y coordinate?”.

Representing an X,Y Coordinate

At first, I considered using a tuple:

let cell = 0,0
let x,y = cell

However, in this domain a grid has cells which is really an identified part of the system. As a result, I decided that it would probably be best to define a cell instead.

Thus, I defined a cell as the following:

type Cell = { X:int; Y:int }

What to do with a Cell?

Having a cell is great. However, a cell by itself is rendered useless. Hence, The Game of Life requires cells to live, die, and be resurrected from the dead based on the status of their neighbors.

Defining a Cell’s Neighbor

Using a grid I can visually identify a cell’s neighbor. However, to do this programmatically, I relied on TDD.

The following logic was generated via TDD:

let isXNeighbor cell1 cell2 = 

    if cell1.X >= 0  && cell2.X >= 0 then
        abs (cell1.X - cell2.X) = abs 1 ||
        abs (cell1.X - cell2.X) = abs 0
    else
        abs (cell2.X - cell1.X) = abs 1 ||
        abs (cell2.X - cell1.X) = abs 0

let isYNeighbor cell1 cell2 = 

    if cell1.Y >= 0  && cell2.Y >= 0 then
        abs (cell1.Y - cell2.Y) = abs 1 ||
        abs (cell1.Y - cell2.Y) = abs 0
    else
        abs (cell2.Y - cell1.Y) = abs 1 ||
        abs (cell2.Y - cell1.Y) = abs 0

let isNeighbor cell1 cell2 =

    if cell1.X <> cell2.X && cell1.Y <> cell2.Y then
        let xAligned = isXNeighbor cell1 cell2
        let yAligned = isYNeighbor cell1 cell2

        xAligned && yAligned

    else false

I then refactored that logic to the following:

let isAbsNeighbor v1 v2 =
    abs (v1 - v2) = abs 1 ||
    abs (v1 - v2) = abs 0

let IsValueNeighbor v1 v2 =

    if v1 >= 0  && v2 >= 0
    then isAbsNeighbor v1 v2
    else isAbsNeighbor v2 v1

let isNeighbor cell1 cell2 =

    if cell1.X <> cell2.X || cell1.Y <> cell2.Y then
        let xAligned = IsValueNeighbor cell1.X cell2.X
        let yAligned = IsValueNeighbor cell1.Y cell2.Y

        xAligned && yAligned

    else false

The actual tests that flushed out the logic is the following:

open FsUnit
open NUnit.Framework

[<Test>]
let ``cells sharing x-coordinate are neighbors``() =
   // Setup
   let cell1 = { X=0; Y=0 }
   let cell2 = { X=0; Y=1 }

   // Verify
   cell1 |> isNeighbor cell2
         |> should equal true

[<Test>]
let ``cells sharing y-coordinate are neighbors``() =
   // Setup
   let cell1 = { X=0; Y=1 }
   let cell2 = { X=1; Y=1 }

   cell1 |> isNeighbor cell2
         |> should equal true

[<Test>]
let ``cell that's right 1 and down 1 is neighbor``() =
   // Setup
   let cell1 = { X=0; Y=0 }
   let cell2 = { X=1; Y=(-1) }

   // Verify
   cell1 |> isNeighbor cell2
         |> should equal true

[<Test>]
let ``cell that's right 1 and down-0 is neighbor``() =
   // Setup
   let cell1 = { X=0; Y=0 }
   let cell2 = { X=1; Y=0 }

   // Verify
   cell1 |> isNeighbor cell2
         |> should equal true

[<Test>]
let ``cell that's right 1 and up 1 is neighbor``() =
   // Setup
   let cell1 = { X=0; Y=0 }
   let cell2 = { X=1; Y=1 }

   // Verify
   cell1 |> isNeighbor cell2
         |> should equal true

[<Test>]
let ``cell that's up 1 is neighbor``() =
   // Setup
   let cell1 = { X=0; Y=0 }
   let cell2 = { X=0; Y=1 }

   // Verify
   cell1 |> isNeighbor cell2
         |> should equal true

[<Test>]
let ``cell that's down 1 is neighbor``() =
   // Setup
   let cell1 = { X=0; Y=0 }
   let cell2 = { X=0; Y=(-1) }

   // Verify
   cell1 |> isNeighbor cell2
         |> should equal true

[<Test>]
let ``cell that's left 1 and up 1 is neighbor``() =
   // Setup
   let cell1 = { X=0; Y=0 }
   let cell2 = { X=(-1); Y=1 }

   // Verify
   cell1 |> isNeighbor cell2
         |> should equal true

[<Test>]
let ``cell that's left 1 and down 1 is neighbor``() =
   // Setup
   let cell1 = { X=0; Y=0 }
   let cell2 = { X=(-1); Y=(-1) }

   // Verify
   cell1 |> isNeighbor cell2
         |> should equal true

[<Test>]
let ``far away x-coordinates are not neighbors``() =
   // Setup
   let cell1 = { X=(-1); Y=0 }
   let cell2 = { X=(+1); Y=0 }

   // Verify
   cell1 |> isNeighbor cell2
         |> should equal false

[<Test>]
let ``far away y-coordinates are not neighbors``() =
   // Setup
   let cell1 = { X=0; Y=(+1) }
   let cell2 = { X=0; Y=(-1) }

   // Verify
   cell1 |> isNeighbor cell2
         |> should equal false

[<Test>]
let ``far away x,y-coordinates are not neighbors``() =
   // Setup
   let cell1 = { X=(+1); Y=(+1) }
   let cell2 = { X=(+1); Y=(-1) }

   // Verify
   cell1 |> isNeighbor cell2
         |> should equal false

[<Test>]
let ``cells with same coordinates cannot be neighbors``() =
   // Setup
   let cell1 = { X=0; Y=0 }
   let cell2 = { X=0; Y=0 }

   // Verify
   cell1 |> isNeighbor cell2
         |> should equal false

Conclusion

In conclusion, I posted my thoughts several articles ago regarding my journey of learning F# via the implementation of The Game of Life. I had initially attempted (through my ignorance) Domain-Driven Design and quickly ran into some design issues. I received some feedback regarding my initial design on StackOverflow and decided to try an alternative design. Thus, I decided to use TDD. I’m still a novice to F#. Hence, I know that my implementation can be further refined. However, I do believe that I no expert starts as an expert. Thus, wish me luck!

Advertisements

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: