Ten-pin bowling score - World Bowling edition

20

World Bowling scoring

Many people have gone to their local bowling center to play a few games of bowling, and many people continue to struggle to calculate their scores. World Bowling has introduced a simplified scoring system in order to attract more people to the sport. This scoring system is utilized in international games.

The scoring system works like this (from Wikipedia):

The World Bowling scoring system—described as "current frame scoring"[32]—awards pins as follows:

  • strike: 30 (regardless of ensuing rolls' results)
  • spare: 10 plus pinfall on first roll of the current frame
  • open: total pinfall for current frame

If you're not familiar with ten-pin bowling, here's a recap.

There are 10 pins at the end of a bowling lane where the goal is to knock all of them down with a bowling ball. You get 2 rolls of a ball to attempt to knock them all down, preferably knocking them all down with the first roll (known as a strike). If you do get a strike, then that frame is completed and you do not need to roll the ball a second time. A strike is worth 30.

If you don't knock down all ten, you get one more roll. If you knock down all of the remaining pins, that is known as a spare. The score is worth 10 pins + the number of pins knocked down on the first roll. For example, if I knocked down 7 pins, then managed to knock down the remaining 3, that would be worth 17.

If after your second roll you fail to knock down all ten, that is known as an open frame. The score is worth the total number of pins knocked down for that frame.

There are 10 frames in a game. If you're familiar with traditional bowling scoring, you don't get an extra roll in the 10th frame with the World Bowling Scoring. In traditional bowling scoring, it takes 12 consecutive strikes to get a perfect score of 300, whereas World Bowling scoring only requires 10 consecutive strikes.

Challenge

Your challenge is to calculate the score given values from a score sheet.

On a score sheet, a miss is indicated by a dash (-), a strike with an X, and a spare with a slash (/). If these don't apply, then the pinfall count is simply indicated with a number (1-9). Fouls and splits are also recorded onto score sheets but you do not need to worry about these.

Input

You will be given a string consisting of scores for each frame, and will have a total of ten frames. Each frame will have up to two values, or as little as 1 value if there was a strike. Your input may be string parameter to a function, read from a file, or from STDIN.

For example, if I knocked down 1 pin on my first roll, then knocked down 2, the frame would look like "12". This does not mean 12 (twelve), but means 1 and 2, for a total of 3.

If I missed every pin with both rolls (gutter balls), it would look like this "--" (score of 0).

Each frame will be separated by a space.

Sample input

-- 9- -9 X -/ 8/ 71 15 44 X

To break down this example,

  • Frame 1 (--) - both rolls missed. scored 0
  • Frame 2 (9-) - knocked down 9 on the first roll, missed on the second roll. Score 9
  • Frame 3 (-9) - Missed all on the first, got 9 on the second. Score 9
  • Frame 4 (X) - Strike, knocked down all ten. Score 30
  • Frame 5 (-/) - Spare, missed all on the first, knocked down all with 2nd roll. Score 10 + 0 = 10
  • Frame 6 (8/) - Spare, 8 pins on first roll, knocked down the other 2 with 2nd roll. Score 10+8 = 18
  • Frame 7 (71) - open frame,7 pins on first roll, 1 pin on second roll. Score 7+1=8
  • Frames 8,9,10 follow same examples as above.

Output

Output will simply be a value that has the sum of the scores from all 10 frames. Using the sample input, the output will be 128. Your output may be a string or a numeric type. It may be a function return value, or written to STDOUT.

Rules

  • Assume that the input will always be valid. For example, an invalid frame would be "/8", "XX", "123", "0", etc.
  • You do not need to worry about splits or fouls.
  • Your code may be a full program or a function that takes in a string and returns the score.
  • Your code must not throw any exceptions.
  • This is code golf, the answer with the fewest number of bytes wins.
  • Languages that use includes or imports must include the import statements as part of their code and count towards the byte count.

Test Cases

"-- 9- -9 X -/ 8/ 71 15 44 X" -> 128
"-- -1 2- 12 22 5- 42 61 8- 72" -> 45
"X X X 1/ 2/ 3/ 4/ 5/ -- 9/" -> 174
"X X X X X X X X X X" -> 300
"-- -- -- -- -- -- -- -- -- --" -> 0

Makotosan

Posted 2018-03-26T04:08:10.933

Reputation: 503

21I'm disappointed this isn't a code-bowling challenge – Jo King – 2018-03-26T04:49:46.533

13Your first spare example says the score would be 13, but I think it is supposed to be 17. – Jo. – 2018-03-26T04:51:50.787

@Jo. Good catch. I've updated the question to fix that error. – Makotosan – 2018-03-26T12:42:21.553

@JoKing I was thinking this is a code-bowling challenge composed of 10 subchallenges when I first saw the title. – Weijun Zhou – 2018-03-27T03:36:44.847

1One of the best documented and written challenges I have seen. – Joshua – 2018-03-27T15:36:07.980

Answers

7

05AB1E, 12 11 bytes

Code

S'/T:'X30:O

Try it online!

Explanation

S             # Split the string into a list of characters
 '/T:         # Replace '/' with 10
     'X30:    # Replace 'X' with 30
          O   # Sum up the array (ignoring non-number elements)

Adnan

Posted 2018-03-26T04:08:10.933

Reputation: 41 965

7

JavaScript, 43 bytes

f=([c,...s])=>c?({'/':10,X:30}[c]|c)+f(s):0

f=([c,...s])=>c?({'/':10,X:30}[c]|c)+f(s):0
Score(<input style="width:200px" oninput="o.value=f(this.value)" />) = <output id=o></output>

How it works

We convert each character to its point:

  • 'X' worth 30 point
  • '/' worth 10 point
  • '1' .. '9' worth 1 .. 9 point
  • other characters worth 0 point

Then sum all points.

Convert

Bitwise OR operator | convert its operand to Int32 before operate. When converting to Int32, value first convert to Number (64bit float number) format, then trunk to Int32 (or converted to 0 if invalid).

  • ToInt32({'/':10,X:30}[c]) could be read as:
    • if c == '/': result is 10;
    • if c == 'X': result is 30;
    • otherwise: result is ToInt32(undefined) -> ToInt32(NaN) -> 0;
  • ToInt32(c) could be:
    • if c == '1' ... '9': result is 1 .. 9;
    • if c == ' ': Number(c) is 0, result is 0;
    • otherwise: Number(c) is NaN, result is 0;
  • Bitwise or here is same as "add", since one of its operand will be 0

Sum

  • [c,...s] = s let c = s[0], and s = s.slice(1);
    • if s is empty string, c is undefined;
    • otherwise, c is first letter of s
  • undefined is falsy, non-empty string (including space) is truthy

tsh

Posted 2018-03-26T04:08:10.933

Reputation: 13 072

1Can you explain your code? looks really good – Luis felipe De jesus Munoz – 2018-03-26T12:57:37.427

@LuisfelipeDejesusMunoz Just added some. – tsh – 2018-03-27T02:37:13.640

5

Stax, 13 bytes

─*âⁿ┴8òt↨HÉ÷8

Run and debug it

Unpacked, ungolfed, and commented it's thus.

F               for each character in input, execute...
 9R$'/20*+'X+   build the string "123456789////////////////////X"
 I              get the index of the current character in string
 ^+             increment and add to running total
                (index is -1 when no match; space and dash are 0 score)

Run this one

recursive

Posted 2018-03-26T04:08:10.933

Reputation: 8 616

3

Java 8, 64 59 46 bytes

s->s.map(c->c<46?0:c<48?10:c>87?30:c-48).sum()

-5 bytes thanks to @Neil.
-13 bytes thanks to @OlivierGrégoire.

Explanation:

Try it online.

s->               // Method with an IntStream parameter and integer return-type
  s.map(c->       //  Loop over the characters
          c<46?   //   If the character is a space or '-':
           0      //    Count it as 0
          :c<48?  //   Else-if it's a '/':
           10     //    Count it as 10
          :c>87?  //   Else-if it's an 'X':
           30     //    Count it as 30
          :       //   Else (it's a digit):
           c-48   //    Count it as the value of the digit
       ).sum()    //   And sum everything

Kevin Cruijssen

Posted 2018-03-26T04:08:10.933

Reputation: 67 575

1("123456789//"+1e6+1e6+"X") seems to save 5 bytes. – Neil – 2018-03-26T12:04:52.683

That's a clever technique to create some filler string. – Makotosan – 2018-03-26T13:47:48.590

146 bytes – Olivier Grégoire – 2018-03-26T16:39:47.140

3

F#, 106 103 bytes

let s c=Seq.sumBy(fun x->if x=' '||x='-'then 0 elif x='X'then 30 elif x='/'then 10 else int(string x))c

Try it online!

I think that this puzzle (without the golfing) would be a great question for a "Functional programming for beginners" guide. And I should know!

-3 from Kevin Cruijssen, for spotting that the whitespace between ' and "then" can be deleted. Thanks!

recursive's Stax solution of using string indexes is very very good. If you port it to F# you can get it for 77 bytes:

let s c=Seq.sumBy(fun x->"123456789/???????????????????X".IndexOf(char x)+1)c

Try this online!

Ciaran_McCarthy

Posted 2018-03-26T04:08:10.933

Reputation: 689

1I don't know F# very well, but it seems you can drop the spaces after ' for -3 bytes. – Kevin Cruijssen – 2018-03-26T12:32:12.017

Neither do I! But you're right, well spotted! Thanks! – Ciaran_McCarthy – 2018-03-26T13:28:15.903

2@Ciaran_McCarthy: I don't mind if you copy my solution if you'd like to include it also. People here are generally pretty open about this kind of thing. It's a co-operative effort to find all the smallest code, even if it's formally a competition. – recursive – 2018-03-26T14:16:10.970

1Thanks recursive. I'll include it then, because it's a very nice solution, and it's interesting to see how it looks in different languages. – Ciaran_McCarthy – 2018-03-26T17:47:13.327

3

Python 2, 55 bytes

lambda l:sum(map(('123456789/'+'X'*20).rfind,l))+len(l)

Try it online!

Based off the string-index approach of many solutions.

xnor

Posted 2018-03-26T04:08:10.933

Reputation: 115 687

2

Jelly, 17 bytes

ḟ⁾ -“X0/⁵”yV€o30S

A monadic link accepting a list of characters and returning an integer

Try it online!

How?

ḟ⁾ -“X0/⁵”yV€o30S - Link: list of characters
 ⁾ -              - literal list of characters [' ','-']
ḟ                 - filter discard
    “X0/⁵”        - literal list of characters ['X','0','/','⁵']
          y       - translate (change 'X's to '0's and '/'s to '⁵'s)
           V€     - evaluate €ach character as Jelly code (the '⁵'s become 10s)
             o30  - logical OR with 30 (change all instances of 0 to 30)
                S - sum

Also at 17:

”/ẋ20ØD;;”XḊiЀ⁸S

Try that

Jonathan Allan

Posted 2018-03-26T04:08:10.933

Reputation: 67 804

2

Perl 5 -pF, 30 27 bytes

-3 bytes thanks to Xcali

#!/usr/bin/perl -pF
$\+=m%/%+3*/X/.0+$_ for@F}{

Try it online!

Ton Hospel

Posted 2018-03-26T04:08:10.933

Reputation: 14 114

You can cut two bytes by using /X/ instead of y/X// and one more by using m%/% instead of y%/%%: Try it online!

– Xcali – 2018-03-26T15:22:00.363

@Xcali Ah, of course. Classic golf shortsightedness, I was still thinking in terms of y/// from when I did them outside a loop. Thanks – Ton Hospel – 2018-03-26T17:51:55.580

2

Perl 6, 30 bytes

{TR'-/X '0㉈㉊'.univals.sum}

Try it online!

nwellnhof

Posted 2018-03-26T04:08:10.933

Reputation: 10 037

2

Retina, 17 bytes

X
///
/
55
\d
*
_

Try it online!

I'm not quite up to date on the latest Retina changes. I'll be looking more into them when I get a chance and seeing if there's any new tricks for golfing this down. The code turns all strikes in to three spares, all spares into ten points, then all points to the corresponding number of underscores. Then it counts the number of underscores.

PunPun1000

Posted 2018-03-26T04:08:10.933

Reputation: 973

1

PHP, 119 109 bytes

-10 bytes thanks to @KevinCruijssen

<?foreach(explode(" ",$argv[1])as$f){[$a,$b]=str_split($f);$n+=$f==X?30:(int)$a+($b=='/'?10:(int)$b);}echo$n;

Try it online!

Jo.

Posted 2018-03-26T04:08:10.933

Reputation: 974

You can change ($b=='/'?10+(int)$a:((int)$a+(int)$b)) to (int)$a+($b=='/'?10:(int)$b) for -10 bytes. – Kevin Cruijssen – 2018-03-26T12:35:48.360

@KevinCruijssen Thanks, looks good! Although, looking at the other answers, looks like I am going about this the wrong/long way. :) – Jo. – 2018-03-27T06:11:57.620

1

05AB1E, 14 bytes

þ`I…/aXS¢ƶT*`O

Try it online!

Explanation

þ`              # Push the digits of the input on the stack (removes everyting that isn't a digit)
  I…/aXS        # Push the input and the array "/","a","X" on the stack
        ¢       # Index of each element in the input ...
         ƶT*    # ... multiplied by its index (a could be anything that can't be found in the input), multiplied by 10.
            `O  # Sum the stack, implicit display

Kaldo

Posted 2018-03-26T04:08:10.933

Reputation: 1 135

1

Python 2, 67 bytes

-3 bytes thanks to @KevinCruijssen

lambda I,p='-123456789/'+20*'X':sum(p.rfind(i)for i in I if i in p)

Try it online!

Dead Possum

Posted 2018-03-26T04:08:10.933

Reputation: 3 256

1You can save 3 bytes by changing '-123456789'+'/'*20+'X':sum(p.index(i) to '-123456789/'+'X'*20:sum(p.rfind(i) – Kevin Cruijssen – 2018-03-26T12:38:42.433

@KevinCruijssen thanks, good one! – Dead Possum – 2018-03-26T13:09:00.393

1

J, 33 bytes

1#.31|('-123456789',20 1#'/X')i.]

Try it online!

Explanation:

] the input

('-123456789',20 1#'/X') appends 20 / and one X to the string -123456789

i. finds the indices of the input in the above string

31| modulo 31 - to get rid of the spaces - they are not found in the string, so i. returns 31 for them

1#. finds the sum of the indices

Galen Ivanov

Posted 2018-03-26T04:08:10.933

Reputation: 13 815

Since J and Red are two completely different languages, it's better to post two separated answers, even though they might do the same. You can add a link from the Red answer to this J answer, stating it's a port of your J answer. – Kevin Cruijssen – 2018-03-26T12:14:55.287

@Kevin Cruijssen - Ok, thanks - I'll do it. The reason for posting them together is that obviously the Red solution is non-competitive (although much readable :) ) – Galen Ivanov – 2018-03-26T12:19:01.497

1

Jelly, 12 bytes

⁾/X,“½œ‘y|0S

Try it online!

How it works

⁾/X,“½œ‘y|0S  Main link. Argument: s (string)

⁾/X,“½œ‘      Literal; yield [['/', 'X'], [10, 30]].
        y     Transliterate; replace '/' with 10, 'X' with 30.
         |0   Bitwise OR with 0. Bitwise operators attempt to cast to int, mapping 
              '0', ..., '9' to 0, ..., 9. All other characters map to 0.
           S  Take the sum.

Dennis

Posted 2018-03-26T04:08:10.933

Reputation: 196 637

1

Python 3.6, 54 bytes

lambda s:sum(map(f'123456789/{6**24}X'.find,s),len(s))

Try it online!

Dennis

Posted 2018-03-26T04:08:10.933

Reputation: 196 637

1

Kotlin, 50 bytes

x->x.sumBy{"123456789/_${Math.E}_X".indexOf(it)+1}

Try it online!

Hopefully it's not against the rules to answer your own question but I wanted to join in on the fun.

Math.E produces the value 2.718281828459045. I'm using it to create some filler string to push X to position 30.

indexOf gets the position (0-based) of the character in the string "12345...". If it isn't found, it returns -1. We add 1 to make these 0, and that also makes the 0-based position the value of the string.

Makotosan

Posted 2018-03-26T04:08:10.933

Reputation: 503

0

Charcoal, 23 bytes

IΣEχΣES⎇№-/Xλ×χ⌕-//XλIλ

Try it online! Link is to verbose version of code. Explanation:

  Eχ                    Map over 10 frames
      S                 Input the frame
     E                  Map over the characters
            λ           Current character
        №-/X            Search the literal string `-/X`
                    λ   Current character
               ⌕-//X    Find position in literal string `-//X`
             ×χ         Multiply by predefined variable 10
                      λ Current character
                     I  Cast to integer
       ⎇                Ternary
    Σ                   Sum for each frame
 Σ                      Sum over frames
I                       Cast to string for implicit print

Neil

Posted 2018-03-26T04:08:10.933

Reputation: 95 035

0

Red, 93 bytes

func[b][s: 0
foreach c trim/all b[s: s - 1 + index? find{-123456789/...................X}c]s]

Try it online!

Galen Ivanov

Posted 2018-03-26T04:08:10.933

Reputation: 13 815

0

SNOBOL4 (CSNOBOL4), 169 151 147 bytes

	F =INPUT ' '
R	F '-' =0	:S(R)
T	F 'X' ='_'	:S(T)
S	F LEN(1) . X ARB . Y ' ' REM . F	:F(O)
	X '_' =30
	Y '/' =10
	S =S + X + Y	:(S)
O	OUTPUT =S
END

Try it online!

	F =INPUT ' '					;* read input and append a space
R	F '-' =0	:S(R)				;* replace - with 0
T	F 'X' ='_'	:S(T)				;* replace X with _
S	F LEN(1) . X ARB . Y ' ' REM . F	:F(O)	;* set first character to x, remainder up to ' ' to y, and remainder to F
	X '_' =20					;* replace _ in x with 20
	Y '/' =10					;* replace / in y with 10
	S =S + X + Y	:(S)				;* else X and Y are their values so we can sum them
O	OUTPUT =S					;* output the sum
END

Giuseppe

Posted 2018-03-26T04:08:10.933

Reputation: 21 077

0

C# (.NET Core), 40 + 18 = 58 bytes

using System.Linq;
s=>s.Sum(c=>c>87?30:c<46?0:c<48?10:c-48)

Try it online!

raznagul

Posted 2018-03-26T04:08:10.933

Reputation: 424

0

C# (Visual C# Interactive Compiler), 53 bytes

Write(ReadLine().Sum(c=>c>87?30:c<46?0:c<48?10:c-48))

Try it online!

raznagul

Posted 2018-03-26T04:08:10.933

Reputation: 424

0

Clojure, 70 bytes

#(reduce(fn[s i](+ s(case i\- 0\/ 10\X 30\space 0(bigint(str i)))))0%)

Try it online!

When reduceing over a String, each char is actually converted into a char - who would have thought. But this meany, I have to write \space and that hurts more than one can imagine. Also, when creating an actual number from a char, the combination of bigint and str seems to be the only usable combination.

Well, aside from all these struggles: Anonymous function that returns the score as a natural.

Joshua

Posted 2018-03-26T04:08:10.933

Reputation: 231

0

Ruby, 38 bytes

->s{s.tr("/X -",":N00").sum-s.size*48}

Try it online!

Reinstate Monica -- notmaynard

Posted 2018-03-26T04:08:10.933

Reputation: 1 053