Scala … a failing test

Jon Bettinger has found a failing test! Excellent!

In a comment on an earlier article, Jon points out that the special check for a final bonus frame can trigger on the 9th frame, not just the 10th. The game can end 10-x-y with a strike in the 9th frame and an open frame in the 10th! A test that succeeds and one that fails are:

    it("should count not double count tenth frame bonus rolls" ) {
    	expect(11)(Game.score(List(0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 10,1,0)))
    }

    // fails !!!
    it("should count non-mark tenth frame after strike in ninth" ) {
    	expect(12)(Game.score(List(0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 10, 1,0)))
    }

My initial–but still thoughtful–reaction to this is that the current tail-recursive approach is doomed to fail without frame counting. I have a fairly simple counting fix in hand. And I suspect that a two-phase approach, parsing frames from the left and then summing, might be made to work without counting. Stylistic arguments aside, I think these count-free solutions are getting hard to understand.

I’d like to see one that shows up as clean and yet in full functional style.

I’ll show my current solution below. But first, some thoughts on helping.

The Code in My Head Always Works

It is fun, when someone is having trouble with a program, to suggest a “solution” that is significantly different from what they are doing, and it is tempting to declare it to work, and to be better. Recent experience here in the comments on these articles confirms the experience of my entire lifetime:

The code in my head always works. The code in the computer … not so much. What does this tell us about how to help some poor fool, like me, from a distance? Jon’s test is a perfect way to help, in that it’s just about the code and the fact that it doesn’t work. He didn’t even have to say “you tiny fool”.

But what about suggestions? It seems to me that a really good way would be to provide a little package built with tests, preferably TDD tests. A great way would be to provide an article, not like these Scala ones but like some earlier ones, showing the program, and understanding, growing bit by bit.

That’s really going to be time-consuming. It takes ages to write that kind of article. Great though.

If we want to help the other person rather than just dump an answer on them, we need to gauge just how quick they’re going to be.

I recall the first time I paired with Ward Cunningham. We were working on an interpreter program for a DSL my team was writing. He looked at what I had and–I believe–saw that it sucked. He said “You know how to write an interpreter in Smalltalk, right?” I hope I said something like “Well, I’m not sure, what do you mean?” Ward gave a hint; I didn’t get it; he gave a stronger hint; I still didn’t get it. This repeated about down to the point where he had to say “OK, now type a star …”.

At no time did he make me feel bad or say something to make himself feel superior. He just increased the level of help until I could get it … and not a bit more.

I should be that good, even once. :)

Applying this notion here, it seems to me that a “vague” kind of help would be a snippet of code that does something that might be useful if transformed into the current domain. Another kind might be a pointer to a technique. Finally, some working code.

When Philip gave me that snippet of code that I built on, I was in full learning mode, so I didn’t feel threatened by his solution. And I don’t feel threatened by people who are smarter than I am anyway, as I have been around them for so long. I might be a bit unique in that. So one needs to be cautious when tossing someone a solution. They might reject the solution; they might just install it; or they might examine it and learn.

This is easier in an environment of pairing and conversation, and difficult in a medium like this. Anyway, just something to think about.

The Current Solution

My current guess is is that a frame count is absolutely necessary to the solution, because it is seems impossible to look at the last few rolls and know whether they represent one frame or two. The reason is that the final frame is special.

So I decided to put a frame number back in, inside my function. First I changed the tests to insert 1 (integer representing first frame) as a parameter in the score method, then made it work, then added a score(rolls) polymorphic entry point. Could have been done another way. Anyway, here’s what I have, and it works. Well … it passes all the tests.

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

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

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

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

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

   def score(rolls: List[Int]):Int = score(1, rolls)
}

I’ve highlighted the changes. Let me know what you think, and how to do better. Thanks!

7 Responses to “Scala … a failing test”

Philip Schwarz

August 5, 2010

6:18 pm

permalink

Hi Ron,

You said:

Jon Bettinger has found a failing test! Excellent!

Excellent indeed! This series of posts is turning into a learning experience.

I only allowed myself to show you my Haskell program on the grounds that it passed your tests.

I wrote the program back in 2009, as a response to the following post: http://blog.moertel.com/articles/2006/04/05/the-bowling-game-kata-in-haskell
It was the first time I had done the bowling kata with a functional language, and while I was pleased with the simplicity of the solution, I also feared that some
corner case might break it. But I told myself: No need to worry, if it passes all the tests, it meets its requirements.

Here are the tests it passed:

[ "gutters" ~: score (replicate 20 0) ~?= 0
, "ones" ~: score (replicate 20 1) ~?= 20
, "fives" ~: score (replicate 21 5) ~?= 150
, "strikes" ~: score (replicate 12 10) ~?= 300
, "1 + gutters" ~: score (1 : replicate 19 0) ~?= 1
, "first spare" ~: score (5:5:5 : replicate 17 0) ~?= 20
, "first strike" ~: score (10:5:5 : replicate 16 0) ~?= 30
, "last spare" ~: score (reverse (5:5:5 : replicate 18 0)) ~?= 15
, "last strike" ~: score (reverse (5:5:10 : replicate 18 0)) ~?= 20]

Instead, I should have heeded Kent Beck’s advice: “test until fear turns to boredom”. Those 9 tests were not enough to eliminate all traces of fear.
And I wasn’t bored yet either.

To atone for my sins, I have run my Scala program against the test suites of Uncle Bob and Brett Schuchert, and here are the results;

// Robert Martin’s tests in his book ASDPP (http://www.amazon.co.uk/Software-Development-Principles-Patterns-Practices/dp/0135974445)
NOT RUN assert(0==quicksort.score(List())) // TestScoreNoThrows
NOT RUN assert(5==quicksort.score(List(5))) // TestAddOneThrow
PASSED assert(9==quicksort.score(List(5,4))) // TestTwoThrowsNoMark
PASSED assert(18==quicksort.score(List(5,4,7,2))) // TestFourThrowsNoMark
PASSED assert(13==quicksort.score(List(3,7,3))) // TestSimpleSpare
PASSED assert(18==quicksort.score(List(3,7,3,2))) // TestSimpleFrameAfterSpare
FAILED assert(28==quicksort.score(List(10,3,6))) // TestSimpleStrike
PASSED assert(300==quicksort.score(List(10,10,10,10,10,10,10,10,10,10,10,10))) // TestPerfectGame
PASSED assert(133==quicksort.score(List(1,4,4,5,6,4,5,5,10,0,1,7,3,6,4,10,2,8,6))) // TestSampleGame
PASSED assert(299==quicksort.score(List(10,10,10,10,10,10,10,10,10,10,10,9))) // TestHeartBreak
PASSED assert(270==quicksort.score(List(10,10,10,10,10,10,10,10,10,9,1,1))) // TestTenthFrameSpare

// Robert Martin’s tests in his bowling kata slides (http://butunclebob.com/files/downloads/Bowling%20Game%20Kata.ppt)
PASSED assert(0==quicksort.score(List(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0))) // testGutterGame
PASSED assert(20==quicksort.score(List(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1))) // testAllOnes
PASSED assert(16==quicksort.score(List(5,5,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0))) // testOneSpare
PASSED assert(24==quicksort.score(List(10,3,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0))) // testOneStrike
PASSED assert(300==quicksort.score(List(10,10,10,10,10,10,10,10,10,10,10,10))) //testPerfectGame

// Schuchert’s tests in his scala bowling kata blog post (http://blog.objectmentor.com/articles/2009/10/07/scala-bowling-kata-still-in-the-middle-i-suppose)
PASSED assert(0==quicksort.score(List(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0))) // all 0′s”
PASSED assert(20==quicksort.score(List(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1))) // all 1′s
NOT RUN assert(20==quicksort.score(List(5,5,5))) // a single spare followed by a 5
PASSED assert(150==quicksort.score(List(5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5))) // all 5′s
NOT RUN assert(20==quicksort.score(List(10,4))) // a single strike followed by a 4
NOT RUN assert(39==quicksort.score(List(10,5,5,3,3))) // a strike, spare then an open frame with two 3′s
FAILED assert(42==quicksort.score(List(5,5,10,3,3))) // spare, strike then an open frame with two 3′s
NOT RUN assert(200==quicksort.score(List(5,5,10,5,5,10,5,5,10,5,5,10,5,5,10)))// Dutch 200 game, Spare-Strike
NOT RUN assert(200==quicksort.score(List(10,5,5,10,5,5,10,5,5,10,5,5,10,5,5)))// A Dutch 200 game, Strike-Spare

Here are your tests:

PASSED assert(0==quicksort.score(List(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)))
PASSED assert(90==quicksort.score(List(5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4)))
PASSED assert(22==quicksort.score(List(6,4,5,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)))
PASSED assert(31==quicksort.score(List(10,5,2,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)))
PASSED assert(300==quicksort.score(List(10,10,10,10,10,10,10,10,10,10,10,10)))

And here is Jon’s test:

FAILED assert(12==quicksort.score(List(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,1,0)))

Philip Schwarz

August 6, 2010

6:13 pm

permalink

Hi Ron,

I have found a contrived way of simplifying the implementation of the function a little bit, but it likely reduces understandability.

Still, here it is: instead of making execution of the second case clause conditional upon the frame number being 10, we can make it conditional upon the frame number being an even number, because we know that if there are three rolls left and the frame number is even, then the frame number must be 10.

So instead of keeping a count of the frame number and checking if it is ten, we keep switching a flag and when there are three rolls left, we check if the flag is set:

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

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

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

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

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

def score(rolls: List[Int]):Int = score(false, rolls)

Garry Shutler

August 8, 2010

6:38 am

permalink

I’ve been looking at Scala a little recently so I’ve been following your posts with interest.

I’m wondering if a good approach would be to do two passes, a map into frames from the raw scores and then mapping the frames into a total score.

eg. (0,0, 0,0, 10, …)
becomes ((0,0), (0,0), (10), …)

I’ll see if I can come up with some code. I’m not even sure if this works in my head!

Ron Jeffries

August 8, 2010

7:28 am

permalink

Do try to come up with some code … and use at least these tests. Some of the recent example comments use that approach. Arguably that’s what my first algorithm does, in some form …

Michael

August 18, 2010

2:36 am

permalink

Here’s an approach that ignores the frame in the core algorithm. It also uses Boolean extractors to clean it up a bit.

class BowlByMapping {
def score(bowls:List[Int]) = {
scoreFrames(bowls).slice(0,10).sum
}

def scoreFrames(bowls:List[Int]):List[Int] = bowls match {
case Nil => List(0)
case isStrike() =>
bowls.slice(0,3).sum :: scoreFrames(bowls drop 1)
case isSpare() =>
bowls.slice(0,3).sum :: scoreFrames(bowls drop 2)
case _ =>
bowls.slice(0,2).sum :: scoreFrames(bowls drop 2)
}

object isStrike {
def unapply(bowls:List[Int]):Boolean = bowls match {
case 10::_ => true
case _ => false
}
}

object isSpare {
def unapply(bowls:List[Int]):Boolean = bowls match {
case a::b::_ if a+b == 10 => true
case _ => false
}
}
}

Ron Jeffries

August 18, 2010

8:45 am

permalink

Does this run the tests? I don’t see how the .slice(0,10) can work, since there are more than ten rolls in all games.

Michael

August 18, 2010

10:14 am

permalink

Yes it runs the tests you had in your first post as well as Jon Bettinger’s tests.

It transforms the bowls into a list of scores per frame. It then takes the first 10 frames and sums their scores

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…

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…

Events & Classes

  • No events.