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 spare

test [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 game
test [1;4; 4;5; 6;4; 5;5; 10; 0;1; 7;3; 6;4; 10; 2;8;6] 133 

Oops!

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.