| 1 | 1 | class BowlingGame: |
| 2 | 2 | def __init__(self): |
| 3 | | pass |
| 3 | self.rolls = [] |
| 4 | self.frame_scores = [] |
| 5 | self.current_frame = 0 |
| 6 | self.frame_rolls = [] |
| 7 | self.game_complete = False |
| 4 | 8 | |
| 5 | 9 | def roll(self, pins): |
| 6 | | pass |
| 10 | # Edge Case: Game is already complete |
| 11 | if self.game_complete: |
| 12 | raise ValueError("game already complete") |
| 13 | |
| 14 | # Edge Case: Invalid pin count (negative or more than 10) |
| 15 | if pins < 0 or pins > 10: |
| 16 | raise ValueError("invalid pin count") |
| 17 | |
| 18 | # Edge Case: Too many pins for current frame state |
| 19 | if len(self.frame_rolls) == 1: |
| 20 | # Check if this is the 10th frame and first roll was a strike |
| 21 | if self.current_frame == 9 and self.frame_rolls[0] == 10: |
| 22 | # In 10th frame after a strike, second roll can be up to 10 pins |
| 23 | pass |
| 24 | # Check if pins exceed remaining pins |
| 25 | elif self.frame_rolls[0] + pins > 10: |
| 26 | raise ValueError("invalid pin count") |
| 27 | |
| 28 | self.frame_rolls.append(pins) |
| 29 | self.rolls.append(pins) |
| 30 | |
| 31 | # Handle frame completion |
| 32 | if self.current_frame < 9: # Frames 1-9 |
| 33 | # Strike frame (only one roll) |
| 34 | if len(self.frame_rolls) == 1 and pins == 10: |
| 35 | self.current_frame += 1 |
| 36 | self.frame_rolls = [] |
| 37 | # Regular frame (two rolls) |
| 38 | elif len(self.frame_rolls) == 2: |
| 39 | self.current_frame += 1 |
| 40 | self.frame_rolls = [] |
| 41 | else: # 10th frame |
| 42 | # Edge Case: Invalid fill balls |
| 43 | if len(self.frame_rolls) == 2: |
| 44 | # If first two rolls are strikes or strike + non-strike |
| 45 | if self.frame_rolls[0] == 10: |
| 46 | pass # Need third roll |
| 47 | # If first two rolls are spare |
| 48 | elif self.frame_rolls[0] + self.frame_rolls[1] == 10: |
| 49 | pass # Need third roll |
| 50 | # If open frame, no third roll allowed |
| 51 | else: |
| 52 | self.game_complete = True |
| 53 | elif len(self.frame_rolls) == 3: |
| 54 | # Validate third roll based on first two |
| 55 | if self.frame_rolls[0] == 10: # First roll was strike |
| 56 | if self.frame_rolls[1] != 10: # Second roll wasn't strike |
| 57 | # Third roll can't exceed remaining pins |
| 58 | if self.frame_rolls[1] + self.frame_rolls[2] > 10: |
| 59 | raise ValueError("invalid fill balls") |
| 60 | # If second roll was also strike, third roll is valid (0-10) |
| 61 | elif self.frame_rolls[0] + self.frame_rolls[1] == 10: # Spare |
| 62 | # Third roll is valid (0-10) |
| 63 | pass |
| 64 | else: |
| 65 | # Should not be possible to have 3 rolls in 10th frame without strike/spare |
| 66 | raise ValueError("invalid fill balls") |
| 67 | self.game_complete = True |
| 7 | 68 | |
| 8 | 69 | def score(self): |
| 9 | | pass |
| 70 | # Edge Case: Score called before game is complete |
| 71 | if not self.game_complete: |
| 72 | raise IndexError("game not complete") |
| 73 | |
| 74 | total_score = 0 |
| 75 | roll_index = 0 |
| 76 | |
| 77 | for frame in range(10): |
| 78 | # Edge Case: Not enough rolls to calculate score |
| 79 | if roll_index >= len(self.rolls): |
| 80 | raise IndexError("game not complete") |
| 81 | |
| 82 | if frame < 9: # Frames 1-9 |
| 83 | # Strike |
| 84 | if self.rolls[roll_index] == 10: |
| 85 | # Edge Case: Not enough rolls for strike bonus |
| 86 | if roll_index + 2 >= len(self.rolls): |
| 87 | raise IndexError("game not complete") |
| 88 | total_score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2] |
| 89 | roll_index += 1 |
| 90 | # Spare |
| 91 | elif self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: |
| 92 | # Edge Case: Not enough rolls for spare bonus |
| 93 | if roll_index + 2 >= len(self.rolls): |
| 94 | raise IndexError("game not complete") |
| 95 | total_score += 10 + self.rolls[roll_index + 2] |
| 96 | roll_index += 2 |
| 97 | # Open frame |
| 98 | else: |
| 99 | total_score += self.rolls[roll_index] + self.rolls[roll_index + 1] |
| 100 | roll_index += 2 |
| 101 | else: # 10th frame |
| 102 | # Calculate 10th frame score directly from rolls |
| 103 | if len(self.rolls) - roll_index == 2: |
| 104 | total_score += self.rolls[roll_index] + self.rolls[roll_index + 1] |
| 105 | elif len(self.rolls) - roll_index == 3: |
| 106 | total_score += self.rolls[roll_index] + self.rolls[roll_index + 1] + self.rolls[roll_index + 2] |
| 107 | else: |
| 108 | raise IndexError("game not complete") |
| 109 | |
| 110 | return total_score |
| 111 | # Handled Edge Cases: Game is already complete, Invalid pin count (negative or more than 10), Too many pins for current frame state, Invalid fill balls, Score called before game is complete, Not enough rolls to calculate score, Not enough rolls for strike bonus, Not enough rolls for spare bonus |