| 1 | 1 | class BowlingGame: |
| 2 | 2 | def __init__(self): |
| 3 | | pass |
| 3 | self.rolls = [] |
| 4 | self.frames = [] # Store completed frames |
| 5 | self.current_frame_rolls = [] # Rolls in current frame |
| 6 | self.current_frame = 0 |
| 7 | self.is_finished = False |
| 4 | 8 | |
| 5 | 9 | def roll(self, pins): |
| 6 | | pass |
| 7 | | |
| 10 | # Edge Case: Game already finished |
| 11 | if self.is_finished: |
| 12 | raise ValueError("cannot roll after game is over") |
| 13 | |
| 14 | # Edge Case: Invalid pin count (negative) |
| 15 | if pins < 0: |
| 16 | raise ValueError("negative roll is invalid") |
| 17 | |
| 18 | # Edge Case: Invalid pin count (more than 10) |
| 19 | if pins > 10: |
| 20 | raise ValueError("pin count exceeds pins on the lane") |
| 21 | |
| 22 | # Handle rolls based on current frame |
| 23 | if self.current_frame < 9: # Frames 0-8 (regular frames) |
| 24 | if len(self.current_frame_rolls) == 0: # First roll of frame |
| 25 | self.current_frame_rolls.append(pins) |
| 26 | self.rolls.append(pins) |
| 27 | # Strike - complete frame immediately |
| 28 | if pins == 10: |
| 29 | self.frames.append([pins]) |
| 30 | self.current_frame_rolls = [] |
| 31 | self.current_frame += 1 |
| 32 | else: # Second roll of frame |
| 33 | # Validate that sum doesn't exceed 10 |
| 34 | if self.current_frame_rolls[0] + pins > 10: |
| 35 | raise ValueError("pin count exceeds pins on the lane") |
| 36 | |
| 37 | self.current_frame_rolls.append(pins) |
| 38 | self.rolls.append(pins) |
| 39 | self.frames.append(self.current_frame_rolls) |
| 40 | self.current_frame_rolls = [] |
| 41 | self.current_frame += 1 |
| 42 | else: # Frame 9 (10th frame) - special rules |
| 43 | # Validate fill balls according to bowling rules |
| 44 | if len(self.current_frame_rolls) == 0: |
| 45 | # First roll - any value 0-10 is valid |
| 46 | pass |
| 47 | elif len(self.current_frame_rolls) == 1: |
| 48 | # Second roll validation |
| 49 | if self.current_frame_rolls[0] == 10: # First roll was strike |
| 50 | # After a strike, second roll can be 0-10 |
| 51 | pass |
| 52 | else: |
| 53 | # After a non-strike first roll, second roll + first roll cannot exceed 10 |
| 54 | if self.current_frame_rolls[0] + pins > 10: |
| 55 | raise ValueError("pin count exceeds pins on the lane") |
| 56 | elif len(self.current_frame_rolls) == 2: |
| 57 | # Third roll validation (fill ball) |
| 58 | if self.current_frame_rolls[0] == 10: # First roll was strike |
| 59 | if self.current_frame_rolls[1] == 10: # Second roll was also strike |
| 60 | # After two strikes, third roll can be 0-10 |
| 61 | pass |
| 62 | else: |
| 63 | # After strike + non-strike, third roll + second roll cannot exceed 10 |
| 64 | if self.current_frame_rolls[1] + pins > 10: |
| 65 | raise ValueError("invalid fill balls") |
| 66 | elif self.current_frame_rolls[0] + self.current_frame_rolls[1] == 10: # First two rolls were spare |
| 67 | # After a spare, third roll can be 0-10 |
| 68 | pass |
| 69 | |
| 70 | self.current_frame_rolls.append(pins) |
| 71 | self.rolls.append(pins) |
| 72 | |
| 73 | # Check if 10th frame is complete |
| 74 | if len(self.current_frame_rolls) == 3: # Three rolls completed |
| 75 | self.frames.append(self.current_frame_rolls) |
| 76 | self.current_frame_rolls = [] |
| 77 | self.current_frame += 1 |
| 78 | self.is_finished = True |
| 79 | elif len(self.current_frame_rolls) == 2: # Two rolls completed |
| 80 | # Check if frame is open (less than 10 pins) |
| 81 | frame_sum = sum(self.current_frame_rolls) |
| 82 | if frame_sum < 10: |
| 83 | self.frames.append(self.current_frame_rolls) |
| 84 | self.current_frame_rolls = [] |
| 85 | self.current_frame += 1 |
| 86 | self.is_finished = True |
| 87 | # If it's a spare or strike, need third roll - continue |
| 88 | elif len(self.current_frame_rolls) == 1: # First roll completed |
| 89 | # If first roll is a strike, need two more rolls |
| 90 | # Just continue to next roll |
| 91 | pass |
| 92 | |
| 8 | 93 | def score(self): |
| 9 | | pass |
| 94 | # Edge Case: Game not finished yet |
| 95 | if not self.is_finished: |
| 96 | raise ValueError("Score cannot be taken until the end of the game") |
| 97 | |
| 98 | total_score = 0 |
| 99 | |
| 100 | # Score all frames (0-9) |
| 101 | for frame_num in range(len(self.frames)): |
| 102 | frame = self.frames[frame_num] |
| 103 | frame_score = sum(frame) |
| 104 | |
| 105 | if frame_num < 9: # Frames 0-8 (regular frames) |
| 106 | if len(frame) == 1 and frame[0] == 10: # Strike |
| 107 | # Add bonus: next two rolls |
| 108 | next_rolls_sum = 0 |
| 109 | rolls_counted = 0 |
| 110 | |
| 111 | # Look for next rolls in subsequent frames |
| 112 | for next_frame_num in range(frame_num + 1, len(self.frames)): |
| 113 | next_frame = self.frames[next_frame_num] |
| 114 | for roll in next_frame: |
| 115 | next_rolls_sum += roll |
| 116 | rolls_counted += 1 |
| 117 | if rolls_counted == 2: |
| 118 | break |
| 119 | if rolls_counted == 2: |
| 120 | break |
| 121 | |
| 122 | frame_score += next_rolls_sum |
| 123 | |
| 124 | elif len(frame) == 2 and sum(frame) == 10: # Spare |
| 125 | # Add bonus: next one roll |
| 126 | next_roll = 0 |
| 127 | |
| 128 | # Look for next roll in subsequent frames |
| 129 | for next_frame_num in range(frame_num + 1, len(self.frames)): |
| 130 | next_frame = self.frames[next_frame_num] |
| 131 | if len(next_frame) > 0: |
| 132 | next_roll = next_frame[0] |
| 133 | break |
| 134 | |
| 135 | frame_score += next_roll |
| 136 | |
| 137 | total_score += frame_score |
| 138 | |
| 139 | return total_score |