Tuesday, July 21, 2009

Clojure + Bowling Kata

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:

  1. Strike – score the first ball, plus the next two ‘bonus’ balls, then remove the first ball (the strike) from the list, then continue scoring.
  2. 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.
  3. No-mark – score all three balls in the frame, then remove them from the list, then continue scoring. (Remember: candlepin!)
In all these cases you want to score three balls, the only difference was in how many balls you remove from the list before you continue scoring (i.e., how many balls were in the frame you are removing from the yet-to-be-scored list). So this is what we came up with: http://github.com/zdsbs/candlepin-bowling/tree/master
(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

No comments:

 
Web Statistics