chris

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)
20 if pins < 0:
21 raise ValueError("invalid roll")
22
23 # Edge Case: Game already has 10 frames and no more rolls are allowed
24 if self.is_game_complete():
25 raise ValueError("Cannot roll after game is over")
26
27 # Edge Case: In tenth frame, trying to roll after a valid frame is completed
28 if len(self.rolls) >= 18 and self.tenth_frame_complete():
29 raise ValueError("Cannot roll after game is over")
30
31 # Edge Case: Validate pin counts for regular frames (1-9)
32 if len(self.rolls) < 18:
33 # Check if we're in the second roll of a frame (after a non-strike first roll)
34 if len(self.rolls) % 2 == 1 and self.rolls[-1] != 10:
35 # Previous roll was not a strike, so we need to check the sum
36 if self.rolls[-1] + pins > 10:
37 raise ValueError("invalid roll")
38
39 # Edge Case: In tenth frame, second roll exceeds remaining pins
40 if len(self.rolls) >= 18 and len(self.rolls) % 2 == 1 and self.rolls[-1] != 10:
41 if self.rolls[-1] + pins > 10:
42 raise ValueError("Pin count exceeds pins on the lane")
43
44 # Edge Case: Validate fill balls in tenth frame
45 if len(self.rolls) >= 18:
46 # If first ball of tenth frame was a strike
47 if len(self.rolls) >= 19 and self.rolls[18] == 10:
48 # First fill ball (roll 19)
49 if len(self.rolls) == 19:
50 if pins > 10:
51 raise ValueError("invalid fill balls")
52 # Second fill ball (roll 20)
53 elif len(self.rolls) == 20:
54 if self.rolls[19] == 10:
55 # After a strike, can have up to 10 pins
56 if pins > 10:
57 raise ValueError("invalid fill balls")
58 else:
59 # After a non-strike, can only have remaining pins
60 if self.rolls[19] + pins > 10:
61 raise ValueError("invalid fill balls")
62 # If tenth frame was a spare
63 elif len(self.rolls) >= 20 and len(self.rolls) >= 19 and self.rolls[18] + self.rolls[19] == 10:
64 # Fill ball after spare (roll 20)
65 if len(self.rolls) == 20:
66 if pins > 10:
67 raise ValueError("invalid fill balls")
68
69 # Edge Case: Invalid number of pins (more than 10) for non-fill balls
70 if pins > 10:
71 # For fill balls, we've already handled the validation above
72 # For non-fill balls, this is an error
73 if len(self.rolls) < 18 or (len(self.rolls) >= 18 and
74 not (len(self.rolls) == 19 or
75 (len(self.rolls) == 20 and self.rolls[18] == 10) or
76 (len(self.rolls) == 20 and self.rolls[18] + self.rolls[19] == 10))):
77 raise ValueError("Pin count exceeds pins on the lane")
78
79 self.rolls.append(pins)
80
881 def score(self):
9 pass
82 """
83 Calculate the total score for the game.
84
85 Returns:
86 int: The total score of the game.
87
88 Raises:
89 ValueError: If the game is not yet complete.
90 """
91 # Edge Case: Game is not complete yet
92 if not self.is_game_complete():
93 raise IndexError("game not complete")
94
95 total_score = 0
96 roll_index = 0
97
98 for frame in range(10):
99 # Edge Case: Strike in regular frames (1-9)
100 if self.is_strike(roll_index) and frame < 9:
101 total_score += 10 + self.strike_bonus(roll_index)
102 roll_index += 1
103 # Edge Case: Spare in regular frames (1-9)
104 elif self.is_spare(roll_index) and frame < 9:
105 total_score += 10 + self.spare_bonus(roll_index)
106 roll_index += 2
107 # Edge Case: Open frame in regular frames (1-9)
108 elif frame < 9:
109 total_score += self.sum_of_balls_in_frame(roll_index)
110 roll_index += 2
111 # Edge Case: Tenth frame scoring
112 else: # frame == 9
113 # Strike in tenth frame
114 if self.is_strike(roll_index):
115 # Edge Case: Tenth frame has only one strike recorded
116 if len(self.rolls) < roll_index + 3:
117 raise ValueError("Score cannot be taken until the end of the game")
118 total_score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
119 # Spare in tenth frame
120 elif self.is_spare(roll_index):
121 # Edge Case: Tenth frame has only one spare recorded
122 if len(self.rolls) < roll_index + 3:
123 raise ValueError("Score cannot be taken until the end of the game")
124 total_score += 10 + self.rolls[roll_index + 2]
125 # Open frame in tenth frame
126 else:
127 # Edge Case: Tenth frame is incomplete
128 if len(self.rolls) < roll_index + 2:
129 raise ValueError("Score cannot be taken until the end of the game")
130 total_score += self.sum_of_balls_in_frame(roll_index)
131
132 return total_score
133
134 def is_game_complete(self):
135 """
136 Check if the game is complete.
137
138 Returns:
139 bool: True if the game is complete, False otherwise.
140 """
141 # Count frames properly considering strikes
142 frame_count = 0
143 roll_index = 0
144
145 while roll_index < len(self.rolls) and frame_count < 9:
146 if self.rolls[roll_index] == 10: # Strike
147 frame_count += 1
148 roll_index += 1
149 else:
150 # Need two rolls for a non-strike frame
151 if roll_index + 1 < len(self.rolls):
152 frame_count += 1
153 roll_index += 2
154 else:
155 # Incomplete frame
156 break
157
158 # If we haven't completed 9 frames, game is not complete
159 if frame_count < 9:
160 return False
161
162 # Now handle the 10th frame
163 if roll_index >= len(self.rolls):
164 return False # Haven't started 10th frame yet
165
166 # Check 10th frame
167 if self.rolls[roll_index] == 10: # Strike in 10th frame
168 # Need 2 more rolls
169 return len(self.rolls) >= roll_index + 3
170 elif roll_index + 1 < len(self.rolls):
171 # Have at least 2 rolls in 10th frame
172 if self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare
173 # Need 1 more roll
174 return len(self.rolls) >= roll_index + 3
175 else: # Open frame
176 return len(self.rolls) >= roll_index + 2
177 else:
178 # Only 1 roll in 10th frame so far
179 return False
180
181 def tenth_frame_complete(self):
182 """
183 Check if the tenth frame is complete.
184
185 Returns:
186 bool: True if the tenth frame is complete, False otherwise.
187 """
188 # Edge Case: Not enough rolls to determine tenth frame status
189 if len(self.rolls) < 18:
190 return False
191
192 # If we have exactly 18 rolls, we haven't started the tenth frame
193 if len(self.rolls) == 18:
194 return False
195
196 # If first ball was a strike
197 if len(self.rolls) >= 19 and self.rolls[18] == 10:
198 # Edge Case: Strike in tenth frame but not enough rolls for fill balls
199 if len(self.rolls) < 21:
200 return False
201 # If second ball made a spare
202 elif len(self.rolls) >= 20 and self.rolls[18] + self.rolls[19] == 10:
203 # Edge Case: Spare in tenth frame but not enough rolls for fill ball
204 if len(self.rolls) < 21:
205 return False
206 # If tenth frame is open
207 elif len(self.rolls) >= 20:
208 return True
209
210 return False
211
212 def is_strike(self, roll_index):
213 """
214 Check if a roll is a strike.
215
216 Args:
217 roll_index (int): Index of the roll to check.
218
219 Returns:
220 bool: True if the roll is a strike, False otherwise.
221 """
222 # Edge Case: Roll index out of bounds
223 if roll_index >= len(self.rolls):
224 return False
225 return self.rolls[roll_index] == 10
226
227 def is_spare(self, roll_index):
228 """
229 Check if a frame is a spare.
230
231 Args:
232 roll_index (int): Index of the first roll in the frame.
233
234 Returns:
235 bool: True if the frame is a spare, False otherwise.
236 """
237 # Edge Case: Not enough rolls to form a frame
238 if roll_index + 1 >= len(self.rolls):
239 return False
240 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
241
242 def strike_bonus(self, roll_index):
243 """
244 Calculate the bonus for a strike.
245
246 Args:
247 roll_index (int): Index of the strike roll.
248
249 Returns:
250 int: The bonus points for the strike.
251 """
252 # Edge Case: Not enough rolls for calculating strike bonus
253 if roll_index + 2 >= len(self.rolls):
254 return 0
255 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
256
257 def spare_bonus(self, roll_index):
258 """
259 Calculate the bonus for a spare.
260
261 Args:
262 roll_index (int): Index of the first roll in the spare frame.
263
264 Returns:
265 int: The bonus points for the spare.
266 """
267 # Edge Case: Not enough rolls for calculating spare bonus
268 if roll_index + 2 >= len(self.rolls):
269 return 0
270 return self.rolls[roll_index + 2]
271
272 def sum_of_balls_in_frame(self, roll_index):
273 """
274 Calculate the sum of pins knocked down in a frame.
275
276 Args:
277 roll_index (int): Index of the first roll in the frame.
278
279 Returns:
280 int: The sum of pins knocked down in the frame.
281 """
282 # Edge Case: Not enough rolls to form a complete frame
283 if roll_index + 1 >= len(self.rolls):
284 return 0
285 return self.rolls[roll_index] + self.rolls[roll_index + 1]
286
287 # Handled Edge Cases: Invalid number of pins (negative), Invalid number of pins (more than 10), Game already has 10 frames and no more rolls are allowed, In tenth frame trying to roll after a valid frame is completed, In tenth frame second roll exceeds remaining pins, Game is not complete yet, Strike in regular frames, Spare in regular frames, Open frame in regular frames, Tenth frame scoring, Less than 10 frames played, Exactly 10 frames with no strikes or spares in tenth frame, Tenth frame had a strike or spare check if fill balls are complete, Not enough rolls to determine tenth frame status, Strike in tenth frame but not enough rolls for fill balls, Spare in tenth frame but not enough rolls for fill ball, Roll index out of bounds, Not enough rolls to form a frame, Not enough rolls for calculating strike bonus, Not enough rolls for calculating spare bonus, Not enough rolls to form a complete frame
Test NameStatus
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_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.