Thomas Skardal

Hello, computer.

Long time, no see. Here's some Clojure!

June 22, 2015

Recently I got inspired by this blog post (it’s in norwegian, but I’m sure you can find a way to translate it) which solves the bowling kata in Haskell. The goal is to implement a function that gives you a score for a series of rolls in a bowling game.

This blog post mentions other posts as well, and all of the solutions are worth a read.

So, back to me. I wanted to give it a try in Clojure, and here’s the result!

(ns kata-bowling.bowling)

(defn- last-frame? [x y z]
  (some nil? [x y z]))

(defn- spare? [x y]
  (= (+ x y) 10))

(defn- strike? [x]
  (= x 10))

(defn score [[x y z & rolls]]  
  (cond
    (last-frame? x y z) (+ (take-while some? [x y z]))
    (spare? x y) (+ 10 z (score (cons z rolls)))
    (strike? x) (+ 10 y z (if (empty? rolls) 0 (score (concat [y z] rolls))))
    :else (+ x y (score (cons z rolls)))))

I’ve implemented this as a recursive function. Not tail recursive, just plain old recursive. I use cond to “match” the different kinds of throws. I’m sure I could make the code shorter and more consice by using something like [core.check](), but I wanted to use vanilla Clojure. If that’s a thing.

What I like about this solution is that it’s quite clear how a strike is better than a spare and how this again is better than a regular frame. I don’t like is how I have to cons and concat the following rolls back into the collection before recursing.

Any feedback is most welcome!

Finally, here are the tests I wrote to feel confident that the solution is correct.

(ns kata-bowling.bowling-test
  (:require [clojure.test :refer :all]
            [kata-bowling.bowling :refer :all]))

(deftest bowling
  (testing "everything in the gutter"
    (is (= 0 (score (repeat 20 0)))))
  (testing "one single pin"
    (is (= 1 (score (cons 1 (repeat 19 0))))))
  (testing "a spare gives ten plus the number of pins in next throw"
    (is (= (+ 10 5 5) (score [5 5 5]))))
  (testing "a strike gives ten plus the number of pins in the two next throws"
    (is (= (+ 10 3 1 2 5) (score [10 1 2 5]))))
  (testing "no spares or strikes"
    (is (= 90 (score (take 20 (interpose 0 (repeat 9)))))))
  (testing "all spares"
    (is (= 150 (score (repeat 21 5)))))
  (testing "a perfect game"
    (is (= 300 (score (repeat 12 10))))))

(also available as a gist)