5
\$\begingroup\$

This is a functional approach to problem described by standard Bowling Game Kata - to implement the rules of calculating a score in a bowling game. The inspiration was the implementation by Uncle Bob: http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata

The game consists of 10 frames as shown above. In each frame the player has two opportunities to knock down 10 pins. The score for the frame is the total number of pins knocked down, plus bonuses for strikes and spares.

A spare is when the player knocks down all 10 pins in two tries. The bonus for that frame is the number of pins knocked down by the next roll.

A strike is when the player knocks down all 10 pins on his first try. The bonus for that frame is the value of the next two balls rolled.

In the tenth frame a player who rolls a spare or strike is allowed to roll the extra balls to complete the frame. However no more than three balls can be rolled in tenth frame.

Any feedback of my pattern-match-with-recursion implementation f bowling game kata will be greatly appreciated:

import org.scalatest.{FlatSpec, Matchers}
class BowlingGameTest extends FlatSpec with Matchers {
 "sanity test" should "pass" in {
 true shouldBe true
 }
 "gutter game" should "have no points" in {
 val score = BowlingGame.count(Seq.fill(20)(0))
 score shouldBe 0
 }
 "all ones" should "return sum of points" in {
 val score = BowlingGame.count(Seq.fill(20)(1))
 score shouldBe 20
 }
 "spare" should "get bonus from next throw" in {
 val points: Seq[Int] = Seq(5, 5, 8, 0) ++ Seq.fill(16)(0)
 val score = BowlingGame.count(points)
 score shouldBe 26
 }
 "multiple spares" should "get bonus from each next throw" in {
 val points: Seq[Int] = Seq(5, 5, 8, 2, 7) ++ Seq.fill(15)(0)
 val score = BowlingGame.count(points)
 score shouldBe 42
 }
 "strike" should "get bonus points from two next throws" in {
 val points: Seq[Int] = Seq(10, 5, 2, 0) ++ Seq.fill(16)(0)
 val score = BowlingGame.count(points)
 score shouldBe 24
 }
 "multiple strikes" should "get bonus points from two next throws" in {
 val points: Seq[Int] = Seq(10, 10, 5, 2, 0) ++ Seq.fill(15)(0)
 val score = BowlingGame.count(points)
 score shouldBe 49
 }
 "spare in last round" should "get bonus points from a single additional throw" in {
 val points: Seq[Int] = Seq.fill(18)(0) ++ Seq(5, 5, 8)
 val score = BowlingGame.count(points)
 score shouldBe 18
 }
 "strike in last round" should "get bonus points from two additional throws" in {
 val points: Seq[Int] = Seq.fill(18)(0) ++ Seq(10, 5, 3)
 val score = BowlingGame.count(points)
 score shouldBe 18
 }
 "strike in last round and in first additional throw" should "get no bonus points from additional throws" in {
 val points: Seq[Int] = Seq.fill(18)(0) ++ Seq(10, 10, 3)
 val score = BowlingGame.count(points)
 score shouldBe 23
 }
 "maximum game" should "get bonuses in each round" in {
 val score= BowlingGame.count(Seq.fill(12)(10))
 score shouldBe 300
 }
 "partial game" should "return score so far" in {
 BowlingGame.count(Seq(1,2,3)) shouldBe 6
 }
}
object BowlingGame {
 def count(points: Seq[Int]): Int = {
 def countHelper(ps: Seq[Int], score: Int): Int = ps match {
 case Nil => score
 case left :: Nil => score + left
 case lastRoundStrike :: additional1 :: additional2 :: Nil => score + lastRoundStrike + additional1 + additional2
 case strike :: next :: nextnext :: _ if strike == 10 => countHelper(ps.tail, score + strike + next + nextnext)
 case spareL :: spareR :: next :: pss if spareL + spareR == 10 => countHelper(next :: pss, score + spareL + spareR + next)
 case left :: right :: pss => countHelper(pss, score + left + right)
 }
 countHelper(points, 0)
 }
}
200_success
145k22 gold badges190 silver badges478 bronze badges
asked May 6, 2017 at 12:53
\$\endgroup\$
6
  • 1
    \$\begingroup\$ What is the objective of the exercise? Is it available online? \$\endgroup\$ Commented May 6, 2017 at 13:07
  • \$\begingroup\$ A code kata is an exercise in programming which helps a programmer hone their skills through practice and repetition. The term was probably first coined by Dave Thomas, co-author of the book The Pragmatic Programmer,[1] in a bow to the Japanese concept of kata in the martial arts A whole lot of different katas can be found online, e.g. github.com/gamontalvo/awesome-katas \$\endgroup\$ Commented May 6, 2017 at 13:52
  • \$\begingroup\$ Why was this question downvoted? \$\endgroup\$ Commented May 6, 2017 at 13:53
  • \$\begingroup\$ I know what katas are. Show us the kata that promoted you to write this code. \$\endgroup\$ Commented May 6, 2017 at 13:54
  • 1
    \$\begingroup\$ I've also added summary of the rules to the question. \$\endgroup\$ Commented May 6, 2017 at 14:16

1 Answer 1

1
\$\begingroup\$

Some tips:

  1. When pattern matching and inspecting multiple values of a sequence type (Seq, List, Array, etc) you can reduce the length of your expression by using sequence patterns instead of explicitly breaking the sequence up with the :: operator. So instead of a :: b :: c :: d :: Nil you can more concisely use Seq(a, b, c, d).

  2. When you want to check for explicit values while pattern matching on an object you can use those values instead of replacing them with variables that are then checked with an if statement. For example case Seq(a, b, c) if a == 10 => ... can be more concisely expressed as case Seq(10, b, c) => ...

  3. When using pattern matching on sequences (especially when the case expression goes beyond the standard head :: tail) it can enhance legibility to use single letter variable names and comments instead of long variable names. I think that we would both agree that good code should describe itself through both value and function names. However, in this case there is no way of adding intuition with function names and so all of the description ended up in your value names.

    Like I said before, do what works for you. If legibility was our only metric then maybe pattern matching isn't the ideal way to implement your function. It could still be recursive, but instead of cases you could use functions. (削除) Do whatever suits you the best but when possible I would recommend short variable names accompanied by comments over long variable names. (削除ここまで)

Some code:

object BowlingGame {
 def count(points: Seq[Int]): Int = {
 def helper(ps: Seq[Int], acc: Int): Int = ps match {
 case Nil => 
 acc
 case Seq(a) => 
 acc + a
 // strike or spare in last round
 case xs @ Seq(a, b, c) => 
 acc + xs.sum
 // strike
 case Seq(10, a, b, _*) =>
 helper(ps.tail, acc + 10 + a + b)
 // spare
 case Seq(a, b, c, _*) if a + b == 10 =>
 helper(ps.drop(2), acc + a + b + c)
 // neither a spare or a strike
 case Seq(a, b, _*) =>
 helper(ps.drop(2), acc + a + b)
 }
 helper(points, 0)
 }
}
answered May 12, 2017 at 8:12
\$\endgroup\$
2
  • \$\begingroup\$ Thanks for the tips. I however don't really agree with the third one - maybe this is an imperative relic but I feel that the need to comment your code is in most cases a failure to write clean and understandable code (this is clearly an overstatement, I treat this more like an "asymptot"). Any comment on that? \$\endgroup\$ Commented May 12, 2017 at 17:28
  • \$\begingroup\$ @SebastianKramer You have a valid point. I edited number three to address it. \$\endgroup\$ Commented May 12, 2017 at 21:46

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.