hubFS: THE place for F#

. . . are you on The Hub?
Welcome to hubFS: THE place for F# Sign in | Join | Help
in Search

Test Driven F# - The Bowling Game

Last post 11-13-2007, 8:01 by RayV. 6 replies.
Sort Posts: Previous Next
  •  11-05-2007, 1:43 3938

    Test Driven F# - The Bowling Game

    Hi all... I had a crack at the classic TDD "Bowling Game" example in F#. The code is pretty short compared to doing it in C# / C++ . However I'd appareiciate any feedback on the actual code. Especially if I can simplify it.

    The original article on the bowling game is http://www.objectmentor.com/resources/articles/xpepisode.htm

    Regards,

    Keith

    #light

     

    open System

    open NUnit.Framework

     

    let rec score_bowls bowls =

        let rec score_bowls' frame l  =

            match l with

            | _ when frame = 10 -> (List.fold_right (fun x y -> x + y) l 0)

            | [] -> 0

            | [f] -> f

            | [f;s] -> f + s

            | f :: s :: n :: tail when f = 10       -> 10 + s + n + score_bowls' (frame+1) ( s :: n :: tail )

            | f :: s :: n :: tail when (f + s) = 10 -> 10 + n + score_bowls' (frame+1) (n :: tail)

            | f :: s :: n :: tail                   -> f + s + score_bowls' (frame+1) (n :: tail)

        score_bowls' 1 bowls

     

    [<TestFixture>]

    type BowlingTestCases = class

       new() = {}  

       [<Test>]

       member x.SimpleScoring() = Assert.AreEqual(6, score_bowls [1;2;3] )

       [<Test>]

       member x.ScoreSpare() = Assert.AreEqual(12, score_bowls [2;8;1] )

       [<Test>]

       member x.ScoreStrike() = Assert.AreEqual(16, score_bowls [10;1;2] )

       [<Test>]

       member x.ScorePerfectGame() = Assert.AreEqual( 300, score_bowls [for i in 1..12 -> 10] )

       [<Test>]

       member x.SpareLastFrame() = Assert.AreEqual( 11, score_bowls ([for i in 1..18 -> 0] @ [2;8;1]) )

       [<Test>]

       member x.StrikeLastFrame() = Assert.AreEqual( 21, score_bowls ([for i in 1..18 -> 0] @ [10;10;1]) )

    end

     


    Keith Nicholas
    http://designingcode.blogspot.com
  •  11-07-2007, 2:00 3959 in reply to 3938

    Re: Test Driven F# - The Bowling Game

    Keith,

    That's a pretty cool program.  Your representation is pretty concise.  I've posted a different version below that is tail recursive (could be construed as pointless, as it is capped at 10 frames by the second match case below).

    I only did some cursory testing, but the logic seems sound.


    let score_bowls bowls =
      let rec sb frame l score =
        match l with
        | [] -> score
        | _ when frame = 10 -> score
        | [f] -> sb (frame+1) [] (score + f)
        | 10 :: s :: n :: tail -> sb (frame+1) (s::n::tail) (score+10+s+n)
        | f :: s :: n :: tail when (f + s) = 10 -> sb (frame+1) (n::tail) (score+10+n)
        | f :: s :: tail -> sb (frame+1) tail (score+f+s)
      sb 0 bowls 0

    In this code, I am using frame to represent how many frames have been processed so far.  score is the accumulator, while l represents the remaining frames to process.  The main difference, other than the accumulator, is that I have removed the match case [f; s], which is handled by the last case above.

    The equivalent non-tail recursive function is:

    let score_bowls2 bowls =
      let rec sb frame l =
        match l with
        | [] -> 0
        | _ when frame = 10 -> 0
        | [f] -> f
        | 10 :: s :: n :: tail -> 10 + s + n + sb (frame+1) (s::n::tail)
        | f :: s :: n :: tail when (f + s) = 10 -> 10 + n + sb (frame+1) (n::tail)
        | f :: s :: tail -> f + s + sb (frame+1) tail
      sb 0 bowls

    Regards,

    z.

  •  11-08-2007, 20:12 3988 in reply to 3959

    Re: Test Driven F# - The Bowling Game

    Continuing to try and improve my initial code and remove some of the duplication (and fix a bug)

     

     

    #light

     

    open System

    open NUnit.Framework

     

    let rec score_bowls bowls =

        let rec score_bowls' frame l  =

            let sum b = (List.fold_right (fun x y -> x + y) b 0)

            let nextframe = score_bowls' (frame+1)

            match l with

            | _ when frame = 10 -> sum l

            | [10;s] -> 10 + s + s

            | 10 :: s :: n :: tail -> 10 + s + n + nextframe (s :: n :: tail ) 

            | f :: s :: n :: tail -> f + s + (if((f+s)=10) then n else 0) + nextframe (n :: tail)

            | _ -> sum l

        score_bowls' 1 bowls

     

    [<TestFixture>]

    type BowlingTestCases = class

       new() = {}  

       [<Test>]

       member x.SimpleScoring() = Assert.AreEqual(6, score_bowls [1;2;3] )

       [<Test>]

       member x.ScoreSpare() = Assert.AreEqual(12, score_bowls [2;8;1] )

       [<Test>]

       member x.ScoreStrike() = Assert.AreEqual(16, score_bowls [10;1;2] )

       [<Test>]

       member x.ScorePerfectGame() = Assert.AreEqual( 300, score_bowls [for i in 1..12 -> 10] )

       [<Test>]

       member x.SpareLastFrame() = Assert.AreEqual( 11, score_bowls ([for i in 1..18 -> 0] @ [2;8;1]) )

       [<Test>]

       member x.StrikeLastFrame() = Assert.AreEqual( 21, score_bowls ([for i in 1..18 -> 0] @ [10;10;1]) )

       [<Test>]

       member x.DoubleStrike() = Assert.AreEqual(33, score_bowls [10;10;1] )

    end

     


    Keith Nicholas
    http://designingcode.blogspot.com
  •  11-08-2007, 20:29 3989 in reply to 3988

    Re: Test Driven F# - The Bowling Game

    another shot... cleaning things up a bit

    #light

     

    open System

    open NUnit.Framework

     

    let score_bowls bowls =

        let rec score_bowls' frame l  =

            let nextframe = score_bowls' (frame+1)

            match l with

            | _ when frame = 11 -> 0

            | [10;s] -> 10 + s + s

            | 10 :: s :: n :: tail -> 10 + s + n + nextframe (s :: n :: tail ) 

            | f :: s :: n :: tail -> f + s + (if((f+s)=10) then n else 0) + nextframe (n :: tail)

            | _ -> List.fold_right (fun x y -> x + y) l 0

        score_bowls' 1 bowls

     

    [<TestFixture>]

    type BowlingTestCases = class

       new() = {}  

       [<Test>]

       member x.SimpleScoring() = Assert.AreEqual(6, score_bowls [1;2;3] )

       [<Test>]

       member x.ScoreSpare() = Assert.AreEqual(12, score_bowls [2;8;1] )

       [<Test>]

       member x.ScoreStrike() = Assert.AreEqual(16, score_bowls [10;1;2] )

       [<Test>]

       member x.ScorePerfectGame() = Assert.AreEqual( 300, score_bowls [for i in 1..12 -> 10] )

       [<Test>]

       member x.SpareLastFrame() = Assert.AreEqual( 11, score_bowls ([for i in 1..18 -> 0] @ [2;8;1]) )

       [<Test>]

       member x.StrikeLastFrame() = Assert.AreEqual( 21, score_bowls ([for i in 1..18 -> 0] @ [10;10;1]) )

       [<Test>]

       member x.DoubleStrike() = Assert.AreEqual(33, score_bowls [10;10;1] )

    end

     


    Keith Nicholas
    http://designingcode.blogspot.com
  •  11-09-2007, 23:02 4009 in reply to 3989

    Re: Test Driven F# - The Bowling Game

    Just if anyones interested...

    a friend tipped me off to another thing...   (fun x y -> x + y)   is the + operator..... obviously enough in retrospect.

    Still not happy with the embedded if statement though.... I don't think its too clear.

    my match basically is

    if we are at the end of the game, dont score anymore frames

    if we have an incomplete strike, score what we can

    if strike, score strike and carry on

    if normal frame, score it, if we got a spare then add on the extra and then carry on

    otherwise, just add up whats there.

     Thing is, without comments I can't see a way of introducing the language too easily.   In C# / C++ you'd introudce an explaining variable / methods.   Here I kind need a new kind of refactoring called "Introduce explaining pattern match"   but I don't think I can do that.  I'm not sure if it would help understanding if I could.  It's still way more concise than any C# / Java / C++ I've seen for the same thing.

    so...the code

    #light

     

    open System

    open NUnit.Framework

     

    let score_bowls bowls =

        let rec score_bowls' frame l  =

            let nextframe = score_bowls' (frame+1)

            match l with

            | _ when frame = 11 -> 0

            | [10;s] -> 10 + s + s

            | 10 :: s :: n :: tail -> 10 + s + n + nextframe (s :: n :: tail ) 

            | f :: s :: n :: tail -> f + s + (if((f+s)=10) then n else 0) + nextframe (n :: tail)

            | _ -> List.fold_right (+) l 0

        score_bowls' 1 bowls

     

    [<TestFixture>]

    type BowlingTestCases = class

       new() = {}  

       [<Test>]

       member x.SimpleScoring() = Assert.AreEqual(6, score_bowls [1;2;3] )

       [<Test>]

       member x.ScoreSpare() = Assert.AreEqual(12, score_bowls [2;8;1] )

       [<Test>]

       member x.ScoreStrike() = Assert.AreEqual(16, score_bowls [10;1;2] )

       [<Test>]

       member x.ScorePerfectGame() = Assert.AreEqual( 300, score_bowls [for i in 1..12 -> 10] )

       [<Test>]

       member x.SpareLastFrame() = Assert.AreEqual( 11, score_bowls ([for i in 1..18 -> 0] @ [2;8;1]) )

       [<Test>]

       member x.StrikeLastFrame() = Assert.AreEqual( 21, score_bowls ([for i in 1..18 -> 0] @ [10;10;1]) )

       [<Test>]

       member x.DoubleStrike() = Assert.AreEqual(33, score_bowls [10;10;1] )

       [<Test>]

       member x.ExampleGame() = Assert.AreEqual(133, score_bowls [1;4;4;5;6;4;5;5;10;0;1;7;3;6;4;10;2;8;6] )

    end

     


    Keith Nicholas
    http://designingcode.blogspot.com
  •  11-11-2007, 21:02 4012 in reply to 4009

    Re: Test Driven F# - The Bowling Game

    If you want to better express the intent of the code then I would probably go with something like I've done below, however, it's just under twice as long as your version so it really comes down to a trade of. Whats more important code size or readability? In this case I'd probably go with code size and just add some comments since the function itself isn't doing anything overly complicated.

    #light

    let (|EndOfGame|IncompleteStrike|Strike|Normal|Other|) (l, frame) =
    match l with
    _ when frame = 11 -> EndOfGame(0)
    | [10;s] -> IncompleteStrike(10 + s + s)
    | 10 :: s :: n :: tail -> Strike(10 + s + n, s :: n :: tail)
    | f :: s :: n :: tail when f + s = 10 -> Normal(f + s + n, n :: tail)
    | f :: s :: n :: tail -> Normal(f + s , n :: tail)
    | ls -> Other(List.fold_left (+) 0 ls)

    let score_bowls bowls =
    let rec score_bowls' frame l current_score =
    let nextframe = score_bowls' (frame+1)
    match (l, frame) with
    EndOfGame(score) -> current_score + score
    | IncompleteStrike(score) -> current_score + score
    | Strike(score, l) -> nextframe l (current_score + score)
    | Normal(score, l) -> nextframe l (current_score + score)
    | Other(score) -> current_score + score
    score_bowls' 1 bowls
  •  11-13-2007, 8:01 4035 in reply to 4009

    Re: Test Driven F# - The Bowling Game

    Thanks a bunch for the examples! I do a lot of test-driven development and I have a project called FsUnit that I could use some input on.

    My project has been up on Google Code for a while now: http://code.google.com/p/fsunit/

    Please check it out if you're interested and let me know what I can add or change. Right now it's just a syntactic wrapper for NUnit but I would like to take this project much further.

    Using FsUnit, you can write assertions like this:



    score_bowls [1;2;3] |> should (equal 6)

    [|"item1"|] |> should (contain "item1")

    true |> should (be True)

View as RSS news feed in XML
Powered by Community Server, by Telligent Systems