| 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 cannot be negative") |
| 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, game is over |
| 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, we can have up to 2 more rolls |
| 38 | elif tenth_frame[0] == 10: |
| 39 | if len(tenth_frame) == 3: |
| 40 | raise IndexError("cannot roll after game is over") |
| 41 | # If it's a spare, we can have 1 more roll |
| 42 | elif sum(tenth_frame) == 10 and len(tenth_frame) == 2: |
| 43 | if len(tenth_frame) == 3: # Already added the bonus roll |
| 44 | raise IndexError("cannot roll after game is over") |
| 45 | |
| 46 | # Edge Case: Validate second roll before adding |
| 47 | if len(self.frame_rolls) == 1 and self.frame_rolls[0] != 10: |
| 48 | if self.frame_rolls[0] + pins > 10: |
| 49 | raise ValueError("pin count exceeds pins on the lane") |
| 50 | |
| 51 | # Add the roll to current frame |
| 52 | self.frame_rolls.append(pins) |
| 53 | |
| 54 | # Handle frame completion |
| 55 | if self.current_frame < 10: |
| 56 | # Regular frames |
| 57 | if self.frame_rolls[0] == 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: |
| 62 | # 10th frame handling |
| 63 | tenth_frame_complete = False |
| 64 | |
| 65 | # Strike in 10th frame - need 3 rolls total |
| 66 | if len(self.frame_rolls) == 3 and self.frame_rolls[0] == 10: |
| 67 | tenth_frame_complete = True |
| 68 | # Spare in 10th frame - need 3 rolls total |
| 69 | elif len(self.frame_rolls) == 3 and sum(self.frame_rolls[:2]) == 10: |
| 70 | tenth_frame_complete = True |
| 71 | # Open frame in 10th frame - need 2 rolls total |
| 72 | elif len(self.frame_rolls) == 2 and sum(self.frame_rolls) < 10: |
| 73 | tenth_frame_complete = True |
| 74 | |
| 75 | if tenth_frame_complete: |
| 76 | self.frames.append(self.frame_rolls) |
| 77 | self.frame_rolls = [] |
| 78 | |
| 79 | |
| 80 | |
| 81 | self.rolls.append(pins) |
| 82 | |
| 8 | 83 | def score(self): |
| 9 | | pass |
| 84 | """ |
| 85 | Calculate the total score for the game. |
| 86 | |
| 87 | Returns: |
| 88 | int: Total score of the game |
| 89 | |
| 90 | Raises: |
| 91 | IndexError: If the game is not yet complete |
| 92 | """ |
| 93 | # Edge Case: Game not complete |
| 94 | if len(self.frames) < 10: |
| 95 | raise IndexError("score cannot be taken until the end of the game") |
| 96 | |
| 97 | # Edge Case: 10th frame incomplete |
| 98 | if len(self.frames) == 10: |
| 99 | tenth_frame = self.frames[9] |
| 100 | # If it's not a strike or spare, it should have 2 rolls |
| 101 | if not (tenth_frame[0] == 10 or sum(tenth_frame[:2]) == 10): |
| 102 | if len(tenth_frame) < 2: |
| 103 | raise IndexError("score cannot be taken until the end of the game") |
| 104 | # If it's a strike or spare, it should have 3 rolls |
| 105 | elif len(tenth_frame) < 3: |
| 106 | raise IndexError("score cannot be taken until the end of the game") |
| 107 | |
| 108 | total_score = 0 |
| 109 | roll_index = 0 |
| 110 | |
| 111 | for frame_num in range(10): |
| 112 | if frame_num < 9: # Frames 1-9 |
| 113 | if self.rolls[roll_index] == 10: # Strike |
| 114 | total_score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2] |
| 115 | roll_index += 1 |
| 116 | elif self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare |
| 117 | total_score += 10 + self.rolls[roll_index + 2] |
| 118 | roll_index += 2 |
| 119 | else: # Open frame |
| 120 | total_score += self.rolls[roll_index] + self.rolls[roll_index + 1] |
| 121 | roll_index += 2 |
| 122 | else: # 10th frame |
| 123 | total_score += sum(self.rolls[roll_index:]) |
| 124 | |
| 125 | return total_score |
| 126 | |
| 127 | # Handled Edge Cases: Invalid number of pins, too many pins, game already completed, too many pins in frame, second roll exceeds remaining pins, third roll in 10th frame exceeds remaining pins, game not complete, 10th frame incomplete |