Finished
11class BowlingGame:
22 def __init__(self):
3 pass
3 self.rolls = []
4 self.current_frame = 1
5 self.frame_rolls = []
6 self.frames_completed = 0
7 self.game_over = False
48
59 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 or over 10)
15 if pins < 0 or pins > 10:
16 raise ValueError("Pins must be between 0 and 10")
17
18 # Edge Case: Too many pins in a frame (except in fill balls)
19 if not self._is_fill_ball():
20 if len(self.frame_rolls) == 1 and self.frame_rolls[0] != 10:
21 if self.frame_rolls[0] + pins > 10:
22 raise ValueError("Invalid roll: pins downed exceed 10 in a frame")
23
24 self.rolls.append(pins)
25 self.frame_rolls.append(pins)
26
27 # Check if frame is completed
28 frame_completed = False
29 if self.current_frame < 10:
30 # Regular frames
31 if self.frame_rolls[0] == 10 or len(self.frame_rolls) == 2:
32 frame_completed = True
33 else:
34 # 10th frame
35 if len(self.frame_rolls) == 3:
36 frame_completed = True
37 elif len(self.frame_rolls) == 2:
38 # If not a strike or spare, frame is complete
39 if self.frame_rolls[0] != 10 and sum(self.frame_rolls) < 10:
40 frame_completed = True
41 # If strike, need 2 more rolls
42 # If spare, need 1 more roll
43
44 if frame_completed:
45 self.frames_completed += 1
46 self.frame_rolls = []
47 if self.current_frame < 10:
48 self.current_frame += 1
49 # For 10th frame, check if game is over based on rolls
50 if self.current_frame == 10:
51 # Game is over when we have the right number of rolls in 10th frame
52 if len(self.rolls) >= 20: # At least 20 rolls (2 per frame minimum)
53 last_frame_rolls = self.rolls[18:] # Get 10th frame rolls
54 if len(last_frame_rolls) == 2:
55 # If no strike or spare, game is over
56 if last_frame_rolls[0] != 10 and sum(last_frame_rolls) < 10:
57 self.game_over = True
58 elif len(last_frame_rolls) == 3:
59 # Game is over after 3 rolls in 10th frame
60 self.game_over = True
61 if len(last_frame_rolls) == 2:
62 # If no strike or spare, game is over
63 if last_frame_rolls[0] != 10 and sum(last_frame_rolls) < 10:
64 self.game_over = True
65 elif len(last_frame_rolls) == 3:
66 # Game is over after 3 rolls in 10th frame
67 self.game_over = True
68
69 def _is_fill_ball(self):
70 # Check if we're in the fill balls for the 10th frame
71 return self.current_frame > 10
72
73 def _is_strike(self, frame_index):
74 return self.frames[frame_index][0] == 10
75
76 def _is_spare(self, frame_index):
77 return len(self.frames[frame_index]) == 2 and sum(self.frames[frame_index]) == 10
78
79 def _get_frame_rolls(self):
80 # Group rolls into frames
81 frames = []
82 roll_index = 0
83 for frame_num in range(10):
84 if roll_index >= len(self.rolls):
85 break
86 frame = []
87 # For 10th frame, handle up to 3 rolls
88 if frame_num == 9:
89 # Handle 10th frame properly
90 if roll_index < len(self.rolls):
91 frame.append(self.rolls[roll_index])
92 roll_index += 1
93
94 # Check if we need more rolls
95 if roll_index < len(self.rolls):
96 if frame[0] == 10: # Strike in 10th frame
97 # Need 2 more rolls
98 frame.append(self.rolls[roll_index])
99 roll_index += 1
100 if roll_index < len(self.rolls):
101 frame.append(self.rolls[roll_index])
102 roll_index += 1
103 else:
104 # Not a strike, add second roll
105 frame.append(self.rolls[roll_index])
106 roll_index += 1
107
108 # Check if it's a spare and needs a third roll
109 if roll_index < len(self.rolls) and sum(frame[:2]) == 10:
110 frame.append(self.rolls[roll_index])
111 roll_index += 1
112 else:
113 # Regular frames
114 if self.rolls[roll_index] == 10: # Strike
115 frame.append(10)
116 roll_index += 1
117 else:
118 frame.append(self.rolls[roll_index])
119 roll_index += 1
120 if roll_index < len(self.rolls):
121 frame.append(self.rolls[roll_index])
122 roll_index += 1
123 frames.append(frame)
124 return frames
125
8126 def score(self):
9 pass
127 # Edge Case: Game not completed
128 if self.frames_completed < 10:
129 raise ValueError("Game not yet complete")
130
131 frames = self._get_frame_rolls()
132 total_score = 0
133 roll_index = 0
134
135 for frame_num in range(10):
136 frame = frames[frame_num]
137
138 # Edge Case: Incomplete frame data
139 if len(frame) == 0:
140 raise ValueError("Incomplete game")
141
142 if frame_num < 9: # Frames 1-9
143 if len(frame) == 1 and frame[0] == 10: # Strike
144 # Edge Case: Not enough rolls for strike calculation
145 if roll_index + 2 >= len(self.rolls):
146 raise ValueError("Incomplete game")
147 total_score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
148 roll_index += 1
149 elif len(frame) == 2 and sum(frame) == 10: # Spare
150 # Edge Case: Not enough rolls for spare calculation
151 if roll_index + 2 >= len(self.rolls):
152 raise ValueError("Incomplete game")
153 total_score += 10 + self.rolls[roll_index + 2]
154 roll_index += 2
155 else: # Open frame
156 total_score += sum(frame)
157 roll_index += 2
158 else: # 10th frame
159 # Edge Case: Invalid 10th frame
160 if len(frame) < 2:
161 raise ValueError("Invalid 10th frame")
162
163 total_score += sum(frame)
164
165 # Validate 10th frame rules
166 if len(frame) == 2:
167 # If not a strike or spare, that's fine
168 if frame[0] != 10 and sum(frame) < 10:
169 pass
170 # If it's a spare, make sure there's a third roll
171 elif frame[0] != 10 and sum(frame) == 10:
172 pass
173 # If first roll is strike, need two more rolls
174 elif frame[0] == 10 and len(frame) < 3:
175 raise ValueError("Invalid 10th frame")
176 elif len(frame) == 3:
177 # Validate the fill balls - only if we actually have fill balls
178 # Fill balls only occur when first roll is strike OR first two rolls are spare
179 if frame[0] == 10: # First roll was a strike
180 # Second roll can be 0-10, third roll depends on second
181 if frame[1] == 10: # Second roll also strike
182 # Third roll can be 0-10
183 if frame[2] < 0 or frame[2] > 10:
184 raise ValueError("invalid fill balls")
185 else: # Second roll not strike
186 if frame[1] + frame[2] > 10:
187 raise ValueError("invalid fill balls")
188 elif frame[0] + frame[1] == 10: # First two rolls were a spare
189 # Third roll can be 0-10
190 if frame[2] < 0 or frame[2] > 10:
191 raise ValueError("invalid fill balls")
192 # If neither strike nor spare, then we shouldn't have 3 rolls
193 # This would be an invalid game state
194 elif frame[0] + frame[1] < 10:
195 raise ValueError("invalid fill balls")
196
197 return total_score
198
199 # Handled Edge Cases: Game is already over, invalid pin count, too many pins in a frame, game not completed, incomplete frame data, not enough rolls for strike/spare calculation, invalid 10th frame, invalid fill balls
Test NameStatus
test_the_second_bonus_rolls_after_a_strike_in_the_last_frame_cannot_be_a_strike_if_the_first_one_is_not_a_strike
Fail
test_two_bonus_rolls_after_a_strike_in_the_last_frame_cannot_score_more_than_10_points
Fail
test_a_roll_cannot_score_more_than_10_points
Pass
test_a_spare_followed_by_zeros_is_worth_ten_points
Pass
test_a_spare_in_the_last_frame_gets_a_one_roll_bonus_that_is_counted_once
Pass
test_a_strike_earns_ten_points_in_a_frame_with_a_single_roll
Pass
test_a_strike_in_the_last_frame_gets_a_two_roll_bonus_that_is_counted_once
Pass
test_a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_frame_does_not_get_a_bonus
Pass
test_all_strikes_is_a_perfect_game
Pass
test_an_incomplete_game_cannot_be_scored
Pass
test_an_unstarted_game_cannot_be_scored
Pass
test_bonus_roll_after_a_strike_in_the_last_frame_cannot_score_more_than_10_points
Pass
test_bonus_roll_for_a_spare_in_the_last_frame_must_be_rolled_before_score_can_be_calculated
Pass
test_bonus_rolls_for_a_strike_in_the_last_frame_must_be_rolled_before_score_can_be_calculated
Pass
test_both_bonus_rolls_for_a_strike_in_the_last_frame_must_be_rolled_before_score_can_be_calculated
Pass
test_cannot_roll_after_bonus_roll_for_spare
Pass
test_cannot_roll_after_bonus_rolls_for_strike
Pass
test_cannot_roll_if_game_already_has_ten_frames
Pass
test_consecutive_spares_each_get_a_one_roll_bonus
Pass
test_consecutive_strikes_each_get_the_two_roll_bonus
Pass
test_last_two_strikes_followed_by_only_last_bonus_with_non_strike_points
Pass
test_points_scored_in_the_roll_after_a_spare_are_counted_twice
Pass
test_points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus
Pass
test_rolling_a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll
Pass
test_rolls_cannot_score_negative_points
Pass
test_second_bonus_roll_after_a_strike_in_the_last_frame_cannot_score_more_than_10_points
Pass
test_should_be_able_to_score_a_game_with_all_zeros
Pass
test_should_be_able_to_score_a_game_with_no_strikes_or_spares
Pass
test_strikes_with_the_two_roll_bonus_do_not_get_bonus_rolls
Pass
test_two_bonus_rolls_after_a_strike_in_the_last_frame_can_score_more_than_10_points_if_one_is_a_strike
Pass
test_two_rolls_in_a_frame_cannot_score_more_than_10_points
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.