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