Ten-pin bowling score - World Bowling edition

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
• I'm disappointed this isn't a code-bowling challenge – Jo King Mar 26 '18 at 4:49
• Your first spare example says the score would be 13, but I think it is supposed to be 17. – Jo. Mar 26 '18 at 4:51
• @Jo. Good catch. I've updated the question to fix that error. – Makotosan Mar 26 '18 at 12:42
• @JoKing I was thinking this is a code-bowling challenge composed of 10 subchallenges when I first saw the title. – Weijun Zhou Mar 27 '18 at 3:36
• One of the best documented and written challenges I have seen. – Joshua Mar 27 '18 at 15:36

05AB1E, 12 11 bytes

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)

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
• Can you explain your code? looks really good – Luis felipe De jesus Munoz Mar 26 '18 at 12:57
• @LuisfelipeDejesusMunoz Just added some. – tsh Mar 27 '18 at 2:37

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 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. Java 8, 6459 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 • ("123456789//"+1e6+1e6+"X") seems to save 5 bytes. – Neil Mar 26 '18 at 12:04 • That's a clever technique to create some filler string. – Makotosan Mar 26 '18 at 13:47 • 46 bytes – Olivier Grégoire Mar 26 '18 at 16:39 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! • I don't know F# very well, but it seems you can drop the spaces after ' for -3 bytes. – Kevin Cruijssen Mar 26 '18 at 12:32 • Neither do I! But you're right, well spotted! Thanks! – Ciaran_McCarthy Mar 26 '18 at 13:28 • @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 Mar 26 '18 at 14:16 • Thanks 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 Mar 26 '18 at 17:47 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 Perl 6, 30 bytes {TR'-/X '0㉈㉊'.univals.sum} Try it online! 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. Perl 5-pF, 30 27 bytes -3 bytes thanks to Xcali #!/usr/bin/perl -pF$\+=m%/%+3*/X/.0+$_ for@F}{ Try it online! • 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 Mar 26 '18 at 15:22 • @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 Mar 26 '18 at 17:51 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 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 • 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 Mar 26 '18 at 12:14 • @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 Mar 26 '18 at 12:19 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! • You can save 3 bytes by changing '-123456789'+'/'*20+'X':sum(p.index(i) to '-123456789/'+'X'*20:sum(p.rfind(i) – Kevin Cruijssen Mar 26 '18 at 12:38 • @KevinCruijssen thanks, good one! – Dead Possum Mar 26 '18 at 13:09 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. Python 3.6, 54 bytes lambda s:sum(map(f'123456789/{6**24}X'.find,s),len(s)) Try it online! 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.

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! • You can change ($b=='/'?10+(int)$a:((int)$a+(int)$b)) to (int)$a+($b=='/'?10:(int)$b) for -10 bytes. – Kevin Cruijssen Mar 26 '18 at 12:35
• @KevinCruijssen Thanks, looks good! Although, looking at the other answers, looks like I am going about this the wrong/long way. :) – Jo. Mar 27 '18 at 6:11

Charcoal, 23 bytes

ＩΣＥχΣＥＳ⎇№-/Xλ×χ⌕-//XλＩλ

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

Ｅχ                    Map over 10 frames
Ｓ                 Input the frame
Ｅ                  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
Ｉ  Cast to integer
⎇                Ternary
Σ                   Sum for each frame
Σ                      Sum over frames
Ｉ                       Cast to string for implicit print

Red, 93 bytes

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

Try it online!

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!

Try it online!

SNOBOL4 (CSNOBOL4), 169151 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

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.

Ruby, 38 bytes

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

Try it online!