Articles Feed

Authors

Categories

Bowling in Clojure

by: micah | July 20th, 2009 |

I’ve been long overdue to learn a new programming language and Clojure recently caught my eye. Clojure attracted my attention because it seemed like a rich Lisp dialect where I could learn all the goodness of functional programming, and it runs on the JVM allowing me to make use of all hte Java goodness out there.

Along with a couple colleagues at 8th Light, I read Halloway’s Book. We then challenged each other to write the bowling game in Clojure so we could compare our solutions. Coincidentally, my father, Unclebob, performed the same exercise independently. Below is my solution.

First comes the tests:

  1. (ns micah.test.bowling (:use micah.bowling clojure.contrib.test-is))
  2. (deftest test-gutter-game
  3. (is (= 0 (score (repeat 20 0)))))
  4. (deftest test-one-pin-wonder
  5. (is (= 20 (score (repeat 20 1)))))
  6. (deftest test-one-spare
  7. (is (= 20 (score (concat (list 5 5 5) (repeat 17 0))))))
  8. (deftest test-one-strike
  9. (is (= 30 (score (cons 10 (repeat 18 1))))))
  10. (deftest test-perfect-game
  11. (is (= 300 (score (repeat 12 10)))))
  12. (deftest test-all-spares
  13. (is (= 150 (score (repeat 21 5)))))
  14. (deftest test-heart-breaker
  15. (is (= 299 (score (concat (repeat 11 10) (list 9))))))
  16. (run-tests)

And the production code:

  1. (ns micah.bowling (:use clojure.contrib.seq-utils))
  2. (defn score [rolls]
  3. (loop [score 0 rolls (vec rolls) frame 1]
  4. (if (> frame 10)
  5. score
  6. (cond
  7. (= 10 (rolls 0))
  8. (recur (+ score 10 (rolls 1) (rolls 2)) (subvec rolls 1) (inc frame))
  9. (= 10 (+ (rolls 0) (rolls 1)))
  10. (recur (+ score 10 (rolls 2)) (subvec rolls 2) (inc frame))
  11. :else
  12. (recur (+ score (rolls 0) (rolls 1)) (subvec rolls 2) (inc frame))))))

Thoughts

As with learing any new language, writing this first program was frustrating. Getting the environment running took a while and simple tasks, like creating a list of 20 zeros, required several minutes of research. Nonetheless, I was very pleased when it was complete.

I was quite impressed with size of the program. Compared to previous Java solutions I’ve written, this Clojure solution is about 20% the size. That’s a huge size reduction. But as I compare the solutions more, it occurrs to me that the Clojure solution is also about 20% as readable. Just look at it! You could get lost in that cond statement for eons.

Now my craftsmanship spirit was nagging me all the while I was coding, just begging me to refactor. However, I struggled to find good ways to improve the code. All my typical tricks, extract variable, extract method, didn’t feel right. My hope is that I’ll acquire new tricks to refactor in Clojure from experienced Clojure programmers, perhaps like you. Please let me know if you see a way to improve my code.

2 Responses to “Bowling in Clojure”

  1. Stuart Halloway Says:

    Hi Micah, hope you liked the book. I have written up my approach to the bowling game here: http://blog.runcoderun.com/post/145675117/tdd-in-a-functional-language-uncle-bobs-bowling.

  2. Don Wells Says:

    First and formost let go of the state. Instead of counting to 10 frames how would you create 10 frames by applying a function to a raw list of rolls? Then if you had a list of frames what function could you apply to add them together?

    cond is bread and butter in Lisp, but it doesn't have to be unreadable. Use functions instead of raw calculations (extract method becomes extract function).

    In terms of TDD I would probably start with one frame at a time. Do I have functions which recognize each frame type? Do I have a function which creates a frame from a list of rolls based on type? When I have tested those functions into existance I would move on to multi-frames. I must finally test that I only score 10 frames per game.

Leave a Reply

#