One of my favorite simple code katas with functional languages is to tally up bowling scores. Previously, I had done this with Erlang and Scala. Here is a Clojure version of the same including some tests:
(defn score-frame [rolls]
(let [[x1 x2 x3] rolls]
(cond
(nil? x1) nil
(nil? x2) nil
(nil? x3) (let [sumx (+ x1 x2)]
(if (< sumx 10) (list sumx))) ; open at end of game
(= x1 10) (cons (+ x1 x2 x3) (rest rolls)) ; strike
(= (+ x1 x2) 10) (cons (+ x1 x2 x3) (nthrest rolls 2)) ; spare
(< (+ x1 x2) 10) (cons (+ x1 x2) (nthrest rolls 2)) ; open
(true) nil
)
)
)
(defn roll-reduce [remaining-rolls]
(if (not (= nil remaining-rolls))
(let [result (score-frame remaining-rolls)]
(if (not (= nil result))
(cons (first result) (roll-reduce (rest result))))))
)
(defn score-ten-frames [rolls]
(take 10 (roll-reduce rolls)))
(defn tally-game [accum scores]
(if (> (count scores) 0) (let [new_accum (+ accum (first scores))] (cons new_accum (tally-game new_accum (rest scores)))))
)
(defn score-game [rolls]
(tally-game 0 (score-ten-frames rolls))
)
(assert (= '(30 60 90 120 150 180 210 240 270 300) (score-game '(10 10 10 10 10 10 10 10 10 10 10 10))))
(assert (= '(30 60 90 120 150 180 210 240 270 299) (score-game '(10 10 10 10 10 10 10 10 10 10 10 9))))
(assert (= '(30 60 90 120 150 180 210 240 270 290) (score-game '(10 10 10 10 10 10 10 10 10 10 10 0))))
(assert (= '(20 50 80 110 140 170 200 230 260 290) (score-game '(9 1 10 10 10 10 10 10 10 10 10 10 10))))
(assert (= '(30 60 90 120 150 180 210 240 269 289) (score-game '(10 10 10 10 10 10 10 10 10 10 9 1))))
(assert (= '(30 60 90 120 150 180 210 240 269 288) (score-game '(10 10 10 10 10 10 10 10 10 10 9 0))))
(assert (= '(30 60 90 120 150 180 210 240 260 280) (score-game '(10 10 10 10 10 10 10 10 10 10 0 10))))
(assert (= '(20 40 70 100 130 160 190 220 250 280) (score-game '(10 9 1 10 10 10 10 10 10 10 10 10 10))))
(assert (= '(30 60 90 120 150 180 210 239 259 279) (score-game '(10 10 10 10 10 10 10 10 10 9 1 10))))
(assert (= '(30 60 90 120 150 180 210 240 260 270) (score-game '(10 10 10 10 10 10 10 10 10 10 0 0))))
(assert (= '(20 40 60 80 100 120 140 160 180 200) (score-game '(10 9 1 10 9 1 10 9 1 10 9 1 10 9 1 10))))
(assert (= '(19 38 57 76 95 114 133 152 171 190) (score-game '(9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 1 9))))
(assert (= '(19 38 57 76 95 114 133 152 171 180) (score-game '(9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 0))))
(assert (= '(0 0 0 0 0 0 0 0 0 0) (score-ten-frames '(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))))
; Partial games
(assert (= nil (score-game nil)))
(assert (= nil (score-game '(0))))
(assert (= nil (score-game '(9))))
(assert (= nil (score-game '(10))))
(assert (= nil (score-game '(9 1))))
(assert (= nil (score-game '(10 10))))
(assert (= '(0) (score-game '(0 0))))
(assert (= '(1) (score-game '(0 1))))
(assert (= '(9) (score-game '(8 1))))
(assert (= '(9) (score-game '(9 0))))
(assert (= '(19) (score-game '(9 1 9))))
(assert (= '(19 28) (score-game '(10 9 0))))
(assert (= '(19 28) (score-game '(9 1 9 0))))
(assert (= '(20) (score-game '(10 9 1))))
(assert (= '(30) (score-game '(10 10 10))))
(assert (= '(30 59) (score-game '(10 10 10 9))))
(assert (= '(30 59 78 87) (score-game '(10 10 10 9 0))))
Note this code has been updated since initial posting to incorporate changes based on feedback in a comment from Mark Volkmann below. Mark has published an excellent overview of Clojure here, http://www.ociweb.com/jnb/jnbMar2009.html
Example:
C:\lang\clojure>java -jar clojure.jar bowling.clj
Clojure
user=> (score-game '(10 10 10 10 10 10 10 10 10 10 10 10))
(30 60 90 120 150 180 210 240 270 300)
user=> (score-game '(10 10 10 10 10 10 10 10 10 10 10 9))
(30 60 90 120 150 180 210 240 270 299)
user=> (score-game '(10 10 10 10 10 10 10 10 10 10 9 1))
(30 60 90 120 150 180 210 240 269 289)
user=> (score-game '(10 10 10 10 10 10 10 10 10 9 1 10))
(30 60 90 120 150 180 210 239 259 279)
user=> (score-game '(10 10 10 10 10 10 10 10 10 9 0))
(30 60 90 120 150 180 210 239 258 267)
user=> (score-game '(9 0 10 10 10 10 10 10 10 10 10 10 10))
(9 39 69 99 129 159 189 219 249 279)
