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.
readers
Long ago I had some fun with “The Bowling Game” including one or two Clean implementations.
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:
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) ~?= 30In “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.
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 writex:(y:rest); justx:y:restwill do. Second, the otherwise guard on the final case isn’t needed because there’s no other guard for that case.Cheers,
Tom
Thanks for the suggestions, now that I am looking more at functional languages again I’ll keep an eye on your blog.