| 1 | 1 | class BowlingGame: |
| 2 | 2 | def __init__(self): |
| 3 | | pass |
| 4 | | |
| 3 | self.rolls = [] |
| 4 | self.current_frame = 0 |
| 5 | self.frame_rolls = [] |
| 6 | self.frames = [] |
| 7 | self.game_over = False |
| 8 | |
| 5 | 9 | def roll(self, pins): |
| 6 | | pass |
| 7 | | |
| 10 | # Edge Case: Game is already over |
| 11 | if self.game_over: |
| 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 | # Edge Case: Validation for 10th frame fill balls |
| 23 | if self.current_frame == 9: # 10th frame |
| 24 | if len(self.frame_rolls) == 0: |
| 25 | # First roll in 10th frame - no special validation needed |
| 26 | pass |
| 27 | elif len(self.frame_rolls) == 1: |
| 28 | # Second roll in 10th frame |
| 29 | if self.frame_rolls[0] == 10: # First roll was a strike |
| 30 | # Second roll can be anything 0-10 |
| 31 | pass |
| 32 | elif self.frame_rolls[0] + pins > 10: # Would exceed 10 pins in frame |
| 33 | raise ValueError("Pin count exceeds pins on the lane") |
| 34 | elif len(self.frame_rolls) == 2: |
| 35 | # Third roll in 10th frame (fill ball) |
| 36 | if self.frame_rolls[0] == 10: # First roll was a strike |
| 37 | # Second fill ball validation - only validate if first fill ball wasn't a strike |
| 38 | if self.frame_rolls[1] != 10 and self.frame_rolls[1] + pins > 10: |
| 39 | raise ValueError("invalid fill balls") |
| 40 | elif self.frame_rolls[0] + self.frame_rolls[1] == 10: # First two rolls were a spare |
| 41 | # Third roll can be anything 0-10 |
| 42 | pass |
| 43 | else: |
| 44 | # Open frame - no third roll allowed |
| 45 | raise ValueError("Cannot roll after game is over") |
| 46 | else: # Regular frames 1-9 |
| 47 | if len(self.frame_rolls) == 1 and self.frame_rolls[0] != 10: |
| 48 | # Edge Case: Invalid pin count when there are not enough pins standing in regular frames |
| 49 | if self.frame_rolls[0] + pins > 10: |
| 50 | raise ValueError("Pin count exceeds pins on the lane") |
| 51 | |
| 52 | self.frame_rolls.append(pins) |
| 53 | self.rolls.append(pins) |
| 54 | |
| 55 | # Handle frame completion |
| 56 | if self.current_frame < 9: # Frames 1-9 |
| 57 | if pins == 10 or len(self.frame_rolls) == 2: |
| 58 | self.frames.append(self.frame_rolls[:]) |
| 59 | self.frame_rolls = [] |
| 60 | self.current_frame += 1 |
| 61 | else: # 10th frame |
| 62 | # Handle frame completion for 10th frame |
| 63 | if len(self.frame_rolls) == 2: |
| 64 | # Check if 10th frame is complete (no spare or strike) |
| 65 | if self.frame_rolls[0] + self.frame_rolls[1] < 10: |
| 66 | self.frames.append(self.frame_rolls[:]) |
| 67 | self.frame_rolls = [] |
| 68 | self.game_over = True |
| 69 | elif len(self.frame_rolls) == 3: |
| 70 | # Frame is complete with fill balls |
| 71 | self.frames.append(self.frame_rolls[:]) |
| 72 | self.frame_rolls = [] |
| 73 | self.game_over = True |
| 74 | |
| 8 | 75 | def score(self): |
| 9 | | pass |
| 76 | # Edge Case: New game should have score of 0 |
| 77 | if len(self.frames) == 0 and len(self.rolls) == 0: |
| 78 | return 0 |
| 79 | |
| 80 | # Edge Case: Attempting to score before game is complete |
| 81 | if not self.game_over and len(self.frames) < 10: |
| 82 | raise IndexError("Score cannot be taken until the end of the game") |
| 83 | |
| 84 | # Edge Case: Attempting to score with incomplete 10th frame |
| 85 | if len(self.frames) == 10 and not self.game_over: |
| 86 | raise IndexError("Score cannot be taken until the end of the game") |
| 87 | |
| 88 | total = 0 |
| 89 | roll_index = 0 |
| 90 | |
| 91 | for frame_num in range(10): |
| 92 | frame = self.frames[frame_num] |
| 93 | |
| 94 | if len(frame) >= 1 and frame[0] == 10: # Strike |
| 95 | total += 10 |
| 96 | # Add next two rolls |
| 97 | if frame_num < 9: # Not the 10th frame |
| 98 | next_frame = self.frames[frame_num + 1] |
| 99 | if len(next_frame) >= 1: |
| 100 | total += next_frame[0] |
| 101 | if len(next_frame) >= 2: |
| 102 | total += next_frame[1] |
| 103 | # Edge Case: When next frame is also a strike |
| 104 | elif next_frame[0] == 10 and frame_num < 8: |
| 105 | third_frame = self.frames[frame_num + 2] |
| 106 | if len(third_frame) >= 1: |
| 107 | total += third_frame[0] |
| 108 | # Edge Case: When we need the second roll from frame after next but it doesn't exist |
| 109 | elif next_frame[0] == 10 and frame_num == 8: |
| 110 | # In 10th frame, we have all rolls we need |
| 111 | if len(next_frame) >= 2: |
| 112 | total += next_frame[1] |
| 113 | else: # 10th frame |
| 114 | if len(frame) >= 3: |
| 115 | total += frame[1] + frame[2] |
| 116 | elif len(frame) >= 2 and frame[0] + frame[1] == 10: # Spare |
| 117 | total += 10 |
| 118 | # Add next roll |
| 119 | if frame_num < 9: # Not the 10th frame |
| 120 | next_frame = self.frames[frame_num + 1] |
| 121 | if len(next_frame) >= 1: |
| 122 | total += next_frame[0] |
| 123 | else: # 10th frame |
| 124 | if len(frame) >= 3: |
| 125 | total += frame[2] |
| 126 | else: # Open frame |
| 127 | total += sum(frame) |
| 128 | |
| 129 | return total |
| 130 | |
| 131 | # Handled Edge Cases: Game is already over, Invalid pin count (negative), Invalid pin count (more than 10), Invalid pin count when there are not enough pins standing, Determining when the 10th frame is complete, Attempting to score before game is complete, Attempting to score with incomplete 10th frame, When next frame is also a strike, When we need the second roll from frame after next but it doesn't exist |