# # An a little over the top Bowling Score app. # Copyright 2022 by Matthew Denson # # Example Usage: # To score a set of bowls provided in JSON format in stdin. # $ echo "[1,4,4,5,6,4,5,5,10,0,1,7,3,6,4,10,2,8,9]" | python3 bowling.py stdin # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ # | 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ # |1 4|4 5|6 /|5 /| X|0 1|7 /|6 /| X|2 / 9| # | | | | | | | | | | | # | 5 | 14 | 29 | 49 | 60 | 61 | 77 | 97 | 117 | 136 | # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ # # To score a game interactively # $ python3 bowling.py play # Let's play! # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ # | 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ # | | | | | | | | | | | # | | | | | | | | | | | # | | | | | | | | | | | # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ # Your next roll: (0-10,x,X) # 8 # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ # | 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ # |8 | | | | | | | | | | # | | | | | | | | | | | # | | | | | | | | | | | # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ # Your next roll: (0-2,/) # / # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ # | 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ # |8 /| | | | | | | | | | # | | | | | | | | | | | # | | | | | | | | | | | # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ # Your next roll: (0-10,x,X) # ..... import argparse import json import sys def score_game(pins): return score_frame([], pins) def score_frame(running_score, pins): frame_num = len(running_score) + 1 #terminal cases if len(pins) == 0: return running_score elif frame_num > 10: running_score[9]['pins'].extend(pins) return running_score if frame_num == 1: prev_score = 0 else: prev_score = running_score[len(running_score) - 1]['score'] if len(pins) > 0 and pins[0] == 10: # strike if prev_score == None or len(pins) < 3: score = None else: score = prev_score + pins[0] + pins[1] + pins[2] running_score.append({"frame": frame_num, "pins": pins[:1], "score": score}) return score_frame(running_score, pins[1:]) elif len(pins) > 1 and pins[0] + pins[1] == 10: # spare if prev_score == None or len(pins) < 3: score = None else: score = prev_score + pins[0] + pins[1] + pins[2] running_score.append({"frame": frame_num, "pins": pins[:2], "score": score}) return score_frame(running_score, pins[2:]) else: # open if prev_score == None or len(pins) < 2: score = None else: score = prev_score + pins[0] + pins[1] running_score.append({"frame": frame_num, "pins": pins[:2], "score": score}) return score_frame(running_score, pins[2:]) def print_game(frames): top_bottom = "" num = "" bowls = "" space = "" scores = "" for f in frames: top_bottom = top_bottom + "+-----" num = num + "| {frame:>2}".format(frame = f["frame"]) pins = f["pins"] if f["frame"] == 10: # 10th frame (special) pin_str = "" sum = 0 for p in pins: sum = sum + p if p == 10: pin_str = pin_str + "X" sum = 0 elif sum == 10: pin_str = pin_str + "/" sum = 0 else: pin_str = pin_str + str(p) pin_str = pin_str + " " pin_str = pin_str.strip().ljust(5) bowls = bowls + "|" + pin_str elif len(pins) == 1: if pins[0] == 10: bowls = bowls + "| X" else: bowls = bowls + "|" + str(pins[0]) + " " else: if pins[0] + pins[1] == 10: bowls = bowls + "|" + str(pins[0]) + " /" else: bowls = bowls + "|" + str(pins[0]) + " " + str(pins[1]) space = space + "| " if f["score"]: scores = scores + "| {score:^3} ".format(score = f["score"]) else: scores = scores + "| " for f in range(len(frames)+1, 11): top_bottom = top_bottom + "+-----" num = num + "| {frame:>2}".format(frame = f) bowls = bowls + "| " space = space + "| " scores = scores + "| " print(top_bottom + "+") print(num + "|") print(top_bottom + "+") print(bowls + "|") print(space + "|") print(scores + "|") print(top_bottom + "+") def from_stdin(args): print_game(score_game(json.load(sys.stdin))) def interactive_game(args): print("Let's play!") frame_pins = 0 curr_frame = 1 pins = [] done = False while not done: score = score_game(pins) print_game(score) if (len(score) == 10 and score[9]["score"]): done = True else: if len(score) > 0 and score[len(score) - 1]['score'] or frame_pins == 10: frame_pins = 0 first_roll = True elif len(score) > 0: first_roll = False else: first_roll = True keep_reading = True if (first_roll): allowed = "0-10,x,X"; else: max = 10 - frame_pins; allowed = "0-" + str(max) + ",/"; while keep_reading: print("Your next roll: (" + allowed + ")") count = 0 roll = input() if roll in ['0','1','2','3','4','5','6','7','8','9','10','/','x','X']: if roll == '/' and frame_pins > 0: count = 10 - frame_pins keep_reading = False elif roll == 'x' or roll == 'X': if frame_pins == 0: count = 10 keep_reading = False else: count = int(roll) if count + frame_pins <=10: keep_reading = False pins.append(count) frame_pins = frame_pins + count parser = argparse.ArgumentParser() parser.add_argument('--version', action='version', version='1.0.0') subparsers = parser.add_subparsers() stdin_parser = subparsers.add_parser('stdin') stdin_parser.set_defaults(func=from_stdin) play_parser = subparsers.add_parser('play') play_parser.set_defaults(func=interactive_game) if __name__ == '__main__': args = parser.parse_args() args.func(args)