| 1 | 1 | class BowlingGame: |
| 2 | 2 | def __init__(self): |
| 3 | | pass |
| 4 | | |
| 3 | """ |
| 4 | Initialize a new bowling game. |
| 5 | """ |
| 6 | self.rolls = [] |
| 7 | self.current_roll = 0 |
| 8 | |
| 5 | 9 | def roll(self, pins): |
| 6 | | pass |
| 7 | | |
| 10 | """ |
| 11 | Record a roll in the game. |
| 12 | |
| 13 | Args: |
| 14 | pins (int): Number of pins knocked down in this roll. |
| 15 | |
| 16 | Raises: |
| 17 | ValueError: If the number of pins is invalid. |
| 18 | """ |
| 19 | # Edge Case: Invalid pin count (negative or more than 10) |
| 20 | if pins < 0 or pins > 10: |
| 21 | raise ValueError("Pins must be between 0 and 10") |
| 22 | |
| 23 | # Edge Case: Game already completed |
| 24 | if self._is_game_completed(): |
| 25 | raise ValueError("Game is already finished") |
| 26 | |
| 27 | # Edge Case: Too many pins in a frame (for frames 1-9) |
| 28 | if len(self.rolls) >= 1 and not self._is_in_tenth_frame(): |
| 29 | frame_start = (len(self.rolls) // 2) * 2 |
| 30 | if frame_start < len(self.rolls): |
| 31 | frame_pins = self.rolls[frame_start] |
| 32 | if frame_pins < 10 and frame_pins + pins > 10: |
| 33 | raise ValueError("Cannot knock down more than 10 pins in a frame") |
| 34 | |
| 35 | # Edge Case: Too many pins in 10th frame first two rolls |
| 36 | if self._is_in_tenth_frame() and len(self.rolls) >= 18: |
| 37 | tenth_frame_rolls = self.rolls[18:] |
| 38 | if len(tenth_frame_rolls) == 0: |
| 39 | # First roll of 10th frame - no validation needed |
| 40 | pass |
| 41 | elif len(tenth_frame_rolls) == 1: |
| 42 | # Second roll of 10th frame |
| 43 | if tenth_frame_rolls[0] < 10 and tenth_frame_rolls[0] + pins > 10: |
| 44 | raise ValueError("Cannot knock down more than 10 pins in a frame") |
| 45 | elif len(tenth_frame_rolls) == 2: |
| 46 | # Third roll of 10th frame - only allowed if first was strike or first two were spare |
| 47 | if tenth_frame_rolls[0] < 10 and tenth_frame_rolls[0] + tenth_frame_rolls[1] < 10: |
| 48 | raise ValueError("Game is already finished") |
| 49 | |
| 50 | self.rolls.append(pins) |
| 51 | self.current_roll += 1 |
| 52 | |
| 53 | def _is_in_tenth_frame(self): |
| 54 | """ |
| 55 | Check if we are currently in the tenth frame. |
| 56 | |
| 57 | Returns: |
| 58 | bool: True if we are in the tenth frame, False otherwise. |
| 59 | """ |
| 60 | return len(self.rolls) >= 18 |
| 61 | |
| 8 | 62 | def score(self): |
| 9 | | pass |
| 63 | """ |
| 64 | Calculate the total score for the game. |
| 65 | |
| 66 | Returns: |
| 67 | int: The total score of the game. |
| 68 | |
| 69 | Raises: |
| 70 | ValueError: If the game is not yet completed. |
| 71 | """ |
| 72 | # Edge Case: Game not completed |
| 73 | if not self._is_game_completed(): |
| 74 | raise ValueError("Game is not yet finished") |
| 75 | |
| 76 | total_score = 0 |
| 77 | roll_index = 0 |
| 78 | |
| 79 | for frame in range(10): |
| 80 | # Edge Case: Strike in regular frames (0-8) |
| 81 | if frame < 9 and self.rolls[roll_index] == 10: # Strike |
| 82 | total_score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2] |
| 83 | roll_index += 1 |
| 84 | # Edge Case: Strike in 10th frame |
| 85 | elif frame == 9 and self.rolls[roll_index] == 10: # Strike in 10th frame |
| 86 | total_score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2] |
| 87 | roll_index += 3 |
| 88 | # Edge Case: Spare in regular frames (0-8) |
| 89 | elif frame < 9 and self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare |
| 90 | total_score += 10 + self.rolls[roll_index + 2] |
| 91 | roll_index += 2 |
| 92 | # Edge Case: Spare in 10th frame |
| 93 | elif frame == 9 and self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare in 10th frame |
| 94 | total_score += 10 + self.rolls[roll_index + 2] |
| 95 | roll_index += 3 |
| 96 | # Edge Case: Open frame |
| 97 | else: # Open frame |
| 98 | total_score += self.rolls[roll_index] + self.rolls[roll_index + 1] |
| 99 | roll_index += 2 |
| 100 | |
| 101 | return total_score |
| 102 | |
| 103 | def _is_game_completed(self): |
| 104 | """ |
| 105 | Check if the game is completed. |
| 106 | |
| 107 | Returns: |
| 108 | bool: True if the game is completed, False otherwise. |
| 109 | """ |
| 110 | # Need at least 12 rolls for a complete game (all strikes) |
| 111 | if len(self.rolls) < 12: |
| 112 | return False |
| 113 | |
| 114 | # Count frames completed |
| 115 | frame_count = 0 |
| 116 | roll_index = 0 |
| 117 | |
| 118 | for frame in range(10): |
| 119 | if roll_index >= len(self.rolls): |
| 120 | return False |
| 121 | |
| 122 | if frame < 9: # Frames 1-9 |
| 123 | if self.rolls[roll_index] == 10: # Strike |
| 124 | roll_index += 1 |
| 125 | frame_count += 1 |
| 126 | else: # Not a strike |
| 127 | if roll_index + 1 >= len(self.rolls): |
| 128 | return False |
| 129 | roll_index += 2 |
| 130 | frame_count += 1 |
| 131 | else: # Frame 10 |
| 132 | if roll_index >= len(self.rolls): |
| 133 | return False |
| 134 | if self.rolls[roll_index] == 10: # Strike in 10th |
| 135 | # Need 2 more rolls |
| 136 | if roll_index + 2 >= len(self.rolls): |
| 137 | return False |
| 138 | return True |
| 139 | elif roll_index + 1 < len(self.rolls) and self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare in 10th |
| 140 | # Need 1 more roll |
| 141 | if roll_index + 2 >= len(self.rolls): |
| 142 | return False |
| 143 | return True |
| 144 | else: # Open frame in 10th |
| 145 | if roll_index + 1 >= len(self.rolls): |
| 146 | return False |
| 147 | return True |
| 148 | |
| 149 | return frame_count >= 10 |
| 150 | # Handled Edge Cases: Invalid pin count, too many pins in a frame, game already completed, extra roll after strike in 10th frame without spare, too many rolls in 10th frame |
| 151 | # Handled Edge Cases: Game not completed, strike in regular frames, strike in 10th frame, spare in regular frames, spare in 10th frame, open frame |
| 152 | # Handled Edge Cases: Not enough rolls, open 10th frame, spare in 10th frame but no third roll recorded, strike in 10th frame but not enough rolls, strike in 10th frame but only one additional roll, strike in 10th frame and spare or open in 11th, strike in 10th frame and 11th, all necessary rolls completed |