Philip Schwarz provided a nice-looking implementation. Let's look at it and try to build on his ideas.

Philip offered this version in a comment on an earlier article:

def score(rolls : List[Int]) : Int = rolls match
  {
  case List(x,y) => x + y
  case List(10,x,y) => 10 + x + y
  case List(x,y,z) => 10 + z
  case (10::x::y::rest) => 10 + x + y + score(x::y::rest)
  case (x::y::z::rest) => if((x + y) == 10)
    10 + z + score(z::rest)
    else x + y + score(z::rest)
  }

</code>

Let’s see what he did. First of all, instead of that silly idea of including the total on the front of the list, he just wrote the recursion to return the total directly. That was boneheaded on my part. I can only plead youthful inexperience and ask for mercy based on my long years of service.

First, I’ll fix that in my version:

class Game(rolls: List[Int]) {
   def score = scoreReduce(rolls)

   def scoreReduce(rolls: List[Int]):Int = {
      rolls match {
           case Nil => 0

           case 10::second::third::Nil
               => 10+second+third

           case first::second::third::Nil if (first+second==10)
                => first+second+third

           case 10::second::third::theRest
              => 10+second+third + scoreReduce(second::third::theRest)

           case first::second::third::theRest if (first + second == 10)
                => 10+third + scoreReduce(third::theRest)

           case first::second::theRest
                 => first+second + scoreReduce(theRest)
      }
   }
}

</code>

That works nicely. We notice that Philip coded things like x :: y :: Nil as List(x,y). That’s more clear. Let’s do that:

class Game(rolls: List[Int]) {
   def score = scoreReduce(rolls)

   def scoreReduce(rolls: List[Int]):Int = {
      rolls match {
           case Nil => 0

           case List(10, second, third)
               => 10+second+third

           case List(first, second, third) if (first+second==10)
                => first+second+third

           case 10::second::third::theRest
              => 10+second+third + scoreReduce(second::third::theRest)

           case first::second::third::theRest if (first + second == 10)
                => 10+third + scoreReduce(third::theRest)

           case first::second::theRest
                 => first+second + scoreReduce(theRest)
      }
   }
}

</code>

This works. Now notice that Philip doesn’t have quite the same cases that I have. I’m unwinding all the way down to an empty list. I’m handling the case of the last frame having three elements, but not the case of it having only two. Philip added in the case of two elements, which terminates the recursion, so he doesn’t have to deal with Nil. We can showthat as part of the next display (I’ve done it separately but will save you reading just that one bit.)

First, look at both his version and mine. I’ve highlighted the bit of interest in my version above. Philip gave us the trick of representing the end cases explicitly. The game always ends either with a frame of two, or a frame of three which is either a strike (10 :: first :: second) or a spare. We note, however, that the answer returned in either case is the sum of the three balls in the frame. Let’s do that, then look at the code again:

class Game(rolls: List[Int]) {
   def score = scoreReduce(rolls)

   def scoreReduce(rolls: List[Int]):Int = {
      rolls match {
           case List(first, second) => first+second

           case List(first, second, third) => first+second+third

           case 10::second::third::theRest
              => 10+second+third + scoreReduce(second::third::theRest)

           case first::second::third::theRest if (first + second == 10)
                => 10+third + scoreReduce(third::theRest)

           case first::second::theRest
                 => first+second + scoreReduce(theRest)
      }
   }
}

</code>

Now Philip handled things differently at the bottom of the match statement. I broke out strike, spare, and open frame into three cases. He broke out strike, then folded the last two of my cases into one, with an else. I think I’ll leave it my way.

Now there’s one more thing to do. Since the function scoreReduce doesn’t use my dim format with the total on the front, it no longer needs to be an auxiliary function, so we can let it be the score function directly. That’s a bit tricky, though, because I built Game as a class, with rolls inside it. The way things are set up now, we almost might as well move the whole function score into our test class. I’ll not go that far: I want a solution that stands alone from the tests.

Instead, I’ll change Game to an object with just one method, score. That makes things turn out like this. First the tests:

import org.scalatest.Spec

class ExampleSpec extends Spec {

  describe("A Bowling Game") {

    it("should score all gutters as zero") {
    	expect(0)(Game.score(List(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)))
    }

    it("should score all 5,4 as 90") {
    	expect(90) (Game.score(List(5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4)))
    }

    it("should score spare game 6, 4, 5, 2 as 22") {
    	expect(22)(Game.score(List(6,4,5,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)))
    }

    it("should score strike game 10, 5, 2, 7 as 31") {
    	expect(31)(Game.score(List(10,5,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)))
    }

    it("should score perfect game as 300" ) {
    	expect(300)(Game.score(List(10,10,10,10,10,10,10,10,10,10,10,10)))
    }
  }
}

</code>

Then the Game object:

</code>

object Game {
   def score(rolls: List[Int]):Int = {
      rolls match {
           case List(first, second) => first+second

           case List(first, second, third) => first+second+third

           case 10::second::third::theRest
              => 10+second+third + score(second::third::theRest)

           case first::second::third::theRest if (first + second == 10)
                => 10+third + score(third::theRest)

           case first::second::theRest
                 => first+second + score(theRest)
      }
   }
}

</code>

OK. That's probably more like it. I expect that this is in the range of tolerance for a functional solution, with people perhaps objecting to notation and having better ideas. I'll be very interested to see if something substantially different comes up.

Go for it!