Scoring games of bowling
Contents
Context
Ron Jeffries has in several places discussed the calculation of bowling scores as a model programming task. The idea is to receive a list of balls (i.e. tallies of pins knocked down by each) that comprise a complete game of bowling (for one player), and produce the game score as the result.
In addition to my own programs to solve this problem, here I've also collected J solutions to this exercise written by others.
Related web pages:
J-related blog posts by Ron Jeffries
A page on the topic by Jeff Zellner
Early notes and TDD example by Raul Miller
My first bowling scorer
Here is what I wrote the first time I took on this task:
frame =: verb define
WhereStrikes =. monad :'y #~ 2 ~:/\ 1, 2|y' (10&= # i.@#) y
BallsAsFramePairs =. WhereStrikes dyad :'y #!._1~ 1j1 x } 1#~#y' y
monad :'(#~ _1&~:)&.> _2<\ y' BallsAsFramePairs
)
scoregame =: verb define
framepins =. [: > {.
bonus1 =. 10=+/ NB. for strikes and spares
bonus2 =. 10={. NB. for strikes
laterballs=. monad :'2 {. ; }.y'
bonus =. laterballs * [:(bonus1, bonus2) framepins
oneframe =. [: +/ framepins, bonus
scoreheadframe=. ([: < ([: > {.), [:oneframe }.) , 2}.]
+/ >{. scoreheadframe^:10 (a:,frame) y
)That program was written without studying any other solutions to the problem. In structuring it, I attempted to make the algorithm apparent strictly through named components rather than commentary. That idea was relatively new to me at the time. Still, I included a couple comments.
My second bowling scorer
Some months later, I wrote a different solution. This one came after I'd studied Henry Rich's solution carefully. I've used indentation to segregate subordinate supportive definitions from the major definitions.
o =: @:
score_game=: +/ o( select`value } )
value =: trim (0, 2 3 +/\ ])o extend
extend=: ,& 0 0
trim =: #o[ |:o{. |:o]
select=: trim [:extend [:>o{: [:score_frame^:10 mark ; 0$0:
mark =: (_9 * 10 =]) *o+/o,: 10 = 2 +/\]
front=: 1 :' (0;0)&{:: u >o{. '
back =: 1 :' (0;0)&{:: u >o{: '
score_frame=: (consume }.])front ; ( ], pins, omit)back
consume=: [{ 2 2 1 "_ NB. TwoBalls, TwoBalls, OneBall
pins =: [{ 1 2 2 "_ NB. OpenFrame, MarkFrame, MarkFrame
omit =: [{:: 0;0;0$0: NB. ExtraBall, ExtraBall, NothingThe general pattern of this solution is to select pin-totals according to the identification of frames. In keeping with the "breadth-wise" bias of J, all possible pin-totals are calculated. A selection from those is then made to fit the actual pattern of frames in the given game. (Mr. Rich's approach is similar.) I'm happy with the expression of the selection relationship at the highest level, particularly ( select`value } ) within score_game and (0, 2 3 +/\ ]) within value. I am less happy about how the details of the selectors (in pins and omit) seem hard to relate to their application, even with the brief comments I included to assist. (These comments are names I decided not to define to cover the special values involved.)
In completing this I got helpful advice from Dan Bron on simplifications. For example, he pointed me toward the use of a gerund pair as the argument to Item Amend. I also appreciate J. M. Quintana's technique of using o as an abbreviation for @: .
A solution by Henry Rich
Henry Rich's solution to this problem has been particularly prominent.
NB. Index of each ball that starts a frame
framex =: 10 {. 0 {~^:a:~ _1 _1 ,~ i.@# >:@:+ 10&~:
NB. Score for each ball, assuming that ball starts a frame
scoreifframe =: 3 +^:(9<])`+/@|.\ ,&0
NB. Pick the balls that actually start frames & add em up
gamescore =: [: +/ framex { scoreifframe
A solution by June Kim
Another J solution to this problem was written by June Kim some years back. He posted it with some explanatory comments here. The code portion I reproduce below:
of=: @
sum=: +/
for=: ^:
match=: -:
applied=: &>/
head=: {.
andbonus=: conjunction def '(m+n)&{. ; m&}.'
open=: 2 andbonus 0
spare=: 2 andbonus 1
strike=: 1 andbonus 2
isStr=: 10 match head
isSpr=: 10 match sum of (2&head)
summed=: (sum@[ , ])applied@ (@])
attach=: [,head@]
rest=: }.@]
accumed=: ([`(attach;rest)`) (`:6)
frame=: open`spare@.isSpr`strike@.isStr
doframe=: frame summed accumed applied
limit=: (<&)(@#)
init=: ''&;
score=: >@head
frames=: doframe for (10 limit of score) for (_:`init)
gscore=: sum of score of frames
empty_case=: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
open_case=: 9 0 8 1 7 2 6 3 5 4 4 5 3 6 2 7 8 1 0 9
spare_case=: 9 1 8 2 7 3 6 4 5 5 4 6 3 7 2 8 1 9 8 2 1
strike_case=: 10 10 10 10 10 10 10 10 10 10 10 10
alt_case=: 9 1 10 8 2 10 7 3 10 6 4 10 5 5 10 4 6
none_and_spare_case=: 0 10 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
strike_none_spare_case=: 10 0 10 1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0
assert 0-:gscore empty_case
assert 90-:gscore open_case
assert 145-:gscore spare_case
assert 300-:gscore strike_case
assert 200-:gscore alt_case
assert 30-:gscore none_and_spare_case
assert 34-:gscore strike_none_spare_case
Comparisons
Over the next few days I intend to compare these programs. This section will contain those notes.
