| 1 | 1 | class BowlingGame: |
| 2 | 2 | def __init__(self): |
| 3 | | pass |
| 4 | | |
| 3 | self.rolls = [] |
| 4 | self.current_roll = 0 |
| 5 | |
| 5 | 6 | def roll(self, pins): |
| 6 | | pass |
| 7 | | |
| 7 | # Edge Case: Number of pins must be between 0 and 10 |
| 8 | if pins < 0 or pins > 10: |
| 9 | raise ValueError("Pins must be between 0 and 10") |
| 10 | |
| 11 | # Edge Case: Cannot roll after game is over |
| 12 | if self.is_game_over(): |
| 13 | raise ValueError("Cannot roll after game is over") |
| 14 | |
| 15 | self.rolls.append(pins) |
| 16 | self.current_roll += 1 |
| 17 | |
| 8 | 18 | def score(self): |
| 9 | | pass |
| 19 | # Edge Case: Cannot score incomplete game |
| 20 | if not self.is_game_over(): |
| 21 | raise ValueError("Score cannot be taken until the end of the game") |
| 22 | |
| 23 | score = 0 |
| 24 | roll_index = 0 |
| 25 | |
| 26 | for frame in range(10): |
| 27 | # Edge Case: Strike (10 pins in first roll) |
| 28 | if self.is_strike(roll_index): |
| 29 | score += 10 + self.strike_bonus(roll_index) |
| 30 | roll_index += 1 |
| 31 | # Edge Case: Spare (10 pins in two rolls) |
| 32 | elif self.is_spare(roll_index): |
| 33 | score += 10 + self.spare_bonus(roll_index) |
| 34 | roll_index += 2 |
| 35 | # Edge Case: Open frame (less than 10 pins in two rolls) |
| 36 | else: |
| 37 | score += self.sum_of_balls_in_frame(roll_index) |
| 38 | roll_index += 2 |
| 39 | |
| 40 | return score |
| 41 | |
| 42 | def is_game_over(self): |
| 43 | if len(self.rolls) < 20: |
| 44 | return False |
| 45 | |
| 46 | # Check if we have exactly 20 rolls and they form 10 complete frames |
| 47 | roll_index = 0 |
| 48 | for frame in range(10): |
| 49 | if frame < 9: # First 9 frames |
| 50 | if self.is_strike(roll_index): |
| 51 | roll_index += 1 |
| 52 | else: |
| 53 | roll_index += 2 |
| 54 | else: # 10th frame |
| 55 | # Edge Case: 10th frame with strike or spare needs additional rolls |
| 56 | if self.is_strike(roll_index): |
| 57 | # Strike in 10th frame - need 2 more rolls |
| 58 | return len(self.rolls) >= roll_index + 3 |
| 59 | elif self.is_spare(roll_index): |
| 60 | # Spare in 10th frame - need 1 more roll |
| 61 | return len(self.rolls) >= roll_index + 3 |
| 62 | else: |
| 63 | # Open frame in 10th frame - no additional rolls needed |
| 64 | return len(self.rolls) >= roll_index + 2 |
| 65 | |
| 66 | return True |
| 67 | |
| 68 | def is_strike(self, roll_index): |
| 69 | return self.rolls[roll_index] == 10 |
| 70 | |
| 71 | def is_spare(self, roll_index): |
| 72 | return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10 |
| 73 | |
| 74 | def sum_of_balls_in_frame(self, roll_index): |
| 75 | return self.rolls[roll_index] + self.rolls[roll_index + 1] |
| 76 | |
| 77 | def spare_bonus(self, roll_index): |
| 78 | return self.rolls[roll_index + 2] |
| 79 | |
| 80 | def strike_bonus(self, roll_index): |
| 81 | return self.rolls[roll_index + 1] + self.rolls[roll_index + 2] |
| 82 | |
| 83 | # Handled Edge Cases: Number of pins must be between 0 and 10, Cannot roll after game is over, Cannot score incomplete game, Strike (10 pins in first roll), Spare (10 pins in two rolls), Open frame (less than 10 pins in two rolls), 10th frame with strike or spare needs additional rolls |