F# and Monopoly Simulation Redux

Now that I am 4 months into my F# adventure, I thought I would revisit the monopoly simulation that I wrote in August.  There are some pretty big differences

1)  I am not using the ‘if…then’ construct at all –> rather I am using pattern matching.  For example, consider the original communityChest function:

  1. let communityChest x y =
  2.     if y = 1 then
  3.         0
  4.     else if y = 2 then
  5.         10
  6.      else
  7.         x

and the most recent one:

  1. let communityChest (tile, randomNumber) =
  2.     match randomNumber with
  3.         | 1 -> 0
  4.         | 2 -> 10
  5.         | _ -> tile

“Big deal”, you are saying to yourself (or at least I did).  But the power of pattern matching is put on display with the revised chance.  The code is much more readable and understandable.

Original:

  1. let chance x y =
  2.     if y = 1 then
  3.         0
  4.     else if y = 2 then
  5.         10
  6.     else if y = 3 then
  7.         11
  8.     else if y = 4 then
  9.         39
  10.     else if y = 5 then
  11.         x – 3
  12.     else if y = 6 then
  13.         5
  14.     else if y = 7 then
  15.         24
  16.     else if y = 8 then
  17.         if x < 5 then
  18.             5
  19.         else if x < 15 then
  20.             15
  21.         else if x < 25 then
  22.             25
  23.         else if x < 35 then
  24.             35
  25.         else
  26.             5
  27.     else if y = 9 then
  28.         if x < 12 then
  29.             12
  30.         else if x < 28 then
  31.             28
  32.         else
  33.             12
  34.     else
  35.         x

 

Revised:

  1. let goToNearestRailroad tile =
  2.     match tile with
  3.         | 36|2 -> 5
  4.         | 7 -> 15
  5.         | 17|22 -> 25
  6.         | 33 -> 35
  7.         | _ -> failwith "not on chance"
  8.  
  9. let goToNearestUtility tile =
  10.     match tile with
  11.         | 36|2|7 -> 12
  12.         | 12|22|33-> 28
  13.         | _ -> failwith "not on chance"
  14.  
  15. let chance (tile, randomNumber) =
  16.     match randomNumber with
  17.         | 1 -> 0
  18.         | 2 -> 10
  19.         | 3 -> 11
  20.         | 4 -> 39
  21.         | 5 -> tile – 3
  22.         | 6 -> 5
  23.         | 7 -> 24
  24.         | 8 -> goToNearestRailroad tile
  25.         | 9 -> goToNearestUtility tile
  26.         | _ -> tile

 

As a side note, I ditched the x and y values because they are unreadable.  When I went back to the code after 3 months, I spent way too long trying to figure out what the heck ‘x’ was.  I know that scientific code uses cryptic values, but clean code does not.  I changed them and the code became much better.

I then took a look at the move() function.  The original:

  1. let move x y z =
  2.     if x + y > 39 then
  3.         x + y – 40
  4.     else if x + y = 30 then
  5.         10
  6.     else if x + y = 2 then
  7.         communityChest 2 z
  8.     else if x + y = 7 then
  9.         chance 7 z
  10.     else if x + y = 17 then
  11.         communityChest 17 z
  12.     else if x + y = 22 then
  13.         chance 22 z
  14.     else if x + y = 33 then
  15.         communityChest 33 z
  16.     else if x + y = 36 then
  17.         chance 36 z
  18.     else
  19.         x + y  

 

and the revised:

  1. let getBoardMove (currentTile, dieTotal) =
  2.     let initialTile = currentTile + dieTotal
  3.       matchinitialTile with
  4.           | 2 ->communityChest (2, random.Next())
  5.           | 7 ->chance (7, random.Next())
  6.           | 17 ->communityChest (17, random.Next())
  7.           | 22 ->chance (22, random.Next())
  8.         | 30 -> 10
  9.           | 33 ->communityChest (2, random.Next())
  10.           | 36 ->chance (7, random.Next())
  11.         | 40|41|42|43|44|45|46|47|48|49|50|51 -> initialTile – 40
  12.         | _ -> initialTile   

 

I am not happy with line 11 above – but apparently there is not a way in F# to do this ‘>40’ or even ‘[40 .. 51]’ in the left hand side of the pattern match.

So far, the biggest changes were to make the values more understandable and to get rid of the if…then statements and replace them with pattern matching.  Both these techniques make the code more readable and understandable.  The next big change came with the actual game play itself.  The original version:

  1. let simulation =
  2.     let mutable startingTile = 0
  3.     let mutable endingTile = 0
  4.     let mutable doublesCount = 0
  5.     let mutable inJail = false
  6.     let mutable jailRolls = 0
  7.     for diceRoll in 1 .. 10000 do
  8.         let dieOneValue = random.Next(1,7)
  9.         let dieTwoValue = random.Next(1,7)
  10.         let cardDraw = random.Next(1,17)
  11.         let numberOfMoves = dieOneValue + dieTwoValue
  12.         
  13.         if dieOneValue = dieTwoValue then
  14.             doublesCount <- doublesCount + 1
  15.         else
  16.             doublesCount <- 0
  17.         if inJail = true then
  18.             if doublesCount > 1 then
  19.                 inJail <- false
  20.                 jailRolls <- 0
  21.                 endingTile <- move 10 numberOfMoves cardDraw
  22.             else
  23.                 if jailRolls = 3 then
  24.                     inJail <- false
  25.                     jailRolls <- 0
  26.                     endingTile <- move 10 numberOfMoves cardDraw
  27.                 else
  28.                     inJail <- true
  29.                     jailRolls <- jailRolls + 1
  30.         else
  31.             if doublesCount = 3 then
  32.                 inJail <- true
  33.                 endingTile <- 10
  34.             else
  35.                 endingTile <- move startingTile numberOfMoves cardDraw
  36.          
  37.         printfn "die1: %A + die2: %A = %A FROM %A TO %A"
  38.             dieOneValue dieTwoValue numberOfMoves startingTile endingTile
  39.         startingTile <- endingTile
  40.         tiles.[endingTile] <- tiles.[endingTile] + 1

You will notice that the word ‘’mutable” shows up six times.  Using the word mutable in F# is a code smell so I refactored it out like so:

  1. let rec rollDice (currentTile, rollCount, doublesCount, inJail, jailRollCount)=
  2.     let dieOneValue = random.Next(1,7)
  3.     let dieTwoValue = random.Next(1,7)
  4.     let dieTotal = dieOneValue + dieTwoValue
  5.     let newRollCount = rollCount + 1
  6.     
  7.     let newDoublesCount =
  8.         if dieOneValue = dieTwoValue then doublesCount + 1
  9.         else 0
  10.  
  11.     let newTile = getTileMove(currentTile,dieTotal,newDoublesCount,inJail,jailRollCount)
  12.     
  13.     let newInJail =
  14.         if newTile = 10 then true
  15.         else false
  16.  
  17.     let newJailRollCount =
  18.         if newInJail = inJail then jailRollCount + 1
  19.         else 0
  20.  
  21.     let targetTuple = scorecard.[newTile]
  22.     let newTuple = (fst targetTuple, snd targetTuple + 1)
  23.     scorecard.[newTile] <- newTuple
  24.  
  25.             if rollCount < 10000 then
  26.         rollDice (newTile, newRollCount, newDoublesCount, newInJail, newJailRollCount)
  27.     else
  28.         scorecard

No “mutable” (thanks to recursion) and only 1 assignment.  I also wanted to get rid of that one ‘<-‘ and Thomas Petrick was kind enough to demonstrate the correct way to do this on stack overflow.  Finally, I had to throw in a supporting function to make the decision logic account for rolling doubles that may put you in jail or may get you out of jail depending on prior state (were you in jail when you rolled doubles, were you out of jail when you rolled doubles for the 3rd time, etc…).  I spent way too much time monkeying around with a series of nest if…then statements when it hit me that I should be using tuples and pattern matching:

  1. let getTileMove (currentTile, dieTotal, doublesCount, inJail, jailRollCount) =
  2.     match (inJail,jailRollCount, doublesCount) with
  3.         | (true,3,_) -> getBoardMove(10,dieTotal)
  4.         | (true,_,_) -> 10
  5.         | (false,_,3) -> 10
  6.         | (false,_,_) -> getBoardMove(10,dieTotal)

So here if the real power of F# on display.  I can think of hundreds of applications that I have seen in C#/VB.NET that have a high cyclomatic complexity and hidden bugs that have reared their head at the most inopportune time because of complex business logic using a series of case..switch and/or if..then. statements. Even by putting step into its own function only helps partially because the code is still there –> it is just labeled better.

By using tupled pattern matching, all of that complexity goes away and we have a succinct series of statements that actually reflect how the brain thinks about the problem.  By using F#, there are fewer lines of code (and therefore fewer unit tests to maintain) and you can write code that better represents how the wetware is approaching the problem.

2 Responses to F# and Monopoly Simulation Redux

  1. You can use | i when i >= 40 && i … instead of | 40|41|42|43|44|45|46|47|48|49|50|51 -> …

  2. Pingback: F# Weekly #51, 2013 | Sergey Tihon's Blog

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 )

Facebook photo

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

Connecting to %s

%d bloggers like this: