Monday, 2 March 2009

Bowling Game Kata Implementation

//Bowling Game Kata Implementation

open StateMonad

let AllPins = 10
let ZeroPins = 0

let IsStrike(roll, _) = roll = AllPins
let IsSpare(roll, nextRoll) = (roll + nextRoll) = AllPins
let SpareBonus rolls = match rolls with
| (rollA, rollB)::rolls' -> rollA
| [] -> 0
let StrikeBonus rolls = match rolls with
| (rollA,rollB)::rolls' -> rollA + if rollA = AllPins then rollA else rollB
| [] -> 0

let frames rolls =
let rec frames' rolls =
match rolls with
| rollA::rollB::[] -> [(rollA,rollB)]
| rollA::rollB::rolls' -> if (IsStrike(rollA, rollB)) then
(rollA, ZeroPins) :: frames' (rollB::rolls')
else
(rollA, rollB) :: frames' rolls'
| rollA::[] -> [(rollA, ZeroPins)]
frames' rolls


let NextScoreNoBonus prevScore rollA rollB = prevScore + rollA + rollB

let rec totals xs score =
match xs with
| (x,y)::[] when x < AllPins -> (NextScoreNoBonus score x y)
| (_,_)::[] -> score
| (x,y)::xs ->
if
(IsStrike(x,y)) then
totals xs (score + x + StrikeBonus xs)
else
if
(IsSpare(x,y)) then
totals xs ((NextScoreNoBonus score x y) + SpareBonus xs)
else
totals xs (NextScoreNoBonus score x y)
| [] -> score


let Score m = Execute (state { do! m
let! score = GetState
return score }) []
let Total xs = (totals (frames xs) 0)

let Roll(pins) = state { let! s = GetState
do! SetState(s @ [pins]) }

let RollList xs = state { let! xs' = MMap (fun s -> state { do! Roll s }) xs
return () }

let RollMany t s = state { do! RollList (List.map (fun _ -> s) [1..t]) }

let RollSpare = state { do! Roll 5
do! Roll 5 }

let RollStrike = state { do! Roll 10 }

Bowling Game Kata Tests

// Bowling Game Kata Tests

open NUnit.Framework
open Bowling
open StateMonad

[<TestFixture>]
type BowlingTests =
new() = {}

[<Test>]
member x.gutterGame() =
Assert.AreEqual(0, Score(RollMany 20 0) |> Total)

[<Test>]
member x.allOnes() =
Assert.AreEqual(20, Score(RollMany 20 1) |> Total)

[<Test>]
member x.oneSpare() =
Assert.AreEqual(16, Score(state { do! RollSpare
do! Roll 3
do! RollMany 17 0 }) |> Total)
[<Test>]
member x.oneStrike() =
Assert.AreEqual(24, Score(state { do! RollStrike
do! Roll 3
do! Roll 4
do! RollMany 16 0 }) |> Total)
[<Test>]
member x.perfectGame() =
Assert.AreEqual(300, Score(state { do! RollMany 12 10 }) |> Total)