{{ message }}

Instantly share code, notes, and snippets.

pepasflo/bowling.py

Created Aug 29, 2018
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 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 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.