## Sunday, August 30, 2015

### Bowling Scores

Today we are going to explore building a bowling score calculator using Factor. In particular, we will be scoring ten-pin bowling.

There are a lot of ways to "golf" this, including this short version in F#, but we will build this in several steps through transformations of the input. The test input is a string representation of the hits, misses, spares, and strikes. The output will be a number which is your total score. We will assume valid inputs and not do much error-checking.

A sample game might look like this:

`12X4--3-69/-98/8-8-`

Our first transformation is to convert each character to a number of pins that have been knocked down for each ball. Strikes are denoted with `X`, spares with `/`, misses with `-`, and normal hits with a number.

```: pin ( last ch -- pin )
{
{ CHAR: X [ 10 ] }
{ CHAR: / [ 10 over - ] }
{ CHAR: - [ 0 ] }
[ CHAR: 0 - ]
} case nip ;```

We use this to convert the entire string into a series of pins knocked down for each ball.

```: pins ( str -- pins )
f swap [ pin dup ] { } map-as nip ;```

A single frame will be either one ball, if a strike, or two balls. We are going to use cut-slice instead of cut because it will be helpful later.

```: frame ( pins -- rest frame )
dup first 10 = 1 2 ? short cut-slice swap ;```

A game is 9 "normal" frames and then a last frame that could have up to three balls in it.

```: frames ( pins -- frames )
9 [ frame ] replicate swap suffix ;```

Some frames will trigger a bonus. Strikes add the value of the next two balls. Spares add the value of the next ball. We build this by "un-slicing" the frame and calling sum on the next balls.

```: bonus ( frame -- bonus )
[ seq>> ] [ to>> tail ] [ length 3 swap - ] tri head sum ;```

We can score the frames by checking for frames where all ten pins are knocked down (either spares or strikes) and adding their bonus.

```: scores ( frames -- scores )
[ [ sum ] keep over 10 = [ bonus + ] [ drop ] if ] map ;```

We can solve the original goal by just adding all the scores:

```: bowl ( str -- score )
pins frames scores sum ;```

And write a bunch of unit tests to make sure it works:

```{ 0 } [ "---------------------" bowl ] unit-test
{ 11 } [ "------------------X1-" bowl ] unit-test
{ 12 } [ "----------------X1-" bowl ] unit-test
{ 15 } [ "------------------5/5" bowl ] unit-test
{ 20 } [ "11111111111111111111" bowl ] unit-test
{ 20 } [ "5/5-----------------" bowl ] unit-test
{ 20 } [ "------------------5/X" bowl ] unit-test
{ 40 } [ "X5/5----------------" bowl ] unit-test
{ 80 } [ "-8-7714215X6172183-" bowl ] unit-test
{ 83 } [ "12X4--3-69/-98/8-8-" bowl ] unit-test
{ 150 } [ "5/5/5/5/5/5/5/5/5/5/5" bowl ] unit-test
{ 144 } [ "XXX6-3/819-44X6-" bowl ] unit-test
{ 266 } [ "XXXXXXXXX81-" bowl ] unit-test
{ 271 } [ "XXXXXXXXX9/2" bowl ] unit-test
{ 279 } [ "XXXXXXXXXX33" bowl ] unit-test
{ 295 } [ "XXXXXXXXXXX5" bowl ] unit-test
{ 300 } [ "XXXXXXXXXXXX" bowl ] unit-test
{ 100 } [ "-/-/-/-/-/-/-/-/-/-/-" bowl ] unit-test
{ 190 } [ "9/9/9/9/9/9/9/9/9/9/9" bowl ] unit-test```

This is available on my GitHub.