xas

Finished
11class BowlingGame:
22 def __init__(self):
3 pass
4
3 """
4 Initialize a new bowling game.
5 """
6 self.rolls = []
7 self.current_roll = 0
8
59 def roll(self, pins):
6 pass
7
10 """
11 Record a roll in the game.
12
13 Args:
14 pins (int): Number of pins knocked down in this roll.
15
16 Raises:
17 ValueError: If the number of pins is invalid.
18 """
19 # Edge Case: Check if game is already complete
20 if self._is_game_complete():
21 raise IndexError("Game is already complete")
22
23 # Edge Case: Invalid fill ball after spare in 10th frame
24 if len(self.rolls) == 20 and self.rolls[18] + self.rolls[19] == 10 and self.rolls[18] != 10 and pins > 10:
25 raise ValueError("Fill ball cannot exceed 10 pins")
26
27 # Edge Case: Invalid pin count (negative or more than 10)
28 if pins < 0 or pins > 10:
29 raise ValueError("Pins must be between 0 and 10")
30
31 # Edge Case: Second roll in a frame exceeds 10 pins
32 if len(self.rolls) % 2 == 1 and not self._is_tenth_frame():
33 # Only check if previous roll was not a strike
34 if self.rolls[-1] != 10 and self.rolls[-1] + pins > 10:
35 raise ValueError("Cannot knock down more than 10 pins in a frame")
36
37 # Edge Case: Invalid fill ball after spare in 10th frame
38 if len(self.rolls) == 20 and self.rolls[18] + self.rolls[19] == 10 and self.rolls[18] != 10 and pins > 10:
39 raise ValueError("Fill ball cannot exceed 10 pins")
40
41 # Edge Case: Invalid third roll in 10th frame
42 if len(self.rolls) == 20:
43 # If first two rolls in 10th frame are not a strike or spare
44 if self.rolls[18] + self.rolls[19] < 10:
45 raise ValueError("Cannot roll after game is over")
46 # If there was a strike in 10th frame and second roll is not valid
47 if self.rolls[18] == 10 and self.rolls[19] < 10 and self.rolls[19] + pins > 10:
48 raise ValueError("Invalid fill ball")
49 # If there was a strike in 10th frame and second roll is also a strike
50 # Third roll can't exceed 10
51 if self.rolls[18] == 10 and self.rolls[19] == 10 and pins > 10:
52 raise ValueError("Invalid fill ball")
53
54 self.rolls.append(pins)
55
856 def score(self):
9 pass
57 """
58 Calculate the total score for the game.
59
60 Returns:
61 int: The total score of the game.
62
63 Raises:
64 ValueError: If the game is not yet complete.
65 """
66 # Edge Case: Game not complete (not enough rolls)
67 if len(self.rolls) < 12: # Minimum rolls for a game with no strikes/spares
68 # Check if we have 10 frames worth of rolls
69 frame_count = 0
70 roll_index = 0
71 while roll_index < len(self.rolls) and frame_count < 10:
72 if self._is_strike_at(roll_index):
73 frame_count += 1
74 roll_index += 1
75 else:
76 if roll_index + 1 < len(self.rolls):
77 frame_count += 1
78 roll_index += 2
79 else:
80 break
81
82 if frame_count < 10:
83 raise IndexError("Game is not yet complete")
84
85 # Edge Case: Incomplete 10th frame (19 rolls but spare needs fill ball)
86 # Check if we actually have 10 complete frames
87 frame_count = 0
88 roll_index = 0
89 while roll_index < len(self.rolls) and frame_count < 10:
90 if self._is_strike_at(roll_index):
91 frame_count += 1
92 roll_index += 1
93 else:
94 if roll_index + 1 < len(self.rolls):
95 frame_count += 1
96 roll_index += 2
97 else:
98 break
99
100 if len(self.rolls) == 19 and frame_count < 10:
101 raise IndexError("Score cannot be taken until the end of the game")
102
103 if len(self.rolls) == 20:
104 # If 10th frame is open (no need for 21st roll)
105 if self.rolls[18] + self.rolls[19] < 10:
106 pass # Game is complete
107 # If 10th frame is spare (need 21st roll)
108 elif self.rolls[18] + self.rolls[19] == 10:
109 raise ValueError("Score cannot be taken until the end of the game")
110 # If 10th frame first roll is strike (need to check second roll)
111 elif self.rolls[18] == 10:
112 if self.rolls[19] < 10:
113 pass # Game is complete (e.g., 10, 5, 3)
114 elif self.rolls[19] == 10:
115 raise ValueError("Score cannot be taken until the end of the game") # Need 21st roll
116
117 if len(self.rolls) == 21:
118 # Edge Case: Invalid fill balls
119 # If 10th frame is spare, 21st roll can be 0-10
120 if self.rolls[18] + self.rolls[19] == 10 and self.rolls[18] != 10:
121 if self.rolls[20] < 0 or self.rolls[20] > 10:
122 raise ValueError("invalid fill balls")
123 # If 10th frame is strike followed by non-strike
124 elif self.rolls[18] == 10 and self.rolls[19] < 10:
125 if self.rolls[20] < 0 or self.rolls[19] + self.rolls[20] > 10:
126 raise ValueError("invalid fill balls")
127 # If 10th frame is two strikes
128 elif self.rolls[18] == 10 and self.rolls[19] == 10:
129 if self.rolls[20] < 0 or self.rolls[20] > 10:
130 raise ValueError("invalid fill balls")
131
132 score = 0
133 roll_index = 0
134
135 for frame in range(10):
136 if self._is_strike_at(roll_index): # Strike
137 score += 10 + self._strike_bonus(roll_index)
138 roll_index += 1
139 elif self._is_spare_at(roll_index): # Spare
140 score += 10 + self._spare_bonus(roll_index)
141 roll_index += 2
142 else: # Open frame
143 score += self.rolls[roll_index] + self.rolls[roll_index + 1]
144 roll_index += 2
145
146 return score
147
148 def _is_strike_at(self, roll_index):
149 """
150 Check if a roll is a strike.
151
152 Args:
153 roll_index (int): Index of the roll to check.
154
155 Returns:
156 bool: True if the roll is a strike, False otherwise.
157 """
158 return self.rolls[roll_index] == 10
159
160 def _is_spare_at(self, roll_index):
161 """
162 Check if a frame is a spare.
163
164 Args:
165 roll_index (int): Index of the first roll in the frame.
166
167 Returns:
168 bool: True if the frame is a spare, False otherwise.
169 """
170 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
171
172 def _strike_bonus(self, roll_index):
173 """
174 Calculate the bonus for a strike.
175
176 Args:
177 roll_index (int): Index of the strike roll.
178
179 Returns:
180 int: The bonus points for the strike.
181 """
182 # Edge Case: Not enough rolls after strike to calculate bonus
183 if roll_index + 2 >= len(self.rolls):
184 return 0
185 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
186
187 def _spare_bonus(self, roll_index):
188 """
189 Calculate the bonus for a spare.
190
191 Args:
192 roll_index (int): Index of the first roll in the spare frame.
193
194 Returns:
195 int: The bonus points for the spare.
196 """
197 # Edge Case: Not enough rolls after spare to calculate bonus
198 if roll_index + 2 >= len(self.rolls):
199 return 0
200 return self.rolls[roll_index + 2]
201
202 def _is_game_complete(self):
203 """
204 Check if the game is complete.
205
206 Returns:
207 bool: True if the game is complete, False otherwise.
208 """
209 if len(self.rolls) < 10:
210 return False
211
212 # Count complete frames
213 frame_count = 0
214 roll_index = 0
215 while roll_index < len(self.rolls) and frame_count < 10:
216 if self._is_strike_at(roll_index):
217 frame_count += 1
218 roll_index += 1
219 else:
220 if roll_index + 1 < len(self.rolls):
221 frame_count += 1
222 roll_index += 2
223 else:
224 break
225
226 # If we don't have 10 complete frames, game is not complete
227 if frame_count < 10:
228 return False
229
230 # Find the start of the 10th frame
231 tenth_frame_start = 0
232 temp_frame_count = 0
233 temp_roll_index = 0
234 while temp_roll_index < len(self.rolls) and temp_frame_count < 9:
235 if self._is_strike_at(temp_roll_index):
236 temp_roll_index += 1
237 else:
238 temp_roll_index += 2
239 temp_frame_count += 1
240 tenth_frame_start = temp_roll_index
241
242 # Check 10th frame status
243 if tenth_frame_start >= len(self.rolls):
244 # No 10th frame yet (shouldn't happen if frame_count == 10)
245 return False
246
247 rolls_in_tenth = len(self.rolls) - tenth_frame_start
248
249 if self._is_strike_at(tenth_frame_start):
250 # 10th frame is a strike
251 return rolls_in_tenth >= 3
252 else:
253 # 10th frame is open or spare
254 if tenth_frame_start + 1 >= len(self.rolls):
255 return False
256
257 frame_total = self.rolls[tenth_frame_start] + self.rolls[tenth_frame_start + 1]
258 if frame_total == 10:
259 # Spare in 10th frame
260 return rolls_in_tenth >= 3
261 else:
262 # Open frame in 10th frame
263 return rolls_in_tenth >= 2
264
265 def _is_tenth_frame(self):
266 """
267 Check if we are currently in the tenth frame.
268
269 Returns:
270 bool: True if we are in the tenth frame, False otherwise.
271 """
272 # Count completed frames
273 frame_count = 0
274 roll_index = 0
275
276 while roll_index < len(self.rolls) and frame_count < 9:
277 if self._is_strike_at(roll_index):
278 roll_index += 1
279 else:
280 roll_index += 2
281 frame_count += 1
282
283 # If we've completed 9 frames, we're in the 10th
284 return frame_count == 9
285
286# Handled Edge Cases: Invalid pin count, game already over, second roll exceeding 10 pins, invalid fill balls, incomplete game, invalid fill balls in scoring
287# Handled Edge Cases: Invalid pin count, game already over, second roll exceeding 10 pins, invalid fill balls in 10th frame, invalid third roll in 10th frame
288# Handled Edge Cases: Game not complete, incomplete 10th frame, invalid fill balls
289# Handled Edge Cases: Not enough rolls after strike to calculate bonus
290# Handled Edge Cases: Not enough rolls after spare to calculate bonus
Test NameStatus
test_bonus_rolls_for_a_strike_in_the_last_frame_must_be_rolled_before_score_can_be_calculated
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_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_the_second_bonus_rolls_after_a_strike_in_the_last_frame_cannot_be_a_strike_if_the_first_one_is_not_a_strike
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_bonus_rolls_after_a_strike_in_the_last_frame_cannot_score_more_than_10_points
Pass
test_two_rolls_in_a_frame_cannot_score_more_than_10_points
Pass

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