Our team’s been doing Katas to get the hang of TDD. One such kata (calculating bowling scores) struck me as insanely simple with pattern matching in F#:
let rec score acc = function | 10 :: (a :: b :: _ as t) -> score (acc + 10 + a + b) t // strike | 10 :: t -> score acc t // final frames following strike | a :: b :: c :: [] when a + b = 10 -> acc + 10 + c // final frame spare | a :: b :: (c :: _ as t) when a + b = 10 -> score (acc + 10 + c) t // spare | h :: t -> score (acc + h) t // normal roll | [] –> acc // Tests let test game expected = let result = score 0 game if result <> expected then printfn "%A = %i (expected %i)" game result expected test [0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0] 0 // gutter game test [1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1] 20 // all ones test [5;5;3] 16 // one spare (wrong!)test [5;5; 3;0; 0;0; 0;0; 0;0; 0;0; 0;0; 0;0; 0;0; 0;0] 16 // one spare test [0;0; 0;0; 0;0; 0;0; 0;0; 0;0; 0;0; 0;0; 0;0; 5;5;3] 13 // one sparetest [10;3;4;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0] 24 // one strike test [10;10;10;10;10;10;10;10;10;10;10;10] 300 // perfect gametest [1;4; 4;5; 6;4; 5;5; 10; 0;1; 7;3; 6;4; 10; 2;8;6] 133
Thanks to Brian Friesen (below in the comments) for pointing out a bug. This wasn't correctly handling spares in the final frame! One line fix (highlighted).
It brings up another issue in one of the test cases as well. [5;5;3] should produce a different score depending on whether it's at the start or end of a complete game. Added two cases for that.