| 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("pins must be between 0 and 10") |
| 25 | |
| 26 | # Edge Case: Too many pins |
| 27 | if pins > 10: |
| 28 | raise ValueError("pins must be between 0 and 10") |
| 29 | |
| 30 | # Edge Case: Game already finished |
| 31 | if len(self.frames) == 10: |
| 32 | # Check if we're in fill balls |
| 33 | if self.is_tenth_frame_complete(): |
| 34 | raise IndexError("cannot roll after game is over") |
| 35 | |
| 36 | self.frame_rolls.append(pins) |
| 37 | self.rolls.append(pins) |
| 38 | |
| 39 | # Handle frame completion |
| 40 | if self.current_frame < 10: # Regular frames (1-9) |
| 41 | # Edge Case: Strike in regular frame |
| 42 | if len(self.frame_rolls) == 1 and pins == 10: |
| 43 | self.frames.append(self.frame_rolls[:]) |
| 44 | self.frame_rolls = [] |
| 45 | self.current_frame += 1 |
| 46 | # Edge Case: Two rolls in regular frame |
| 47 | elif len(self.frame_rolls) == 2: |
| 48 | # Edge Case: Invalid frame total |
| 49 | if sum(self.frame_rolls) > 10 and self.frame_rolls[0] != 10: |
| 50 | self.frame_rolls = self.frame_rolls[:1] # Keep first roll |
| 51 | raise ValueError("invalid frame pins") |
| 52 | self.frames.append(self.frame_rolls[:]) |
| 53 | self.frame_rolls = [] |
| 54 | self.current_frame += 1 |
| 55 | elif self.current_frame == 10: # Tenth frame |
| 56 | # Edge Case: Tenth frame validation |
| 57 | if len(self.frame_rolls) == 1: |
| 58 | # If first roll is a strike, need two more rolls |
| 59 | if self.frame_rolls[0] == 10: |
| 60 | # Wait for two more rolls |
| 61 | pass |
| 62 | # Otherwise, wait for second roll |
| 63 | else: |
| 64 | pass |
| 65 | elif len(self.frame_rolls) == 2: |
| 66 | # If first roll is a strike, need one more roll |
| 67 | if self.frame_rolls[0] == 10: |
| 68 | # Wait for one more roll |
| 69 | pass |
| 70 | # If first two rolls are a spare, need one more roll (fill ball) |
| 71 | elif sum(self.frame_rolls[:2]) == 10: |
| 72 | # Wait for fill ball |
| 73 | pass |
| 74 | # If first two rolls are not a strike or spare, frame is complete |
| 75 | elif sum(self.frame_rolls[:2]) < 10: |
| 76 | self.frames.append(self.frame_rolls[:]) |
| 77 | self.frame_rolls = [] |
| 78 | elif len(self.frame_rolls) == 3: |
| 79 | # Edge Case: Validate tenth frame fill balls |
| 80 | if self.frame_rolls[0] == 10: # First roll was a strike |
| 81 | # Second roll can be anything, third roll depends on second |
| 82 | if self.frame_rolls[1] != 10 and sum(self.frame_rolls[1:]) > 10: |
| 83 | self.frame_rolls = self.frame_rolls[:2] # Remove invalid third roll |
| 84 | raise ValueError("Pin count exceeds pins on the lane") |
| 85 | elif sum(self.frame_rolls[:2]) == 10: # First two rolls were a spare |
| 86 | # Third roll is a fill ball, no validation needed beyond pin count |
| 87 | pass |
| 88 | else: # Open frame, shouldn't have a third roll |
| 89 | self.frame_rolls = self.frame_rolls[:2] # Remove invalid third roll |
| 90 | raise ValueError("Pin count exceeds pins on the lane") |
| 91 | |
| 92 | self.frames.append(self.frame_rolls[:]) |
| 93 | self.frame_rolls = [] |
| 94 | else: # Tenth frame |
| 95 | # Edge Case: Tenth frame validation |
| 96 | if len(self.frame_rolls) == 1: |
| 97 | # If first roll is a strike, need two more rolls |
| 98 | if self.frame_rolls[0] == 10: |
| 99 | # Wait for two more rolls |
| 100 | pass |
| 101 | # Otherwise, wait for second roll |
| 102 | else: |
| 103 | pass |
| 104 | elif len(self.frame_rolls) == 2: |
| 105 | # If first roll is a strike, need one more roll |
| 106 | if self.frame_rolls[0] == 10: |
| 107 | # Wait for one more roll |
| 108 | pass |
| 109 | # If first two rolls are a spare, need one more roll (fill ball) |
| 110 | elif sum(self.frame_rolls[:2]) == 10: |
| 111 | # Wait for fill ball |
| 112 | pass |
| 113 | # If first two rolls are not a strike or spare, frame is complete |
| 114 | elif sum(self.frame_rolls[:2]) < 10: |
| 115 | self.frames.append(self.frame_rolls[:]) |
| 116 | self.frame_rolls = [] |
| 117 | elif len(self.frame_rolls) == 3: |
| 118 | # Edge Case: Validate tenth frame fill balls |
| 119 | if self.frame_rolls[0] == 10: # First roll was a strike |
| 120 | # Second roll can be anything, third roll depends on second |
| 121 | if self.frame_rolls[1] != 10 and sum(self.frame_rolls[1:]) > 10: |
| 122 | self.frame_rolls = self.frame_rolls[:2] # Remove invalid third roll |
| 123 | raise ValueError("Pin count exceeds pins on the lane") |
| 124 | elif sum(self.frame_rolls[:2]) == 10: # First two rolls were a spare |
| 125 | # Third roll is a fill ball, no validation needed beyond pin count |
| 126 | pass |
| 127 | else: # Open frame, shouldn't have a third roll |
| 128 | self.frame_rolls = self.frame_rolls[:2] # Remove invalid third roll |
| 129 | raise ValueError("Pin count exceeds pins on the lane") |
| 130 | |
| 131 | self.frames.append(self.frame_rolls[:]) |
| 132 | self.frame_rolls = [] |
| 133 | # Edge Case: Validate tenth frame fill balls |
| 134 | if self.frame_rolls[0] == 10: # First roll was a strike |
| 135 | # Second roll can be anything, third roll depends on second |
| 136 | if self.frame_rolls[1] != 10 and sum(self.frame_rolls[1:]) > 10: |
| 137 | self.frame_rolls = self.frame_rolls[:2] # Remove invalid third roll |
| 138 | raise ValueError("Pin count exceeds pins on the lane") |
| 139 | elif self.frame_rolls[0] != 10 and sum(self.frame_rolls[:2]) == 10: # First two rolls were a spare |
| 140 | # Third roll is a fill ball, no validation needed beyond pin count |
| 141 | pass |
| 142 | else: # Open frame, shouldn't have a third roll |
| 143 | self.frame_rolls = self.frame_rolls[:2] # Remove invalid third roll |
| 144 | raise ValueError("Pin count exceeds pins on the lane") |
| 145 | |
| 146 | self.frames.append(self.frame_rolls[:]) |
| 147 | self.frame_rolls = [] |
| 148 | elif len(self.frame_rolls) == 3: |
| 149 | # Edge Case: Validate tenth frame fill balls |
| 150 | if self.frame_rolls[0] == 10: # First roll was a strike |
| 151 | # Second roll can be anything, third roll depends on second |
| 152 | if self.frame_rolls[1] != 10 and sum(self.frame_rolls[1:]) > 10: |
| 153 | self.frame_rolls = self.frame_rolls[:2] # Remove invalid third roll |
| 154 | raise ValueError("Pin count exceeds pins on the lane") |
| 155 | elif self.frame_rolls[0] != 10 and sum(self.frame_rolls[:2]) == 10: # First two rolls were a spare |
| 156 | # Third roll is a fill ball, no validation needed beyond pin count |
| 157 | pass |
| 158 | else: # Open frame, shouldn't have a third roll |
| 159 | self.frame_rolls = self.frame_rolls[:2] # Remove invalid third roll |
| 160 | raise ValueError("Pin count exceeds pins on the lane") |
| 161 | |
| 162 | self.frames.append(self.frame_rolls[:]) |
| 163 | self.frame_rolls = [] |
| 164 | |
| 165 | def is_tenth_frame_complete(self): |
| 166 | """ |
| 167 | Check if the tenth frame is complete. |
| 168 | |
| 169 | Returns: |
| 170 | bool: True if tenth frame is complete, False otherwise |
| 171 | """ |
| 172 | # If we haven't completed 10 frames yet |
| 173 | if len(self.frames) < 9: # Less than 9 complete frames |
| 174 | return False |
| 175 | |
| 176 | # If we have exactly 9 complete frames, check current frame rolls |
| 177 | if len(self.frames) == 9: |
| 178 | # We're still rolling in the tenth frame (in self.frame_rolls) |
| 179 | # Check if tenth frame is complete based on self.frame_rolls |
| 180 | if len(self.frame_rolls) >= 1 and self.frame_rolls[0] == 10: # Strike |
| 181 | # Need two more rolls |
| 182 | return len(self.frame_rolls) == 3 |
| 183 | elif len(self.frame_rolls) >= 2 and sum(self.frame_rolls[:2]) == 10: # Spare |
| 184 | # Need one more roll |
| 185 | return len(self.frame_rolls) == 3 |
| 186 | elif len(self.frame_rolls) >= 2: # Open frame |
| 187 | # Only need two rolls |
| 188 | return True |
| 189 | else: |
| 190 | return False |
| 191 | |
| 192 | # If we have 10 complete frames |
| 193 | if len(self.frames) >= 10: |
| 194 | tenth_frame = self.frames[9] |
| 195 | |
| 196 | # Edge Case: Tenth frame is a strike |
| 197 | if len(tenth_frame) >= 1 and tenth_frame[0] == 10: |
| 198 | # Need two more rolls |
| 199 | return len(tenth_frame) == 3 |
| 200 | # Edge Case: Tenth frame is a spare |
| 201 | elif len(tenth_frame) >= 2 and sum(tenth_frame[:2]) == 10: |
| 202 | # Need one more roll |
| 203 | return len(tenth_frame) == 3 |
| 204 | # Edge Case: Tenth frame is open |
| 205 | else: |
| 206 | # Only need two rolls |
| 207 | return len(tenth_frame) == 2 |
| 208 | |
| 209 | return False |
| 210 | |
| 8 | 211 | def score(self): |
| 9 | | pass |
| 212 | """ |
| 213 | Calculate the total score for the game. |
| 214 | |
| 215 | Returns: |
| 216 | int: Total score for the game |
| 217 | |
| 218 | Raises: |
| 219 | IndexError: If game is not yet complete |
| 220 | """ |
| 221 | # Edge Case: Game not complete |
| 222 | if len(self.frames) < 10 or not self.is_tenth_frame_complete(): |
| 223 | raise IndexError("game is not yet complete") |
| 224 | |
| 225 | total_score = 0 |
| 226 | roll_index = 0 |
| 227 | |
| 228 | # Edge Case: Score calculation for each frame |
| 229 | for frame_num in range(10): |
| 230 | frame = self.frames[frame_num] |
| 231 | |
| 232 | if frame[0] == 10: # Strike |
| 233 | # Edge Case: Strike scoring |
| 234 | total_score += 10 |
| 235 | # Add next two rolls |
| 236 | if frame_num < 9: # Not tenth frame |
| 237 | next_frame = self.frames[frame_num + 1] |
| 238 | if len(next_frame) >= 1: |
| 239 | total_score += next_frame[0] |
| 240 | if len(next_frame) >= 2: |
| 241 | total_score += next_frame[1] |
| 242 | elif frame_num < 8 and len(next_frame) == 1: # If next frame is also a strike |
| 243 | next_next_frame = self.frames[frame_num + 2] |
| 244 | if len(next_next_frame) >= 1: |
| 245 | total_score += next_next_frame[0] |
| 246 | else: # Tenth frame |
| 247 | if len(frame) >= 2: |
| 248 | total_score += frame[1] |
| 249 | if len(frame) >= 3: |
| 250 | total_score += frame[2] |
| 251 | elif sum(frame[:2]) == 10: # Spare |
| 252 | # Edge Case: Spare scoring |
| 253 | total_score += 10 |
| 254 | # Add next roll |
| 255 | if frame_num < 9: # Not tenth frame |
| 256 | next_frame = self.frames[frame_num + 1] |
| 257 | if len(next_frame) >= 1: |
| 258 | total_score += next_frame[0] |
| 259 | else: # Tenth frame |
| 260 | if len(frame) >= 3: |
| 261 | total_score += frame[2] |
| 262 | else: # Open frame |
| 263 | # Edge Case: Open frame scoring |
| 264 | total_score += sum(frame[:2]) |
| 265 | |
| 266 | return total_score |
| 267 | |
| 268 | # Handled Edge Cases: Invalid number of pins, too many pins, game already finished, strike in regular frame, two rolls in regular frame, invalid frame total, tenth frame validation, invalid tenth frame fill balls, tenth frame is a strike, tenth frame is a spare, tenth frame is open, game not complete, strike scoring, spare scoring, open frame scoring |