The Bowling Game Kata in Haskell

Posted by Tom Moertel Wed, 05 Apr 2006 19:18:00 GMT

On the WPLUG mailing list I came across a post about the formation of a Pittsburgh Coding Dojo. The idea is to get a bunch of hackers together and have them work on solving a challenge problem with the goal of sharpening their programming skills and learning from each other.

There was a trial meeting on 31 March that focused on The Bowling Game Kata. The challenge was essentially to write some code that scores a full (ten-frame) game of bowling. A game is represented by a series of “rolls,” each being the number of pins knocked down by a roll of the bowling ball. The scoring function must determine frame boundaries from the sequence of rolls and score all ten frames according to the rules of bowling, i.e., taking into account spares and strikes and the final frame.

The challenge sounded like a fun lunch-break problem, and so I whipped up the following solution in Haskell. (You might find it interesting to compare this solution to the Java-based solutions on the web.)

{-
   My solution to "The Bowling Game Kata" 
   Tom Moertel <tom@moertel.com>
   2006-04-05

   See http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata
-}

module Bowling (score) where

import Test.HUnit

-- | Compute the score for the list of rolls 'rs'

score rs = sc 0 1 rs

-- accumulate the score 's' and frame count 'f' while consuming a
-- list of rolls 'rs' one frame at a time

sc s 11 _  = s           -- frame 11 means all done; return score
sc s f rs  = case rs of  -- otherwise, consume the frame & recurse
    10:rs'                -> sc' 3 rs'  -- strike
    x:y:rs' | x + y == 10 -> sc' 3 rs'  -- spare
            | otherwise   -> sc' 2 rs'  -- normal
    _                     -> error "ill-formed sequence of rolls"
  where
    -- accumulate the next 'n' rolls into the score and recurse
    sc' n rs' = sc (s + sum (take n rs)) (f + 1) rs'

Here are my unit tests:

{-
                      *** Unit tests ***

             *Bowling> runTestTT tests
             Cases: 9  Tried: 9  Errors: 0  Failures: 0
-}

tests = test
    [ "gutters"       ~: score  (rep 20  0)          ~?=   0
    , "ones"          ~: score  (rep 20  1)          ~?=  20
    , "fives"         ~: score  (rep 22  5)          ~?= 150
    , "strikes"       ~: score  (rep 12 10)          ~?= 300
    , "1 + gutters"   ~: score  (1 : rep 19 0)       ~?=   1
    , "first spare"   ~: score  (5:5:5 : rep 17 0)   ~?=  20
    , "first strike"  ~: score  (10:5:5 : rep 17 0)  ~?=  30
    , "last spare"    ~: rscore (5:5:5 : rep 18 0)   ~?=  15
    , "last strike"   ~: rscore (5:5:10 : rep 18 0)  ~?=  20
    ]
  where
    rep    = replicate
    rscore = score . reverse  -- reverse list and then score it

If you have a little free time, code up a solution in your favorite language.

Posted in , ,
Tags , ,
4 comments
no trackbacks
Reddit Delicious

Comments

  1. Isaac Gouy said 7 days later:

    Long ago I had some fun with “The Bowling Game” including one or two Clean implementations.

  2. Philip Schwarz said 1112 days later:

    Hi Tom,

    I wanted to get some practice in Haskell, so I thought I’d look for some Kata, and since I had recently been going over Robert Martin’s Java Bowling Kata, I said to myself, what the hell, why not try the Bowling Kata in Haskell?...but how can I do TDD without XUnit? Let me just Google that just in case someone has already tried it, and sure enough, you had. Thanks for your post. I was pleasantly surprised to see that there is such a thing as HUnit.

    Here is my attempt:

    score [x, y]                             = x + y      -- Normal Frame
    score [10, x, y]                         = 10 + x + y -- Strike
    score [x, y, z]                          = 10 + z     -- Spare
    score (10:(x:(y:rest)))                  = 10 + x + y + score (x:(y:rest)) -- Strike
    score (x:(y:(z:rest)))  | (x + y) == 10  = 10 + z + score (z:rest)         -- Spare
    score (x:(y:rest))      | otherwise      = x + y + score rest              -- Normal Frame
    

    What do you think? I think it should be possible to simplify this further (say from 6 lines down to 5 or 4), but I haven’t got there yet.

    It passes all your tests, except for ‘fives’ and ‘first strike’, whose frames need (IMHO) to be corrected to the following respectively:

        , "fives"         ~: score  (rep 21  5)          ~?= 150
        , "first strike"  ~: score  (10:5:5 : rep 16 0)  ~?=  30
    

    In “fives”, I have taken away the 22nd roll, because you cannot have more than 21 rolls in a game. In “first strike”, I have removed the 17 gutter because after the strike (1 roll) and the spare (2 rolls), there are 8 normal frames, and therefore 8×2=16 rolls.

  3. Tom Moertel said 1112 days later:

    Philip, I believe you are right about my “fives” and “first strike” tests: they are incorrect. Luckily, my code passes the corrected tests, too. Whew!

    Your code looks pretty good. I’ll offer a couple of stylistic suggestions, though. First, the “cons” operator (:) is right associative, so you don’t need to write x:(y:rest); just x:y:rest will do. Second, the otherwise guard on the final case isn’t needed because there’s no other guard for that case.

    Cheers,
    Tom

  4. Philip Schwarz said 1112 days later:

    Thanks for the suggestions, now that I am looking more at functional languages again I’ll keep an eye on your blog.

Trackbacks

Use the following link to trackback from your own site:
http://blog.moertel.com/articles/trackback/61

(leave url/email »)

   Comment Markup Help Preview comment