Skip to content

Instantly share code, notes, and snippets.

@pepasflo

pepasflo/bowling.py

Created Aug 29, 2018
Embed
What would you like to do?
Bowling scoring system
#!/usr/bin/env python
import sys
import json
# Safe access for lists.
def lget(l, i, default=None):
if len(l) <= i:
return default
else:
return l[i]
# Safe access for lists of integers.
def iget(l, i):
return lget(l, i, 0)
# Transform a list of frames into a list of pinfalls.
# e.g. [['X'],[1,'/'],[4,3]] -> [10, 1, 9, 4, 3]
def pinfalls(frames):
falls = []
for frame in frames:
for ball in frame:
if ball == 'X':
falls.append(10)
elif ball == '/':
falls.append(10 - falls[-1])
else:
falls.append(int(ball))
return falls
# Return the score of the first frame in the given list of frames.
# e.g. [['X'],[2,3]] -> 15
def single_frame_score(frames):
frame = frames[0]
falls = pinfalls(frames)
if lget(frame, 0) == 'X' or lget(frame, 1) == '/' or len(frame) == 3:
return iget(falls, 0) + iget(falls, 1) + iget(falls, 2)
elif len(frame) == 2:
return iget(falls, 0) + iget(falls, 1)
else:
return iget(falls, 0)
# Compute the (individual) scores of the given frames.
# e.g. [['X'],[2,3]] -> [15,5]
def frame_scores(frames):
if len(frames) == 0:
return []
elif len(frames) == 1:
return [single_frame_score(frames)]
else:
return [single_frame_score(frames)] + frame_scores(frames[1:])
# Compute the running totals for the given set of frame scores.
# e.g. [1,2,3] -> [1,3,6]
def running_totals(scores):
if len(frames) == 0:
return []
sum = 0
totals = []
for score in scores:
sum += score
totals.append(sum)
return totals
if __name__ == "__main__":
if len(sys.argv) > 1:
# if a command-line argument was given, assume that is a JSON array of frames.
input_string = sys.argv[1]
else:
# else, read the JSON array of frames from stdin.
input_string = sys.stdin.read()
frames = json.loads(input_string)
scores = running_totals(frame_scores(frames))
# print out the scores as JSON to stdout.
print json.dumps(scores)
$ ./test.py
πŸ‘‰ Test case: full-game-all-strikes.json
πŸ‘‰ Running ./bowling.py '[["X"], ["X"], ["X"], ["X"], ["X"], ["X"], ["X"], ["X"], ["X"], ["X", "X", "X"]]'
βœ… Passed: [30, 60, 90, 120, 150, 180, 210, 240, 270, 300]
πŸ‘‰ Test case: full-game-no-score.json
πŸ‘‰ Running ./bowling.py '[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]'
βœ… Passed: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
πŸ‘‰ Test case: ncna2013-example1.json
πŸ‘‰ Running ./bowling.py '[[4, "/"], [6, 2], [4, 2], [8, 1], ["X"], [6, 2], [7, 2], [9, 0], [0, 5], [6, 3]]'
βœ… Passed: [16, 24, 30, 39, 57, 65, 74, 83, 88, 97]
πŸ‘‰ Test case: ncna2013-example2.json
πŸ‘‰ Running ./bowling.py '[[4, "/"], [6, 2], [4, 2], [8, 1], ["X"], [6, 2], [7, 2], [9, 0], [0, 5], [6, 3]]'
βœ… Passed: [16, 24, 30, 39, 57, 65, 74, 83, 88, 97]
πŸ‘‰ Test case: ncna2013-example3.json
πŸ‘‰ Running ./bowling.py '[[4, "/"], [6, 2], [4, 2], [8, 1], ["X"], ["X"], [7, 2], [9, 0], [0, 5], [6, 3]]'
βœ… Passed: [16, 24, 30, 39, 66, 85, 94, 103, 108, 117]
πŸ‘‰ Test case: ncna2013-example4.json
πŸ‘‰ Running ./bowling.py '[[4, "/"], [6, 2], [4, 2], [8, 1], ["X"], ["X"], [7, 2], [9, 0], [0, 5], [6, "/", 7]]'
βœ… Passed: [16, 24, 30, 39, 66, 85, 94, 103, 108, 125]
πŸ‘‰ Test case: wikipedia-example1.json
πŸ‘‰ Running ./bowling.py '[["X"], [3, 6]]'
βœ… Passed: [19, 28]
πŸ‘‰ Test case: wikipedia-example2.json
πŸ‘‰ Running ./bowling.py '[["X"], ["X"], [9, 0]]'
βœ… Passed: [29, 48, 57]
πŸ‘‰ Test case: wikipedia-example3.json
πŸ‘‰ Running ./bowling.py '[["X"], ["X"], ["X"], [8, "/"], [8, 0]]'
βœ… Passed: [30, 58, 78, 96, 104]
πŸ‘‰ Test case: wikipedia-example4.json
πŸ‘‰ Running ./bowling.py '[["X"], ["X"], ["X"], ["X"], ["X"], [7, 2]]'
βœ… Passed: [30, 60, 90, 117, 136, 145]
#!/usr/bin/env python
# encoding: utf-8
import sys
import os
import subprocess
import json
# replace this with the name of your program.
exe = "./bowling.py"
for test_fname in sorted(os.listdir("tests")):
# parse out the test case JSON:
j = json.load(open("tests/%s" % test_fname))
frames = j["frames"]
expected_scores = j["scores"]
# feed this test case to the scoring program:
exe_input = json.dumps(frames)
sys.stdout.write("\n")
sys.stdout.write("πŸ‘‰ Test case: %s\n" % test_fname)
sys.stdout.write(" πŸ‘‰ Running %s '%s'\n" % (exe, exe_input))
sys.stdout.flush()
output = subprocess.check_output([exe, exe_input]).rstrip()
try:
computed_scores = json.loads(output)
except Exception as e:
sys.stderr.write(" ❌ Failed to decode output JSON: '%s'\n" % output)
raise e
# compare the computed scores to the actual scores:
if expected_scores == computed_scores:
sys.stdout.write(" βœ… Passed: %s\n" % computed_scores)
else:
sys.stderr.write(" ❌ Failed: %s\n" % test_fname)
sys.stderr.write(" expected output: %s\n" % expected_scores)
sys.stderr.write(" actual output: %s\n" % computed_scores)
sys.exit(1)
{
"frames": [["X"],["X"],["X"],["X"],["X"],["X"],["X"],["X"],["X"],["X","X","X"]],
"scores": [30,60,90,120,150,180,210,240,270,300]
}
{
"frames": [[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],
"scores": [0,0,0,0,0,0,0,0,0,0]
}
{
"frames": [[4,"/"],[6,2],[4,2],[8,1],["X"],[6,2],[7,2],[9,0],[0,5],[6,3]],
"scores": [16,24,30,39,57,65,74,83,88,97],
"comment": "https://ncna-region.unl.edu/regional_2013_final.pdf"
}
{
"frames": [[4,"/"],[6,2],[4,2],[8,1],["X"],[6,2],[7,2],[9,0],[0,5],[6,3]],
"scores": [16,24,30,39,57,65,74,83,88,97],
"comment": "https://ncna-region.unl.edu/regional_2013_final.pdf"
}
{
"frames": [[4,"/"],[6,2],[4,2],[8,1],["X"],["X"],[7,2],[9,0],[0,5],[6,3]],
"scores": [16,24,30,39,66,85,94,103,108,117],
"comment": "https://ncna-region.unl.edu/regional_2013_final.pdf"
}
{
"frames": [[4,"/"],[6,2],[4,2],[8,1],["X"],["X"],[7,2],[9,0],[0,5],[6,"/",7]],
"scores": [16,24,30,39,66,85,94,103,108,125],
"comment": "https://ncna-region.unl.edu/regional_2013_final.pdf"
}
{
"frames": [["X"],[3,6]],
"scores": [19,28],
"comment": "https://en.wikipedia.org/wiki/Ten-pin_bowling#Traditional_scoring"
}
{
"frames": [["X"],["X"],[9,0]],
"scores": [29,48,57],
"comment": "https://en.wikipedia.org/wiki/Ten-pin_bowling#Traditional_scoring"
}
{
"frames": [["X"],["X"],["X"],[8,"/"],[8,0]],
"scores": [30,58,78,96,104],
"comment": "https://en.wikipedia.org/wiki/Ten-pin_bowling#Traditional_scoring"
}
{
"frames": [["X"],["X"],["X"],["X"],["X"],[7,2]],
"scores": [30,60,90,117,136,145],
"comment": "https://en.wikipedia.org/wiki/Ten-pin_bowling#Traditional_scoring"
}
@pepasflo

This comment has been minimized.

Copy link
Owner Author

@pepasflo pepasflo commented Aug 29, 2018

oops, I accidentally made tests_ncna2013-example1.json and tests_ncna2013-example2.json the same.

tests_ncna2013-example1.json should instead be:

{
    "frames": [[4,3],[6,2],[4,2],[8,1],[4,5],[6,2],[7,2],[9,0],[0,5],[6,3]],
    "scores": [7,15,21,30,39,47,56,65,70,79],
    "comment": "https://ncna-region.unl.edu/regional_2013_final.pdf"
}
@pepasflo

This comment has been minimized.

Copy link
Owner Author

@pepasflo pepasflo commented Aug 29, 2018

Note: you can re-use test.py and the test cases for your own implementation. Just edit the line which says exe = './bowling.py'. Design your implementation to accept a single command-line argument which is a JSON string describing the frames, and output a JSON string which describes the (running total) frame scores.

Note that test.py actually expects the test JSON files to be in a directory called tests.

To conveniently TDD your implementation, run this in a terminal: while true; do sleep 1; clear; ./test.py; done. Each time you save your source file, check the terminal to see if the tests passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment