| 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_frame = 1 |
| 8 | self.frame_rolls = [] |
| 9 | self.frames = [] |
| 10 | |
| 5 | 11 | def roll(self, pins): |
| 6 | | pass |
| 7 | | |
| 12 | """ |
| 13 | Record a roll in the game. |
| 14 | |
| 15 | Args: |
| 16 | pins (int): Number of pins knocked down in this roll |
| 17 | |
| 18 | Raises: |
| 19 | ValueError: If pins is negative or greater than 10 |
| 20 | IndexError: If game is already over |
| 21 | """ |
| 22 | # Edge Case: Invalid number of pins |
| 23 | if pins < 0: |
| 24 | raise ValueError("Negative roll is invalid") |
| 25 | |
| 26 | # Edge Case: Too many pins |
| 27 | if pins > 10: |
| 28 | raise ValueError("Pin count exceeds pins on the lane") |
| 29 | |
| 30 | # Edge Case: Game already completed |
| 31 | if len(self.frames) == 10: |
| 32 | # Check if we're in the 10th frame and can still roll |
| 33 | tenth_frame = self.frames[9] |
| 34 | # If it's not a strike or spare, no more rolls allowed |
| 35 | if not (tenth_frame[0] == 10 or sum(tenth_frame) >= 10): |
| 36 | raise IndexError("Cannot roll after game is over") |
| 37 | # If it's a strike or spare, check if we've had enough fill balls |
| 38 | elif len(tenth_frame) >= 3 or (tenth_frame[0] != 10 and len(tenth_frame) >= 2): |
| 39 | raise IndexError("Cannot roll after game is over") |
| 40 | |
| 41 | # Edge Case: Frame validation in progress |
| 42 | if self.current_frame <= 10: |
| 43 | self.frame_rolls.append(pins) |
| 44 | |
| 45 | # Handle regular frames (1-9) |
| 46 | if self.current_frame < 10: |
| 47 | # Strike - move to next frame |
| 48 | if self.frame_rolls[0] == 10: |
| 49 | self.frames.append(self.frame_rolls) |
| 50 | self.current_frame += 1 |
| 51 | self.frame_rolls = [] |
| 52 | # Second roll - move to next frame |
| 53 | elif len(self.frame_rolls) == 2: |
| 54 | # Edge Case: Frame total exceeds 10 |
| 55 | if sum(self.frame_rolls) > 10: |
| 56 | raise ValueError("Pin count exceeds pins on the lane") |
| 57 | self.frames.append(self.frame_rolls) |
| 58 | self.current_frame += 1 |
| 59 | self.frame_rolls = [] |
| 60 | # Handle 10th frame |
| 61 | else: # current_frame == 10 |
| 62 | tenth_frame = self.frame_rolls |
| 63 | # First roll is a strike |
| 64 | if len(tenth_frame) == 1 and tenth_frame[0] == 10: |
| 65 | # Need two more rolls |
| 66 | pass |
| 67 | # Second roll |
| 68 | elif len(tenth_frame) == 2: |
| 69 | # If first two rolls are a strike or spare, need one more roll |
| 70 | if tenth_frame[0] == 10 or sum(tenth_frame) >= 10: |
| 71 | pass |
| 72 | else: |
| 73 | # Open frame - game over |
| 74 | self.frames.append(tenth_frame) |
| 75 | # Third roll |
| 76 | elif len(tenth_frame) == 3: |
| 77 | # Edge Case: Invalid fill balls |
| 78 | if tenth_frame[0] == 10: # First roll was a strike |
| 79 | # Second roll can be anything, third roll depends on second |
| 80 | if tenth_frame[1] < 10 and tenth_frame[1] + tenth_frame[2] > 10: |
| 81 | raise ValueError("Pin count exceeds pins on the lane") |
| 82 | elif tenth_frame[0] + tenth_frame[1] == 10: # First two rolls were a spare |
| 83 | # Third roll can be anything from 0 to 10 |
| 84 | pass |
| 85 | else: |
| 86 | # This shouldn't happen based on previous logic |
| 87 | raise ValueError("invalid fill balls") |
| 88 | self.frames.append(tenth_frame) |
| 89 | else: |
| 90 | # Edge Case: Roll after game completion |
| 91 | raise IndexError("Cannot roll after game is over") |
| 92 | |
| 93 | self.rolls.append(pins) |
| 94 | |
| 8 | 95 | def score(self): |
| 9 | | pass |
| 96 | """ |
| 97 | Calculate the total score for the game. |
| 98 | |
| 99 | Returns: |
| 100 | int: The total score for the game |
| 101 | |
| 102 | Raises: |
| 103 | IndexError: If the game is not yet complete |
| 104 | """ |
| 105 | # Edge Case: New game with no frames |
| 106 | if len(self.frames) == 0: |
| 107 | return 0 |
| 108 | |
| 109 | # Edge Case: Game not complete |
| 110 | if len(self.frames) < 10: |
| 111 | raise IndexError("Score cannot be taken until the end of the game") |
| 112 | |
| 113 | # Edge Case: 10th frame incomplete |
| 114 | if len(self.frames) == 10 and len(self.frames[9]) < 2: |
| 115 | raise IndexError("Score cannot be taken until the end of the game") |
| 116 | |
| 117 | # Edge Case: 10th frame is spare but missing fill ball |
| 118 | tenth_frame = self.frames[9] |
| 119 | if len(tenth_frame) == 2 and sum(tenth_frame) >= 10: |
| 120 | raise IndexError("Score cannot be taken until the end of the game") |
| 121 | |
| 122 | total_score = 0 |
| 123 | |
| 124 | # Score first 9 frames |
| 125 | for frame_index in range(9): |
| 126 | frame = self.frames[frame_index] |
| 127 | |
| 128 | # Strike |
| 129 | if frame[0] == 10: |
| 130 | total_score += 10 + self._next_two_balls(frame_index) |
| 131 | # Spare |
| 132 | elif sum(frame) == 10: |
| 133 | total_score += 10 + self._next_ball(frame_index) |
| 134 | # Open frame |
| 135 | else: |
| 136 | total_score += sum(frame) |
| 137 | |
| 138 | # Score 10th frame |
| 139 | tenth_frame = self.frames[9] |
| 140 | total_score += sum(tenth_frame) |
| 141 | |
| 142 | return total_score |
| 143 | |
| 144 | def _next_ball(self, frame_index): |
| 145 | """ |
| 146 | Get the score of the next ball after the given frame. |
| 147 | |
| 148 | Args: |
| 149 | frame_index (int): Index of the current frame |
| 150 | |
| 151 | Returns: |
| 152 | int: Score of the next ball |
| 153 | """ |
| 154 | if frame_index + 1 < len(self.frames): |
| 155 | return self.frames[frame_index + 1][0] |
| 156 | return 0 |
| 157 | |
| 158 | def _next_two_balls(self, frame_index): |
| 159 | """ |
| 160 | Get the score of the next two balls after the given frame. |
| 161 | |
| 162 | Args: |
| 163 | frame_index (int): Index of the current frame |
| 164 | |
| 165 | Returns: |
| 166 | int: Score of the next two balls |
| 167 | """ |
| 168 | score = 0 |
| 169 | |
| 170 | # If we're not in the last frame |
| 171 | if frame_index + 1 < len(self.frames): |
| 172 | next_frame = self.frames[frame_index + 1] |
| 173 | |
| 174 | # If next frame is a strike and not the 10th frame |
| 175 | if len(next_frame) > 0 and next_frame[0] == 10 and frame_index + 1 < 9: |
| 176 | score += 10 |
| 177 | # Add first ball of frame after next |
| 178 | if frame_index + 2 < len(self.frames) and len(self.frames[frame_index + 2]) > 0: |
| 179 | score += self.frames[frame_index + 2][0] |
| 180 | else: |
| 181 | # Add up to two balls from next frame |
| 182 | balls_added = 0 |
| 183 | for ball in next_frame: |
| 184 | if balls_added < 2: |
| 185 | score += ball |
| 186 | balls_added += 1 |
| 187 | |
| 188 | return score |
| 189 | |
| 190 | # Handled Edge Cases: Invalid number of pins, too many pins, game already completed, frame total exceeds 10, roll after game completion, game not complete, 10th frame incomplete, 10th frame is spare but missing fill ball |