Immune

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: Invalid number of pins (negative or more than 10)
20 if pins < 0 or pins > 10:
21 raise ValueError("Pins must be between 0 and 10")
22
23 # Edge Case: Game already completed (10 frames plus any fill balls)
24 if self.is_game_complete():
25 raise ValueError("Game is already finished")
26
27 # Edge Case: Can't knock down more pins than are standing in current frame
28 if self.would_exceed_frame_pins(pins):
29 raise ValueError("Cannot knock down more than 10 pins in a frame")
30
31 self.rolls.append(pins)
32 self.current_roll += 1
33
834 def score(self):
9 pass
35 """
36 Calculate the total score for the game.
37
38 Returns:
39 int: The total score of the game.
40
41 Raises:
42 ValueError: If the game is not yet complete.
43 """
44 # Edge Case: Game not yet complete
45 if not self.is_game_complete():
46 raise ValueError("Game is not yet finished")
47
48 score = 0
49 roll_index = 0
50
51 for frame in range(10):
52 # Edge Case: Strike in regular frames (1-9)
53 if self.is_strike(roll_index) and frame < 9:
54 score += 10 + self.strike_bonus(roll_index)
55 roll_index += 1
56 # Edge Case: Spare in regular frames (1-9)
57 elif self.is_spare(roll_index) and frame < 9:
58 score += 10 + self.spare_bonus(roll_index)
59 roll_index += 2
60 # Edge Case: Open frame in regular frames (1-9)
61 elif frame < 9:
62 score += self.sum_of_balls_in_frame(roll_index)
63 roll_index += 2
64 # Edge Case: Tenth frame handling
65 else: # frame == 9 (10th frame)
66 # Edge Case: Strike in 10th frame
67 if self.is_strike(roll_index):
68 # Edge Case: Invalid fill balls for strike in 10th frame
69 if len(self.rolls) < roll_index + 3:
70 raise ValueError("Score cannot be taken until the end of the game")
71 score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
72 # Edge Case: Spare in 10th frame
73 elif self.is_spare(roll_index):
74 # Edge Case: Invalid fill balls for spare in 10th frame
75 if len(self.rolls) < roll_index + 3:
76 raise ValueError("Score cannot be taken until the end of the game")
77 score += 10 + self.rolls[roll_index + 2]
78 # Edge Case: Open 10th frame
79 else:
80 # Edge Case: Too many rolls in 10th frame
81 if roll_index + 1 >= len(self.rolls):
82 raise ValueError("Score cannot be taken until the end of the game")
83 score += self.sum_of_balls_in_frame(roll_index)
84
85 return score
86
87 def is_game_complete(self):
88 """
89 Check if the game is complete (10 frames have been played).
90
91 Returns:
92 bool: True if the game is complete, False otherwise.
93 """
94 if len(self.rolls) < 12: # Minimum rolls needed (10 strikes)
95 return False
96
97 roll_index = 0
98
99 # Check first 9 frames
100 for frame in range(9):
101 if roll_index >= len(self.rolls):
102 return False
103 if self.is_strike(roll_index):
104 roll_index += 1
105 else:
106 if roll_index + 1 >= len(self.rolls):
107 return False
108 roll_index += 2
109
110 # Check 10th frame
111 if roll_index >= len(self.rolls):
112 return False
113
114 # If 10th frame is a strike, need 2 more rolls
115 if self.rolls[roll_index] == 10:
116 return len(self.rolls) >= roll_index + 3
117
118 # If 10th frame is a spare, need 1 more roll
119 if roll_index + 1 < len(self.rolls) and self.rolls[roll_index] + self.rolls[roll_index + 1] == 10:
120 return len(self.rolls) >= roll_index + 3
121
122 # If 10th frame is open, need exactly 2 rolls
123 return len(self.rolls) >= roll_index + 2
124
125 def is_strike(self, roll_index):
126 """
127 Check if a roll is a strike.
128
129 Args:
130 roll_index (int): Index of the roll to check.
131
132 Returns:
133 bool: True if the roll is a strike, False otherwise.
134 """
135 return roll_index < len(self.rolls) and self.rolls[roll_index] == 10
136
137 def is_spare(self, roll_index):
138 """
139 Check if a frame is a spare.
140
141 Args:
142 roll_index (int): Index of the first roll in the frame.
143
144 Returns:
145 bool: True if the frame is a spare, False otherwise.
146 """
147 return (roll_index + 1 < len(self.rolls) and
148 self.rolls[roll_index] + self.rolls[roll_index + 1] == 10)
149
150 def strike_bonus(self, roll_index):
151 """
152 Calculate the bonus for a strike.
153
154 Args:
155 roll_index (int): Index of the strike roll.
156
157 Returns:
158 int: The bonus points for the strike.
159 """
160 # Edge Case: Not enough rolls for strike bonus
161 if roll_index + 2 >= len(self.rolls):
162 return 0
163 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
164
165 def spare_bonus(self, roll_index):
166 """
167 Calculate the bonus for a spare.
168
169 Args:
170 roll_index (int): Index of the first roll in the spare frame.
171
172 Returns:
173 int: The bonus points for the spare.
174 """
175 # Edge Case: Not enough rolls for spare bonus
176 if roll_index + 2 >= len(self.rolls):
177 return 0
178 return self.rolls[roll_index + 2]
179
180 def sum_of_balls_in_frame(self, roll_index):
181 """
182 Calculate the sum of pins knocked down in a frame.
183
184 Args:
185 roll_index (int): Index of the first roll in the frame.
186
187 Returns:
188 int: The sum of pins knocked down in the frame.
189 """
190 # Edge Case: Not enough rolls in frame
191 if roll_index + 1 >= len(self.rolls):
192 return 0
193 return self.rolls[roll_index] + self.rolls[roll_index + 1]
194
195 def would_exceed_frame_pins(self, pins):
196 """
197 Check if adding pins would exceed 10 pins in the current frame.
198
199 Args:
200 pins (int): Number of pins to be rolled.
201
202 Returns:
203 bool: True if it would exceed 10 pins, False otherwise.
204 """
205 # If no rolls yet, can't exceed
206 if len(self.rolls) == 0:
207 return False
208
209 # Determine if we're on the first roll of a frame or second roll
210 roll_index = len(self.rolls)
211
212 # For frames 1-9
213 if self.get_current_frame() < 9:
214 # If previous roll was a strike, we're starting a new frame
215 if roll_index > 0 and self.is_strike(roll_index - 1):
216 return False
217 # If even number of rolls, we're starting a new frame
218 elif roll_index % 2 == 0:
219 return False
220 # Otherwise we're on second roll, check if total exceeds 10
221 else:
222 return self.rolls[roll_index - 1] + pins > 10
223 else:
224 # For 10th frame, logic is more complex
225 return self.would_exceed_tenth_frame_pins(pins)
226
227 def would_exceed_tenth_frame_pins(self, pins):
228 """
229 Check if adding pins would exceed 10 pins in the 10th frame.
230
231 Args:
232 pins (int): Number of pins to be rolled.
233
234 Returns:
235 bool: True if it would exceed 10 pins, False otherwise.
236 """
237 roll_index = len(self.rolls)
238
239 # Count rolls in first 9 frames
240 frame_roll_count = 0
241 for frame in range(9):
242 if frame_roll_count < len(self.rolls):
243 if self.is_strike(frame_roll_count):
244 frame_roll_count += 1
245 elif frame_roll_count + 1 < len(self.rolls):
246 frame_roll_count += 2
247
248 # Rolls in 10th frame
249 tenth_frame_rolls = roll_index - frame_roll_count
250
251 # If starting 10th frame or starting a new roll after a strike/spare
252 if tenth_frame_rolls == 0 or (tenth_frame_rolls == 2 and
253 (self.rolls[frame_roll_count] == 10 or
254 self.rolls[frame_roll_count] + self.rolls[frame_roll_count + 1] == 10)):
255 return False
256 # If second roll in 10th frame (and first wasn't a strike)
257 elif tenth_frame_rolls == 1 and self.rolls[frame_roll_count] != 10:
258 return self.rolls[frame_roll_count] + pins > 10
259 # If third roll in 10th frame (after a spare or second strike)
260 elif tenth_frame_rolls == 2:
261 # If first two rolls were a strike and a non-strike
262 if (self.rolls[frame_roll_count] == 10 and
263 self.rolls[frame_roll_count + 1] != 10):
264 return self.rolls[frame_roll_count + 1] + pins > 10
265 # Otherwise it's a new roll, no limit
266 else:
267 return False
268 else:
269 return False
270
271 def get_current_frame(self):
272 """
273 Get the current frame number (0-9).
274
275 Returns:
276 int: Current frame number (0-indexed).
277 """
278 roll_index = len(self.rolls)
279 frame_count = 0
280 roll_count = 0
281
282 # Count complete frames
283 for frame in range(9): # First 9 frames
284 if roll_count < len(self.rolls):
285 if self.is_strike(roll_count):
286 roll_count += 1
287 elif roll_count + 1 < len(self.rolls):
288 roll_count += 2
289 frame_count += 1
290
291 # If we've completed 9 frames, we're in the 10th frame
292 if frame_count >= 9:
293 return 9
294
295 # Otherwise, we're in frame frame_count
296 return frame_count
297
298 # Handled Edge Cases: Invalid pins count, rolling after game completion, scoring before game completion,
299 # strike in regular frames, spare in regular frames, open frame in regular frames, strike in 10th frame,
300 # spare in 10th frame, open 10th frame, invalid fill balls for strike/spare in 10th frame,
301 # not enough rolls for strike/spare bonus, not enough rolls in frame
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.