| 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.frames = [] |
| 8 | self.current_frame = [] |
| 9 | self.frame_count = 0 |
| 10 | self.game_over = False |
| 11 | |
| 5 | 12 | def roll(self, pins): |
| 6 | | pass |
| 13 | """ |
| 14 | Record a roll in the game. |
| 15 | |
| 16 | Args: |
| 17 | pins (int): Number of pins knocked down in this roll |
| 18 | |
| 7 | 19 | |
| 20 | Raises: |
| 21 | ValueError: If pins is negative or greater than 10 |
| 22 | ValueError: If game is already over |
| 23 | """ |
| 24 | # Edge Case: Game is already over |
| 25 | if self.game_over: |
| 26 | raise ValueError("Cannot roll after game is over") |
| 27 | |
| 28 | # Edge Case: Invalid number of pins |
| 29 | if pins < 0 or pins > 10: |
| 30 | raise ValueError("Pins must be between 0 and 10") |
| 31 | |
| 32 | # Edge Case: Too many pins in a frame (not considering 10th frame special rules yet) |
| 33 | if len(self.current_frame) > 0 and self.frame_count < 10: |
| 34 | if self.current_frame[0] + pins > 10: |
| 35 | raise ValueError("Invalid roll: pins downed in a frame cannot exceed 10") |
| 36 | |
| 37 | self.current_frame.append(pins) |
| 38 | self.rolls.append(pins) |
| 39 | |
| 40 | # Handle frame completion |
| 41 | if self.frame_count < 9: # Frames 1-9 |
| 42 | # Edge Case: Strike (first roll of 10 pins) |
| 43 | if len(self.current_frame) == 1 and pins == 10: |
| 44 | self.frames.append(self.current_frame) |
| 45 | self.current_frame = [] |
| 46 | self.frame_count += 1 |
| 47 | # Edge Case: Second roll in a regular frame |
| 48 | elif len(self.current_frame) == 2: |
| 49 | self.frames.append(self.current_frame) |
| 50 | self.current_frame = [] |
| 51 | self.frame_count += 1 |
| 52 | else: # 10th frame |
| 53 | # Edge Case: 10th frame with strike or spare needs special handling |
| 54 | if len(self.current_frame) == 3: |
| 55 | self.frames.append(self.current_frame) |
| 56 | self.current_frame = [] |
| 57 | self.frame_count += 1 |
| 58 | self.game_over = True |
| 59 | elif len(self.current_frame) == 2: |
| 60 | # If it's not a spare or strike, game is over |
| 61 | if self.current_frame[0] + self.current_frame[1] < 10: |
| 62 | self.frames.append(self.current_frame) |
| 63 | self.current_frame = [] |
| 64 | self.frame_count += 1 |
| 65 | self.game_over = True |
| 66 | # Otherwise, need one more roll |
| 67 | elif len(self.current_frame) == 1 and self.current_frame[0] < 10: |
| 68 | # First roll was not a strike, so need a second roll |
| 69 | pass |
| 70 | |
| 8 | 71 | def score(self): |
| 9 | | pass |
| 72 | """ |
| 73 | Calculate the total score for the game. |
| 74 | |
| 75 | |
| 76 | Returns: |
| 77 | int: The total score for the game |
| 78 | |
| 79 | |
| 80 | Raises: |
| 81 | ValueError: If the game is not yet complete |
| 82 | """ |
| 83 | # Edge Case: Game not complete |
| 84 | if not self.game_over: |
| 85 | raise ValueError("Score cannot be taken until the end of the game") |
| 86 | |
| 87 | total_score = 0 |
| 88 | roll_index = 0 |
| 89 | |
| 90 | # Edge Case: Not enough frames |
| 91 | if len(self.frames) < 10: |
| 92 | raise ValueError("Score cannot be taken until the end of the game") |
| 93 | |
| 94 | # Score first 9 frames |
| 95 | for frame_index in range(9): |
| 96 | # Edge Case: Frame doesn't have enough rolls |
| 97 | if frame_index >= len(self.frames): |
| 98 | raise ValueError("Score cannot be taken until the end of the game") |
| 99 | |
| 100 | frame = self.frames[frame_index] |
| 101 | |
| 102 | # Edge Case: Invalid frame |
| 103 | if len(frame) == 0: |
| 104 | raise ValueError("Invalid frame") |
| 105 | |
| 106 | if len(frame) == 1: # Strike |
| 107 | # Edge Case: Not enough rolls for strike bonus |
| 108 | if frame_index + 1 >= len(self.frames): |
| 109 | raise ValueError("Invalid frame") |
| 110 | |
| 111 | next_frame = self.frames[frame_index + 1] |
| 112 | |
| 113 | # Edge Case: Next frame is invalid |
| 114 | if len(next_frame) == 0: |
| 115 | raise ValueError("Invalid frame") |
| 116 | |
| 117 | if len(next_frame) >= 2: # Normal case |
| 118 | total_score += 10 + next_frame[0] + next_frame[1] |
| 119 | elif len(next_frame) == 1: # Next frame is also a strike |
| 120 | # Edge Case: Need third roll for bonus calculation |
| 121 | if frame_index + 2 >= len(self.frames) or len(self.frames[frame_index + 2]) == 0: |
| 122 | raise ValueError("Invalid frame") |
| 123 | third_frame = self.frames[frame_index + 2] |
| 124 | total_score += 10 + next_frame[0] + third_frame[0] |
| 125 | else: # Regular frame (2 rolls) |
| 126 | frame_score = frame[0] + frame[1] |
| 127 | if frame_score == 10: # Spare |
| 128 | # Edge Case: Not enough rolls for spare bonus |
| 129 | if frame_index + 1 >= len(self.frames) or len(self.frames[frame_index + 1]) == 0: |
| 130 | raise ValueError("Invalid frame") |
| 131 | next_roll = self.frames[frame_index + 1][0] |
| 132 | total_score += 10 + next_roll |
| 133 | else: # Open frame |
| 134 | total_score += frame_score |
| 135 | |
| 136 | # Score 10th frame |
| 137 | # Edge Case: Missing 10th frame |
| 138 | if len(self.frames) < 10: |
| 139 | raise ValueError("Score cannot be taken until the end of the game") |
| 140 | |
| 141 | tenth_frame = self.frames[9] |
| 142 | |
| 143 | # Edge Case: Invalid 10th frame |
| 144 | if len(tenth_frame) < 2: |
| 145 | raise ValueError("Invalid frame") |
| 146 | |
| 147 | if len(tenth_frame) == 2: |
| 148 | total_score += tenth_frame[0] + tenth_frame[1] |
| 149 | elif len(tenth_frame) == 3: |
| 150 | total_score += tenth_frame[0] + tenth_frame[1] + tenth_frame[2] |
| 151 | |
| 152 | return total_score |
| 153 | |
| 154 | # Handled Edge Cases: Game already over, invalid pins, too many pins in frame, strike in regular frame, second roll in regular frame, 10th frame special rules, game not complete, not enough frames, invalid frame, not enough rolls for strike bonus, next frame invalid, need third roll for bonus, not enough rolls for spare bonus, open frame, missing 10th frame, invalid 10th frame |