I was first introduced to the "Bowling Game" kata/exercise in "Agile Principles, Patterns, and Practices in C#" (by Robert and Micah Martin), which I read more than 10 years ago. Chapter 6 of that book is a 40+ page programming episode presented in dialog between two programmers using TDD to implement an algorithm for scoring a bowling game. Robert Martin has used it to teach TDD ever since. I was drawn to the exercise as a way to begin learning new programming languages or new approaches to testing. Here are a few previous renditions:
- [code]
bowling_test.go
- This rendition illustrates how to use my own Go testing module.
- [video] The "Bowling Game" Kata, in TCR (test && commit || revert)
- In this rendition I was practicing a new technique: TCR
- [video] The "Bowling Game" Kata in TCR using "Functional Fixtures"
- In this rendition I present a new approach to standard-library Go testing using "functional options".
- [code]
bowling.rkt
- A few months ago I dabbled in Racket, a functional programming language descended from Scheme.
I'm currently learning Clojure (a functional language descended from Lisp that runs on the JVM) so I took yet another stab at the "Bowling Game" today:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns bowling.core-spec | |
(:require [speclj.core :refer :all])) | |
(def all-pins 10) | |
(def frames-per-game 10) | |
(defn game-over [frame] (= frame frames-per-game)) | |
(defn strike-score [rolls] (+ all-pins (nth rolls 1) (nth rolls 2))) | |
(defn spare-score [rolls] (+ all-pins (nth rolls 2))) | |
(defn frame-score [rolls] (+ (first rolls) (second rolls))) | |
(defn is-strike [rolls] (= all-pins (first rolls))) | |
(defn is-spare [rolls] (= all-pins (frame-score rolls))) | |
(defn bowl [rolls] | |
(loop [rolls rolls | |
frame 0 | |
score 0] | |
(if (game-over frame) | |
score | |
(cond | |
(is-strike rolls) | |
(recur (rest rolls) | |
(inc frame) | |
(+ score (strike-score rolls))) | |
(is-spare rolls) | |
(recur (rest (rest rolls)) | |
(inc frame) | |
(+ score (spare-score rolls))) | |
:else | |
(recur (rest (rest rolls)) | |
(inc frame) | |
(+ score (frame-score rolls))))))) | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
(defn roll-many [times pins] | |
(take times (cycle [pins]))) | |
(describe "gutter game" | |
(it "scores zero points" | |
(let [gutter-game (roll-many 20 0)] | |
(should= 0 (bowl gutter-game))))) | |
(describe "all ones" | |
(it "scores one point per throw" | |
(let [all-ones (roll-many 20 1)] | |
(should= 20 (bowl all-ones))))) | |
(describe "spare" | |
(it "counts the next roll as a bonus" | |
(let [game (concat [4 6 3 1] (roll-many 16 0))] | |
(should= (+ 4 6 3 3 1) | |
(bowl game))))) | |
(describe "strike" | |
(it "counts the next two rolls as a bonus" | |
(let [game (concat [10 3 4 1] (roll-many 15 0))] | |
(should= (+ 10 3 4 3 4 1) | |
(bowl game))))) | |
(describe "perfection" | |
(it "should score 300" | |
(let [game (roll-many 12 10)] | |
(should= 300 (bowl game))))) |