armor

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 pins is negative or greater than 10
18 IndexError: If game is already over
19 """
20 # Edge Case: Invalid number of pins (negative or more than 10)
21 if pins < 0 or pins > 10:
22 raise ValueError("pins must be between 0 and 10")
23
24 # Edge Case: Game is already over (10 frames completed)
25 if self.is_game_over():
26 raise IndexError("cannot roll after game is over")
27
28 # Edge Case: In frames other than the 10th, can't knock down more pins than remaining
29 # We need to determine if this is the first or second roll of the current frame
30 frame_index = self._get_current_frame_index()
31
32 # If this is the second roll of a frame (not the 10th frame)
33 if frame_index < 9 and not self._is_first_roll_of_frame():
34 # Get the pins from the first roll of this frame
35 first_roll_pins = self.rolls[len(self.rolls) - 1]
36 # Edge Case: Second roll can't knock down more pins than remaining
37 if first_roll_pins + pins > 10:
38 raise ValueError("invalid roll")
39
40 self.rolls.append(pins)
41
842 def score(self):
9 pass
43 """
44 Calculate the total score for the game.
45
46 Returns:
47 int: The total score of the game
48
49 Raises:
50 IndexError: If the game is not yet complete
51 """
52 # Edge Case: Game is not complete yet
53 if not self.is_game_over():
54 raise IndexError("Score cannot be taken until the end of the game")
55
56 total_score = 0
57 roll_index = 0
58
59 for frame in range(10):
60 # Edge Case: Incomplete game (should not happen due to check above, but for safety)
61 if roll_index >= len(self.rolls):
62 raise IndexError("incomplete game")
63
64 if self.is_strike(roll_index): # Strike
65 # Edge Case: Not enough rolls to calculate strike bonus
66 if roll_index + 2 >= len(self.rolls):
67 raise IndexError("incomplete game")
68 total_score += 10 + self.strike_bonus(roll_index)
69 roll_index += 1
70 elif self.is_spare(roll_index): # Spare
71 # Edge Case: Not enough rolls to calculate spare bonus
72 if roll_index + 2 >= len(self.rolls):
73 raise IndexError("incomplete game")
74 total_score += 10 + self.spare_bonus(roll_index)
75 roll_index += 2
76 else: # Open frame
77 total_score += self.sum_of_balls_in_frame(roll_index)
78 roll_index += 2
79
80 return total_score
81
82 def is_strike(self, roll_index):
83 """
84 Check if a roll is a strike.
85
86 Args:
87 roll_index (int): Index of the roll to check
88
89 Returns:
90 bool: True if the roll is a strike, False otherwise
91 """
92 return self.rolls[roll_index] == 10
93
94 def is_spare(self, roll_index):
95 """
96 Check if a frame is a spare.
97
98 Args:
99 roll_index (int): Index of the first roll of the frame
100
101 Returns:
102 bool: True if the frame is a spare, False otherwise
103 """
104 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
105
106 def sum_of_balls_in_frame(self, roll_index):
107 """
108 Calculate the sum of pins knocked down in a frame.
109
110 Args:
111 roll_index (int): Index of the first roll of the frame
112
113 Returns:
114 int: Sum of pins knocked down in the frame
115 """
116 return self.rolls[roll_index] + self.rolls[roll_index + 1]
117
118 def spare_bonus(self, roll_index):
119 """
120 Calculate the bonus for a spare.
121
122 Args:
123 roll_index (int): Index of the first roll of the spare frame
124
125 Returns:
126 int: The bonus points for the spare
127 """
128 return self.rolls[roll_index + 2]
129
130 def strike_bonus(self, roll_index):
131 """
132 Calculate the bonus for a strike.
133
134 Args:
135 roll_index (int): Index of the strike roll
136
137 Returns:
138 int: The bonus points for the strike
139 """
140 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
141
142 def is_game_over(self):
143 """
144 Check if the game is over (10 frames completed).
145
146 Returns:
147 bool: True if the game is over, False otherwise
148 """
149 # Edge Case: Game with less than 10 frames
150 if len(self.rolls) < 10:
151 return False
152
153 # Count complete frames
154 frame_count = 0
155 roll_index = 0
156
157 # Process first 9 frames
158 while frame_count < 9 and roll_index < len(self.rolls):
159 if self.is_strike(roll_index):
160 roll_index += 1
161 else:
162 roll_index += 2
163 frame_count += 1
164
165 # Process 10th frame
166 if frame_count < 9: # Should not happen but for safety
167 return False
168
169 # Edge Case: 10th frame processing
170 if roll_index >= len(self.rolls):
171 return False
172
173 # 10th frame is a strike
174 if self.is_strike(roll_index):
175 # Need two more rolls
176 if roll_index + 2 >= len(self.rolls):
177 return False
178 # Edge Case: Invalid fill balls after strike in 10th frame
179 if self.rolls[roll_index+1] == 10 and self.rolls[roll_index+2] > 10:
180 raise ValueError("invalid fill balls")
181 # Edge Case: Invalid fill balls after strike in 10th frame
182 if self.rolls[roll_index+1] != 10 and self.rolls[roll_index+1] + self.rolls[roll_index+2] > 10:
183 raise ValueError("invalid fill balls")
184 return roll_index + 3 == len(self.rolls)
185
186 # Edge Case: Not enough rolls for 10th frame
187 if roll_index + 1 >= len(self.rolls):
188 return False
189
190 # 10th frame is a spare
191 if self.is_spare(roll_index):
192 # Need one more roll
193 if roll_index + 2 >= len(self.rolls):
194 return False
195 # Edge Case: Invalid fill ball after spare in 10th frame
196 if self.rolls[roll_index+2] > 10:
197 raise ValueError("invalid fill balls")
198 return roll_index + 3 == len(self.rolls)
199
200 # 10th frame is open
201 return roll_index + 2 == len(self.rolls)
202
203 def _get_current_frame_index(self):
204 """
205 Get the index of the current frame (0-9).
206
207 Returns:
208 int: The current frame index
209 """
210 frame_count = 0
211 roll_index = 0
212
213 # Count complete frames
214 while frame_count < 10 and roll_index < len(self.rolls):
215 if frame_count < 9: # Frames 1-9
216 if self.rolls[roll_index] == 10: # Strike
217 roll_index += 1
218 else: # Not a strike
219 if roll_index + 1 < len(self.rolls):
220 roll_index += 2
221 else:
222 break # Incomplete frame
223 else: # 10th frame
224 break # We're in or before the 10th frame
225 frame_count += 1
226
227 return frame_count
228
229 def _is_first_roll_of_frame(self):
230 """
231 Check if the current roll is the first roll of its frame.
232
233 Returns:
234 bool: True if this is the first roll of the frame, False otherwise
235 """
236 if len(self.rolls) == 0:
237 return True
238
239 frame_index = self._get_current_frame_index()
240
241 # If we're in the 10th frame, we need special handling
242 if frame_index >= 9:
243 return self._is_first_roll_of_tenth_frame()
244
245 # For frames 1-9, we need to determine if we're starting a new frame
246 # Count how many rolls have been used for complete frames so far
247 frame_count = 0
248 roll_index = 0
249
250 while frame_count < frame_index and roll_index < len(self.rolls):
251 if self.rolls[roll_index] == 10: # Strike
252 roll_index += 1
253 else: # Not a strike
254 roll_index += 2
255 frame_count += 1
256
257 # If we've used exactly the right number of rolls for the complete frames,
258 # then this is the first roll of the current frame
259 return roll_index == len(self.rolls)
260
261 def _is_first_roll_of_tenth_frame(self):
262 """
263 Check if the current roll is the first roll of the 10th frame.
264
265 Returns:
266 bool: True if this is the first roll of the 10th frame, False otherwise
267 """
268 # Count rolls in first 9 frames
269 frame_count = 0
270 roll_index = 0
271
272 while frame_count < 9 and roll_index < len(self.rolls):
273 if self.rolls[roll_index] == 10: # Strike
274 roll_index += 1
275 else: # Not a strike
276 roll_index += 2
277 frame_count += 1
278
279 # If we've processed exactly 9 frames, the next roll is the first of the 10th
280 return frame_count == 9 and roll_index == len(self.rolls)
281
282 # Handled Edge Cases: Invalid number of pins, game already over, second roll exceeding remaining pins,
283 # incomplete game when scoring, not enough rolls for strike/spare bonus, invalid fill balls after strike/spare in 10th frame,
284 # not enough rolls for 10th frame completion
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.