Just the other day (yesterday) Uncle Bob had a post about Learning Clojure. He implemented a bowling scoring kata. So Dan P. and I decided to try it ourselves, but since we're from Boston we implemented candle pin bowling rules:
As we were talking through designs for this, and as we talked about scoring frames, we realized that there were three cases:
- Strike – score the first ball, plus the next two ‘bonus’ balls, then remove the first ball (the strike) from the list, then continue scoring.
- Spare – score the first two balls, plus the next ‘bonus’ ball, then remove the first two balls (the spare) from the list, then continue scoring.
- No-mark – score all three balls in the frame, then remove them from the list, then continue scoring. (Remember: candlepin!)
(ns scorecard)
(defn third [rolls]
(first (next (next rolls))))
(defn first-two [rolls]
(+ (first rolls) (second rolls)))
(defn strike? [rolls]
(= 10 (first rolls)))
(defn spare? [rolls]
(= 10 (first-two rolls)))
(defn remove-strike [rolls]
(rest rolls))
(defn remove-spare [rolls]
(rest (rest rolls)))
(defn remove-normal-frame [rolls]
(rest (rest (rest rolls))))
(defn remove-frame [rolls]
(if (strike? rolls)
(remove-strike rolls)
(if (spare? rolls)
(remove-spare rolls)
(remove-normal-frame rolls))))
(defn score [input-rolls]
(loop [rolls input-rolls score 0 frame-counter 0]
(if (or (empty? rolls) (= 10 frame-counter))
score
(recur
(remove-frame rolls)
(+ score (first rolls) (second rolls) (third rolls)) (inc frame-counter)))))
And the tests:
(ns scorecard-test
(:use clojure.contrib.test-is
scorecard))
(deftest test-third
(is (= 3 (third [1 2 3 4]))))
(deftest test-first-two
(is (= 3 (first-two [1 2 3]))))
(deftest test-remove-frame
(is (= [3 4 5] (remove-frame [0 1 2 3 4 5])))
(is (= [3 4] (remove-frame [10 3 4])))
(is (= [5] (remove-frame [6 4 5]))))
(deftest test-remvo-two-frames
(is (= []
(remove-frame
(remove-frame [0 1 2 0 0 0])))))
(deftest test-scores
(is (= 0 (score [])))
(is (= 6 (score [1 2 3])))
(is (= 15 (score [1 2 3 4 5 0])))
(is (= 19 (score [10 1 2 3])))
(is (= 17 (score [5 5 1 2 3])))
(is (= 300 (score [10 10 10 10 10 10 10 10 10 10 10 10])))
(is (= 19 (score [5 5 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 ])))
(is (= 21 (score [10 1 1 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 ]))))
Also check out:
Stuart Halloway's implementation. Besides just an implementation, he has a great discussion on how to approach the problem in a functional way. (his implementation is much nicer than this one)
Uncle Bob's son over at 8th light also wrote up an implementation