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
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)