The Bowling Kata in Clojure

I have bee reading Programming Clojure lately to learn about functional programming. Interest in functional programming is increasing so it might be a good idea to get familiar with it. :-)

Clojure is a Lisp like programming language that runs on the Java VM and also on the .NET CLR.

I didn’t have much knowledge about functional programming before and the book still left me with a lot of questions. After practicing OOP for most of my programming life, Clojure is very strange at first.

To get a little bit practice at it I spend a couple of hours (reading documentation, playing with the REPL) tdd’ing the Bowling Kata in Clojure.

There are already a few versions of the Clojure Bowling Kata on the net: Uncle Bob, Micah, Halloway. There is a lot of info in the comments of the first article and the third link points to a version from the author of the Clojure book mentioned above.

Here is mine :-)

I didn’t read the links in detail before I started because the idea was to do this on my own. So I guess there is still some potential for improvements.

If you are looking for an environment, you may try Intellij IDEA’s Community Edition and their Clojure plugin La-Clojure. That’s what I have used.

Unfortunately WordPress’s [sourcode] tag doesn’t seem to know Clojure yet. So no highlighting.

The tests:

(ns test
  (:use
    bowling
    clojure.contrib.test-is))

(defn- zero
  ([] (repeat 2 0))
  ([n] (repeat (* n 2) 0)))

(defn- spare
  ([] (repeat 2 5))
  ([n] (repeat (* n 2) 5)))

(deftest the-score-of-a-gutter-game-is-zero
  (is (= 0 (score (zero 10)))))

(deftest the-score-of-one-rolls-only-is-twenty
  (is (= 20 (score (repeat 20 1)))))

(deftest the-score-of-a-spare-includes-the-next-roll
  (is (= 16 (score (concat (spare) '(3 0)  (zero 8)))))
  (is (= 16 (score (concat (zero)   (spare) '(3 0) (zero 7)))))
  (is (= 16 (score (concat (zero 2) (spare) '(3 0) (zero 6)))))
  )

(deftest two-spares-in-a-row
  (is (= 31 (score (concat (spare 2) '(3 0) (zero 7))))))

(deftest three-spares-in-a-row
  (is (= 46 (score (concat (spare 3) '(3 0) (zero 6))))))

(deftest the-score-of-a-strike-includes-the-next-two-rolls
  (is (= 24 (score (concat '(10) '(3 4) (zero 8))))))

(deftest spare-in-last-frame
  (is (= 16 (score (concat (zero 9) (spare) '(3))))))

(deftest strike-in-last-frame
  (is (= 17 (score (concat (zero 9) '(10) '(3 4))))))

(deftest the-score-of-a-perfect-game-is-300
  (is (= 300 (score (repeat 12 10)))))

(run-tests)

and the code:

(ns bowling)

(defn- sum [rolls]
  (reduce + rolls))

(defn- spare? [rolls]
  (= 10 (sum (take 2 rolls))))

(defn- strike? [rolls]
  (= 10 (first rolls)))

(defn- more? [rolls]
  (seq rolls))

(defn- score-strike [rolls]
  (sum (take 3 rolls)))

(defn- score-spare [rolls]
  (sum (take 3 rolls)))

(defn- score-frame [rolls]
  (sum (take 2 rolls)))

(def score)

(defn- score-after-strike [rolls]
  (score
    (if (= 3 (count rolls))
      (drop 3 rolls)
      (drop 1 rolls)
    )))

(defn- score-after-frame [rolls]
  (score (drop 2 rolls)))

(defn score
  "calculate the bowling score of a [rolls] sequence"
  [rolls]
  (cond
    (empty? rolls)     0
    (strike? rolls)    (+ (score-strike rolls) (score-after-strike rolls))
    (spare?  rolls)    (+ (score-spare rolls)  (score-after-frame rolls))
    (more?   rolls)    (+ (score-frame rolls)  (score-after-frame rolls))
    )
  )

A few notes:

The last test I added (the-score-of-a-perfect-game-is-300) made me insert that strange if to (score-after-strike). This didn’t happen on the Java version. Because there is no loop that counts the frames in the Clojure version, one has to detect the last frame some other way. Without it, the last two strikes would not only be counted as the bonus for the 10th strike but also as an 11th and 12th frame.

I think my solution is not too bad for my first piece of Clojure code.

What I dislike is that every call in the (score) function has the rolls sequence as parameter. That is a lot of duplication noise. I see an object here ;-)

In the test I’m not sure if the (spare) and (zero) helper methods improve the readability. Maybe simply writing out every single roll would be easier to understand.

What do you think of my Clojure Bowling Kata? Is there anything that’s totally not the Clojure way? ;)

Update (10.2.2010)

After some feedback on the clojure group, I fixed the forward declaration of the (score) function. It should use (declare) and instead of (def) and I modified the score function to use (condp) with (apply). That is a small trick to avoid the rolls parameter on the predicate functions (empty? etc.). From a readability point, I think I still prefer the simple (cond) from my first version.

(ns bowling)

(defn- sum [rolls]
  (reduce + rolls))

(defn- spare? [rolls]
  (= 10 (sum (take 2 rolls))))

(defn- strike? [rolls]
  (= 10 (first rolls)))

(defn- more? [rolls]
  (seq rolls))

(defn- score-strike [rolls]
  (sum (take 3 rolls)))

(defn- score-spare [rolls]
  (sum (take 3 rolls)))

(defn- score-frame [rolls]
  (sum (take 2 rolls)))

(declare score)

(defn- score-after-strike [rolls]
  (score
    (if (= 3 (count rolls))
      (drop 3 rolls)
      (drop 1 rolls)
    )))

(defn- score-after-frame [rolls]
  (score (drop 2 rolls)))

(defn score
  "calculate the bowling score of a [rolls] sequence"
  [rolls]
  (condp apply [rolls]
    empty?   0
    strike?  (+ (score-strike rolls) (score-after-strike rolls))
    spare?   (+ (score-spare rolls)  (score-after-frame rolls))
    more?    (+ (score-frame rolls)  (score-after-frame rolls))
    )
  )

2 thoughts on “The Bowling Kata in Clojure

  1. Hi, this probably stands against anything the kata is trying to teach, but I came up with a short function for score.

    1) Each frame score can be done in 2 steps
    a) get the sum of 2 rolls
    b) if that sum is >= 10 (i.e. it was a strike or a spare), then add the 3rd
    3) Sequence advances by 1 if there was a strike, 2 otherwise

    (defn score
    “calculate the bowling score of a [rolls] sequence”
    [ rolls ]
    (loop [ rolls (seq rolls) frame 1 score 0]
    (if (> frame 10) score
    (let [s12 (reduce + (take 2 rolls))
    fscore (if (>= s12 10) (+ s12 (nth rolls 2)) s12)]
    (recur (drop (if (= 10 (first rolls)) 1 2) rolls) (inc frame) (+ score fscore))))))

    and the 5 test functions
    (and
    (= 0 (score (repeat 0)))
    (= 20 (score (repeat 1)))
    (= 16 (score (list* 5 5 3 (repeat 0))))
    (= 24 (score (list* 10 3 4 (repeat 0))))
    (= 300 (score (repeat 10))))

  2. I *think* that you could use a binding to make the rolls implicit in those calls. But I don’t know whether that’s considered good style / idiomatic Clojure. Still learning..

Leave a comment