Wednesday, November 23, 2011

Bowling Kata

I've been working on a lot of Katas past few days. Some of them have been trivial and some more challenging. Generally, before I start on a kata I will watch a kata cast before trying it. This gives me a good idea of where I need to go before starting. However, it doesn't really simulate a real life situation very well. In real life you are given a problem and you have to solve it without any clue what the solution might be. So, before I started the bowling kata I decided not to watch the kata cast or look at any previous solutions. It took me a lot longer to come up with the solution than I thought it would. It was significantly harder to write the algorithm with out knowing the solution ahead of time. However, I think it was very valuable to approach the problem without any preconceptions. My algorithm was very messy. I took in an array of rolls and looped over them to sum up the score. The algorithm was really long with lots of duplicaiton. So I decided to make the problem smaller. I refactored so that I was taking in an array of rolls and returning an array of frames. This refactoring allowed me to stop looping once I have reached the maximum amount of frames, 10. It is also a lot easier to test. I can test special frames (strikes, spares, and the 10th frame) without passing in an entire game. So, through this kata I learn a valuable lesson of continuing to break the problem into smaller chucks. Here's the final product.

(def STRIKE_SCORE 10)
(def SPARE_SCORE 10)
(def MAX_FRAMES 10)
(defn- max-frames? [frames]
(>= (count frames) MAX_FRAMES)
)
(defn- strike? [roll]
(= STRIKE_SCORE roll)
)
(defn- spare? [frame-score]
(= SPARE_SCORE frame-score)
)
(defn- second-roll [rolls]
(if-let [second-roll (second rolls)]
second-roll
0
)
)
(defn- first-roll [rolls]
(if-let [first-roll (first rolls)]
first-roll
0
)
)
(defn- next-two-rolls [rolls]
(+ (first-roll rolls) (second-roll rolls))
)
(defn- get-spare-frame-score [remaining-frames]
(+ SPARE_SCORE (first-roll remaining-frames))
)
(defn- get-strike-frame-score [remaining-frames]
(+ STRIKE_SCORE (next-two-rolls remaining-frames))
)
(defn rolls-to-frames [rolls]
(loop [rolls rolls frames []]
(if (or (empty? rolls) (max-frames? frames))
frames
(let [frame-score (next-two-rolls rolls)]
(cond
(strike? (first-roll rolls))
(let [remaining-rolls (rest rolls)]
(recur remaining-rolls (conj frames (get-strike-frame-score remaining-rolls)))
)
(spare? frame-score)
(let [remaining-rolls (rest (rest rolls))]
(recur remaining-rolls (conj frames (get-spare-frame-score remaining-rolls)))
)
:else
(let [remaining-rolls (rest (rest rolls))]
(recur remaining-rolls (conj frames frame-score))
)
)
)
)
)
)
(defn get-score [rolls]
(apply + (rolls-to-frames rolls))
)
view raw bowling.clj hosted with ❤ by GitHub

No comments:

Post a Comment