Scala Bowling … building on Philip’s approach

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)
  }

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)
      }
   }
}

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)
      }
   }
}

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)
      }
   }
}

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)))
    }
  }
}

Then the Game object:

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)
      }
   }
}

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!

Posted on:

Written by: Ron Jeffries

Categorization: Articles, Practices

3 Responses to “Scala Bowling … building on Philip’s approach”

Philip Schwarz

August 4, 2010

4:32 pm

permalink

Hi Ron,

You said:

“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.”

I much prefer your approach of course, which is the one I used in the original Haskell version. I did it the other way because I didn’t know how to write a guard in Scala. So that’s how you do it… Great, thanks.

Philip Schwarz

August 4, 2010

5:50 pm

permalink

Hi Ron,

just a minor point…

You said: “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.”

Just for the record, I was aware of that simplification, and while on the one hand it makes perfect sense, on the other I felt that it traded understandability for succinctness:
1) While there is a recursive case for normal/spare/strike frames, there are no dedicated base cases for spare and strike. The asymmetry may surprise and slow down the reader.
2) I think it may take the reader a bit longer to map the single base case covering both the spare frame and the strike frame to the common formulation of the scoring rules.

Ron Jeffries

August 5, 2010

6:49 am

permalink

Yes … the simplification actually applies throughout bowling … both a strike and spare always sum the same three rolls. I like to use the example because it is a case where “remove all duplication” and “express all ideas” seem to collide. Makes for fun discussions during demos.

Note however that Jon has found test cases that the scheme I wound up with does not pass. I believe yours is subject to them as well.

Recent Articles

Scrum Information Base — Agile Skills

Chet Hendrickson and I have offered to help the Scrum Alliance build a broad and growing base of information relating to Scrum / Agile, and to the many skills and practices that can help teams be successful. Our offer has…

Why Did You Do That, Ron?

Piers Thompson sent me a good question about my recent database articles. I suspect others would like to hear the question and answers.

Events & Classes

  • No events.