The Bowling Game Kata in Scala

Here is the end result of my implementation of the Bowling Game Kata in Scala. You can see all steps including the tests on CodersDojo.org.

class BowlingGame {

  var rolls: List[Int] = List()

  def roll(pins: Int): Unit = {
    rolls = rolls :+ pins
  }

  def score: Int = {
    scoreRecursive(0, 1, rolls)
  }

  def scoreRecursive(currentScore: Int, frame: Int, rolls: List[Int]): Int = {
    frame match {
      case 11 => currentScore
      case f => rolls match {
        case 10 :: rollsTail // strike
          => scoreRecursive(currentScore + (rolls take (3) sum), f + 1, rollsTail)
        case first :: second :: rollsTail if (first + second == 10) // spare
          => scoreRecursive(currentScore + (rolls take (3) sum), f + 1, rollsTail)
        case first :: second :: rollsTail // normal
          => scoreRecursive(currentScore + (rolls take (2) sum), f + 1, rollsTail)
        case _ // incomplete game
          => throw new Exception("Only complete games can be scored.")
      }
    }
  }
}

Comparing this piece of code with the Java implementation from Uncle Bob (who, no doubt, knows how to write clean Java code) I would conclude that it is more expressive and concise. The pattern matching is a) shorter and b) makes the algorithm stand out more clearly. I do, however, appreciate that this might have to do with personal taste. But, what this implementation with pattern matching does make trivial is to also score partial games. It just requires two more lines of code that seem almost obvious.

class BowlingGame {

  var rolls: List[Int] = List()

  def roll(pins: Int): Unit = {
    rolls = rolls :+ pins
  }

  def score: Int = {
    scoreRecursive(0, 1, rolls)
  }

  def scoreRecursive(currentScore: Int, frame: Int, rolls: List[Int]): Int = {
    frame match {
      case 11 => currentScore
      case f => rolls match {
        case 10 :: rollsTail // strike
          => scoreRecursive(currentScore + (rolls take (3) sum), f + 1, rollsTail)
        case first :: second :: rollsTail if (first + second == 10) // spare
          => scoreRecursive(currentScore + (rolls take (3) sum), f + 1, rollsTail)
        case first :: second :: rollsTail // normal
          => scoreRecursive(currentScore + (rolls take (2) sum), f + 1, rollsTail)
        case last :: rollsTail // partial frame
          => currentScore + last
        case nil // partial game
          => currentScore
      }
    }
  }
}

As I described in my previous post, the variable rolls might indicate that the above code could be made more functional by replacing it with some other construct like list comprehension or recursion. However, thinking about it, it sort of makes sense since the rolls are indeed what changes as a game progresses. I couldn’t come up with a more functional implementation that is not less expressive. Can you?

Lukasz took me up on the challenge and got rid of the nested match. I like it. I like it a lot. It makes the code both more expressive and concise.

class BowlingGameTailRecursion {

  var rolls: List[Int] = List()

  def roll(pins : Int) = rolls = rolls :+ pins
  def score = scoreRecursive(0, 1, rolls)

  def scoreRecursive(score: Int, frame: Int, rolls: List[Int]) : Int = {
    (frame, rolls) match {
      case (11, _) => score
      case (_, 10 :: tail)
        => scoreRecursive(score + 10 + tail.take(2).sum, frame + 1, tail)
      case (_, first :: second :: tail) if first + second == 10
        => scoreRecursive(score + 10 + tail.head, frame + 1, tail)
      case (_, first :: second :: tail)
        => scoreRecursive(score + first + second, frame + 1, tail)
      case (_, first :: tail)
        => scoreRecursive(score + first, frame + 1, tail)
      case _ => score
    }
  }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s