Thursday, February 17

Spares vs. strikes (take 2)

So... spares vs. strikes is a hot issue for some people, in bowling.  So how about if we make tracking them be a requirement?

Now, instead of being some kind of strange coding artifact, they need to be a part of the result.  This also means that our previous result (the total score) is no longer an acceptable result.  Instead, let's say that we need a running total of the score for each frame, along with an indication of whether the frame was open, a spare, or a strike.

Now, I could start over from scratch here, but I am not inclined to do so.  This seems like an easy change to make, so let's just jump right in:

assert (10#"0 'open';0) -: bowlingScores 20#0

In other words, I want
   bowlingScores 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
to give me the result
+----+----+----+----+----+----+----+----+----+----+
|open|open|open|open|open|open|open|open|open|open|
+----+----+----+----+----+----+----+----+----+----+
|0   |0   |0   |0   |0   |0   |0   |0   |0   |0   |
+----+----+----+----+----+----+----+----+----+----+

(Note that I am using ascii boxes here, rather than line drawing characters, because in my experience some browser implementations do the wrong thing with line drawing characters. Yes, ascii is ugly. But it is ubiquitous.)

  bowlingScores=:3 :0
   i=.0
   frameType=.frameScore=.''
   while.(i<#y) *. 10>#frameScore do.
    bonus=. 10 = +/\ 2{.i}. y
    frameType=.frameType, #.bonus
    frameScore=.frameScore ,+/(2++./bonus){.i}.y
    i=. i+2-10=i}y
   end.
   (frameType { ;:'open spare strike') ,: ]&.> +/\frameScore
  )

This next test also succeeds, so that is probably good enough?

assert ((,: +each/\)/10#"0'strike';30) -: bowlingScores 12#10

Here, I was looking for:

   (,: +each/\)/10#"0'strike';30
+------+------+------+------+------+------+------+------+------+------+
|strike|strike|strike|strike|strike|strike|strike|strike|strike|strike|
+------+------+------+------+------+------+------+------+------+------+
|30    |60    |90    |120   |150   |180   |210   |240   |270   |300   |
+------+------+------+------+------+------+------+------+------+------+

Since that test all by itself is getting a bit complicated (and because @RonJefferies suggested it might be a good idea), I should take time here to explain what I am doing:

The phrase 10#"0'strike';30 works the same way that the previous phrase (10#"0'strike';30) worked.  I am just replicating each item into a list of ten copies.  Meanwhile the / operator puts the verb on its left between each item of the array on its right.  In this case that means:

   (10#<'strike')  (,: +each/\) (10#<30)

That (,: +each/\) is a hook, which means that the left verb combines the left argument (10 copies of the box containing the word 'strike') with the result of the right verb on the right argument (the individually boxed running sum of 10 copies of the number 30).

And, no, that implementation of bowlingScores was not quite good enough:

   bowlingScores 10 1 0 6 3 10 0 6 4 2 10 6 4 1 0 1 3
|index error: bowlingScores

The problem, here, is that when i=5, I am computing 10=+/\10 0 which is equivalent to 10=10 10 which gives me a result of 1 1, and #. 1 1 is 3.  But my list of frame types only has 3 elements: 0: open, 1: spare, 2: strike.

I could deal with this by adding a fourth 'strike' element to that list.  Or, I could deal with it by preventing the result obtained from #.bonus from ever being larger than 2.

But, also, my implementation of bowlingScores was a bit unwieldy, with no modularity between the (hypothetically useful) computational code and the (perhaps dubious) presentation code.  So perhaps I should refactor it:

 bScores=:3 :0
  i=.0
  frames=.i.0 2
  while.(i<#y) *. 10>#frames do.
   bonus=. 10 = +/\ 2{.i}. y
   frames=. frames, (2<.#.bonus) ,+/(2++./bonus){.i}.y
   i=. i+2-10=i}y
  end.
  (,: +/\)/|:frames
 )

 bowlingScores=:3 :0
   types=. ;:'open spare strike'
   ((types{~{.),: [:]&.>{:) bScores y
 )

Here, bScores does all the work, and bowlingScores just repackages that to match my (somewhat arbitrary) test oriented representation.  And, it also works when I have a strike followed by a gutter ball:

   bScores 10 1 0 6 3 10 0 6 4 2 10 6 4 1 0 1 3
 2  0  0  2  0  0  2  1  0  0
11 12 21 41 47 53 73 84 85 89

   bowlingScores 10 1 0 6 3 10 0 6 4 2 10 6 4 1 0 1 3
+------+----+----+------+----+----+------+-----+----+----+
|strike|open|open|strike|open|open|strike|spare|open|open|
+------+----+----+------+----+----+------+-----+----+----+
|11    |12  |21  |41    |47  |53  |73    |84   |85  |89  |
+------+----+----+------+----+----+------+-----+----+----+

I did not have an explicit test result I was checking here -- just running the code was test enough.  I think that that is an appropriate test, sometimes.

Perhaps next up would be to track down a full set of specifications about how scoring machines work, in bowling alleys, and add requirements so that the scoring routine could represent game progress at any point in a game.  In other words, we would want an adjacent turky count, a done indicator, as well as indicators for incomplete spares and incomplete spikes, and perhaps other requirements.  (But probably leave out the timer that shuts down your lanes if you take too long, since that can be imposed independently of the scoring system.)

No comments:

Post a Comment