Scala Bowling, I think …

I’ve been working with Scala a bit, just to learn what it is. I’ve found it interesting, if frustrating. Here is a bowling experiment.

The bowling problem that Chet and I use for TDD demonstrations is somewhat different from the one that Bob Martin uses. Ours can be stated: “Given a list of rolls of a legal game of bowling, compute the final score of the game.” This turns out to be just big enough to be interesting and to be something we can do in about an hour, with plenty of discussion and questions.

I wrote this one in full TDD style, but I will present here only the final version, with some discussion. Mostly I spent the early part of the project trying to learn the Scala language and tools, and trying to work in what I hope is a Scala-like style. I’ll leave the article open for comments in case you’d like to advise me otherwise.

First, the Tests

Here are the tests, written in ScalaTest Spec format. I kind of like that form, as it means I don’t have to make up really weird test method names.

import org.scalatest.Spec

class ExampleSpec extends Spec {

  describe("A Bowling Game") {

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

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

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

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

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

As always, these are shown in the order created.  Nothing special here, I just create a game and expect its score. I did, of course, work one test at a time, making each one work before creating a new one.

I had a design in mind, as always. I think it is neither desirable, nor quite likely even possible, to work with no design in mind. The trick is to make no steps in the direction of the design until the code calls for them. You’ll have to take my word for it that I accomplished that. The point here is just to show you the result, which I think is interesting.

The design I had in mind is one that Chet and I tried in Java or C# last week. Under the Game object, there is a “Framer” object that steps along the sequence of rolls, positioning itself to the start of each frame, and adding the score of that frame to the game total. In Java, we wound up having that object maintain an index into the array of rolls, and it would step that index by 2 for spares and open frames, and by one for strikes. (All done one step at a time, of course.)

Now, the Game

The game didn’t change after I first refactored in a rudimentary framer. It goes like this:

class Game(rolls: List[Int]) {
   def score = {
      val framer = new Framer(rolls)
      var total = 0
      for (frame < - 1 to 10) { total += framer.score }
      total
   }
}

Not much to see here. As I look at it now, I suspect there is a more Scala-like way  to do this. If you see one, please let me know.

And, the Framer

The Framer class has gone through a number of iterations, from a procedural, indexed approach, to this more functional one. One essential idea is that this Framer, rather than indexing through the list of rolls, actually consumes it. The other idea is that at each frame position, the Framer adds together two rolls, or three rolls, to get the frame score. An open frame scores just the two rolls of the frame, while a strike scores the one roll of the frame and the next two rolls, while a spare scores the two rolls of the frame and the next one. Since 2 + 1 equals 1 + 2, both “mark” frames wind up scoring three rolls.

I first had the idea of creating a tuple saying how many rolls to score, and how many to consume or “eat”. Then I hit on using Scala pattern matching to identify which kind of frame we’re dealing with.

Here again, I’m open to comments on style, and offers of better ways of doing it.

class Framer(var rolls: List[Int]) {
   def score = scoreFrame(frameList)

   def scoreFrame(sumAndEat: Tuple2[Int,Int]) = {
      val rollsToSum = rolls.take(sumAndEat._1)
      rolls = rolls.drop(sumAndEat._2)
      rollsToSum reduceLeft (_+_)
   }   

   def frameList = {
      rolls match {
         case List(10, _*) => (3,1)
         case List(a:Int, b:Int, _*) if (a + b == 10) => (3, 2)
         case _ => (2,2)
      }
   }
}

Overall, it was a somewhat frustrating experience, but mostly this was due to trying to hook together all the tools one would need. Partly, however, I found the two books I had, Wampler’s and Venkat’s, didn’t lend themselves to my style of learning, whatever that is.

Some of that is due to the fact that I am a Windows user and prefer to use an IDE. I realize this calls my masculinity into question, but I can live with that.

Anyway, that’s my report. I look forward to your comments, and I’ll probably write a few more articles about my experience with Scala. Enjoy!


Updates from Ilmari

Based on Ilmari’s suggestions, we can rewrite Framer as:

class Framer(var rolls: List[Int]) {
   def score = scoreFrame(frameList)

   def scoreFrame(sumAndEat: (Int,Int)) = {
      val rollsToSum = rolls.take(sumAndEat._1)
      rolls = rolls.drop(sumAndEat._2)
      rollsToSum reduceLeft (_+_)
   }   

   def frameList = {
      rolls match {
         case 10 :: theRest => (3,1)
         case first :: second :: theRest if (first + second == 10) => (3, 2)
         case _ => (2,2)
      }
   }
}

Frankly, I’m surprised by the match … apparently everything inside a case is taken implicitly as abstract parameters. Very interesting.

I couldn’t find a way to get rid of the ._1 and ._2 in the scoreFrame method: everything I tried to give those two Ints names wouldn’t compile.

As for Ilmari’s comment about the 1 to 10, I don’t see how to get rid of that either. Would like to learn more …


Another Pattern Match

I fiddled with the pattern match a bit, making it return two lists, the rolls to sum, and the rolls to work with next time. You’ll notice I’m still referring to the rolls state variable and storing over it. The scoreFrame method is simpler now: I’m not so happy with the pattern itself. Your thoughts?

class Framer(var rolls: List[Int]) {
   def score = scoreFrame(frameList)

   def scoreFrame(sumAndEat: (List[Int], List[Int])) = {
      rolls = sumAndEat._2
      sumAndEat._1.reduceLeft (_+_)
   }   

   def frameList = {
      rolls match {
         case 10 :: second :: third :: theRest =>
         	(10 :: second :: third :: Nil, second::third::theRest)

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

         case first::second::theRest => (first::second::Nil, theRest)

         case Nil => (Nil, Nil)
      }
   }
}

Posted on:

Written by: Ron Jeffries

Categorization: Articles, Practices

12 Responses to “Scala Bowling, I think …”

Ilmari Vacklin

August 3, 2010

11:02 am

permalink

Tuple2[Foo, Bar] can be also written (Foo, Bar). List(x, foo*) can be written x :: foo. I would probably pattern match on the tuple previously mentioned instead of accessing _1 and _2. The design seems a bit too stateful: I don’t actually understand why you’re adding .score ten times to the total!

I think you said on Twitter you only tried MyEclipse. Perhaps you could try IntelliJ IDEA and/or NetBeans and report your thoughts on them as Scala IDEs?

Thanks for the post.

J. B. Rainsberger

August 3, 2010

2:02 pm

permalink

I would have expected a more purely functional design. I definitely have no real experience with these languages either, but I see this exercise as a sequence of mappings: (1) map rolls to rolls-by-frame, (2) map rolls-by-frame to score-by-frame, (3) map score-by-frame to score. Only (1) requires something more complex than #sum.

Ron Jeffries

August 3, 2010

3:08 pm

permalink

Thanks, Ilmari … I’ve added an update based on what you’ve said here. Thanks again!

Ron Jeffries

August 3, 2010

3:22 pm

permalink

Well, I’m not smart enough to TDD to that incrementally. Not even sure how to do it the old fashioned way. I’d love to see an example, and I’ll see if I can learn enough more to do them. Certainly (1) is the interesting one. Seems to me that it requires disassembling the list and assembling a new one bit by bit. That’s like the .map function, or maybe it would require a fold. Fun to try …

Ron Jeffries

August 3, 2010

3:25 pm

permalink

Ilmari … I don’t have IntelliJ, nor NetBeans. I suppose I could get them but it seemed to me to be fraught to try to learn Scala and a new IDE at the same time. I always preferred IntelliJ to Eclipse … Chet and I use Eclipse mostly because we think it may be more common. No real data on that, either …

Philip Schwarz

August 3, 2010

6:24 pm

permalink

Hi Ron,

have not touched Scala for months now, still smarting from the problems I had in Eclipse. I should be sleeping by now, but I couldn’t resist translating my Haskell attempt into Scala. It seems to pass your tests. Here is the Haskell version:

score [x, y] = x + y — Normal Frame
score [10, x, y] = 10 + x + y — Strike
score [x, y, z] = 10 + z — Spare
score (10:(x:(y:rest))) = 10 + x + y + score (x:(y:rest)) — Strike
score (x:(y:(z:rest))) | (x + y) == 10 = 10 + z + score (z:rest) — Spare
score (x:(y:rest)) | otherwise = x + y + score rest — Normal Frame

And here is the corresponding Scala version:

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

Nigel Thorne

August 7, 2010

3:25 am

permalink

I am new to scala too, but from what I have read, this seems to be a more Scala-ish solution…

class Game(rolls: List[Int]) {
abstract class Frame
case class Strike(nextTwo:Int, rest:Frame) extends Frame
case class Spare(nextOne:Int, rest:Frame) extends Frame
case class Standard(total:Int, rest:Frame) extends Frame
case class Last() extends Frame

def score = scoreFrames(convertToFrames(rolls), 10)

def convertToFrames(xrolls: List[Int]): Frame = xrolls match {
case List() => Last()
case 10 :: b :: c :: rest => Strike(b+c, convertToFrames(b::c::rest))
case a :: b :: c :: rest if a+b == 10 => Spare(c, convertToFrames(c :: rest))
case a :: b :: rest => Standard(a + b, convertToFrames(rest))
}

def scoreFrames(frame :Frame, count:Int) :Int = {
if(count == 0) return 0
frame match {
case Last() => 0
case Strike(nextTwo, rest) => 10 + nextTwo + scoreFrames(rest, count-1)
case Spare(nextOne, rest) => 10 + nextOne + scoreFrames(rest, count-1)
case Standard(total, rest) => total + scoreFrames(rest, count-1)
}
}
}

Ron Jeffries

August 7, 2010

6:03 am

permalink

Hi Nigel,

Well, it works. Future posters: Please let us know whether a proposed solution passes all the tests in the latest version of the article.

I don’t know if it is more scala-like or not: I’m too new to the language to judge.

I did find it a bit hard to figure out. It is odd that Strike and Spare contain the frame BONUS value while Standard contains the total frame score.

This means that the scoreFrame cases calculate a value in some cases and just return it in others. If the convertToFrame rolled the 10 in, then scoreFrames would need to do less work.

I’m also not clear why, since you have a last frame, you also need the frame count in scoreFrames.

If time permits, I’ll experiment. Thanks!

Nigel Thorne

August 7, 2010

5:15 pm

permalink

Ron, That’s interesting. I had some of the same concerns.. also the case classes aren’t doing much.

Here’s another version. I’d love more feedback.

class Game(rolls: List[Int]) {
abstract class Frame {
def score : Int
}

case class AFrame(total:Int, next:Frame) extends Frame {
def score = total + next.score
}

case object EndOfFrames extends Frame {
def score = 0
}

def score = convertToFrames(rolls, 0).score

def convertToFrames(remainingrolls: List[Int], index:Int): Frame = {
if (index == 10) return EndOfFrames
remainingrolls match {
case 10 :: b :: c :: rest => AFrame(10+b+c, convertToFrames(b::c::rest, index+1))
case a :: b :: c :: rest if a+b == 10 => AFrame(10+c, convertToFrames(c :: rest, index+1))
case a :: b :: rest => AFrame(a + b, convertToFrames(rest, index+1))
case List() => EndOfFrames
}
}
}

I don’t like having to have a class called AFrame.. i’m sure there is a better name or some way to remove the need for it. I loose the explicit calling out of Spare and Strike as cases.

Nigel Thorne

August 8, 2010

5:00 am

permalink

Here’s yet another take. Yes it passes all the tests..
http://gist.github.com/513828

I think it’s clearer, but it does use more Scala idioms. I like how it’s all immutable.

The game only holds Frames now, which may/may not be good depending on what the next set of requirements are..

This is honestly my last go at this ;) but I have enjoyed it .. let me know what you think.

Ron Jeffries

August 8, 2010

5:20 am

permalink

Interesting. I find it hard to follow. It seems to me that most of our list-oriented and recursion-oriented solutions are harder to follow that the procedural ones.

I imagine that functional programming experts will say that’s just because we aren’t used to the functional approach yet. I’d like to see that experiment done. I suspect that it would turn out that someone expert in both procedural and functional style would understand the procedural solutions faster.

There definitely comes a time when a functional solution is easier to write, and sometimes they are easier to read. Sometimes.

Ron Jeffries

August 8, 2010

7:29 am

permalink

Closing comments on some of these older articles. Later ones still timely and open …

Recent Articles

The Gift … a Report and a Request

The Story in Brief

At Agile2011, I brought along a “gift”, a nicely formatted and illustrated Kate Oneal story. I gave a copy to everyone who asked for one, and to a few people who didn’t but who I wanted…

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.