sn62

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 pin count (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 is already over
24 if self.is_game_over():
25 raise IndexError("Game is already complete")
26
27 # Edge Case: Validate frame pin total for non-tenth frames
28 if len(self.rolls) >= 1 and not self.is_tenth_frame():
29 # If this is the second roll of a frame (and the first roll wasn't a strike)
30 if len(self.rolls) % 2 == 1 and self.rolls[-1] != 10:
31 if self.rolls[-1] + pins > 10:
32 raise ValueError("Cannot knock down more than 10 pins in a frame")
33
34 # Edge Case: Validate tenth frame
35 if self.is_tenth_frame():
36 # Get the starting index of the tenth frame
37 tenth_frame_start = self.get_tenth_frame_start_index()
38
39 # If we're in the fill balls (after the first two rolls of 10th frame)
40 if len(self.rolls) >= tenth_frame_start + 2:
41 # Check what kind of 10th frame we had
42 first_roll = self.rolls[tenth_frame_start]
43 second_roll = self.rolls[tenth_frame_start + 1] if tenth_frame_start + 1 < len(self.rolls) else 0
44
45 # If first roll was a strike
46 if first_roll == 10:
47 # First fill ball (second roll of 10th frame)
48 if len(self.rolls) == tenth_frame_start + 1:
49 if pins > 10:
50 raise ValueError("Pins must be between 0 and 10")
51 # Second fill ball (third roll of 10th frame)
52 elif len(self.rolls) == tenth_frame_start + 2: # Before appending, this will be the second fill ball
53 # If second roll was also a strike, third can be 0-10
54 # If second roll plus third would exceed 10 (and second wasn't a strike)
55 if second_roll != 10 and second_roll + pins > 10:
56 raise ValueError("Cannot knock down more than 10 pins in a frame")
57 # If it was a spare (first two rolls sum to 10 but first isn't a strike)
58 elif first_roll + second_roll == 10:
59 # Fill ball can be 0-10
60 if len(self.rolls) == tenth_frame_start + 2 and pins > 10:
61 raise ValueError("Pins must be between 0 and 10")
62 # If it was an open frame
63 elif len(self.rolls) == tenth_frame_start + 2:
64 # Should not have a fill ball
65 raise ValueError("game is already over")
66
67 self.rolls.append(pins)
68
869 def score(self):
9 pass
70 """
71 Calculate the total score for the game.
72
73 Returns:
74 int: The total score for the game.
75
76 Raises:
77 ValueError: If the game is not yet complete.
78 """
79 # Edge Case: Game is not complete
80 if not self.is_game_over():
81 raise IndexError("Game is not yet complete")
82
83 score = 0
84 roll_index = 0
85
86 for frame in range(10):
87 # Edge Case: Strike in regular frames (1-9)
88 if self.is_strike(roll_index) and frame < 9:
89 score += 10 + self.strike_bonus(roll_index)
90 roll_index += 1
91 # Edge Case: Spare in regular frames (1-9)
92 elif self.is_spare(roll_index) and frame < 9:
93 score += 10 + self.spare_bonus(roll_index)
94 roll_index += 2
95 # Edge Case: Open frame in regular frames (1-9)
96 elif frame < 9:
97 score += self.sum_of_balls_in_frame(roll_index)
98 roll_index += 2
99 # Edge Case: Tenth frame handling
100 else: # 10th frame
101 # Edge Case: Strike in tenth frame
102 if self.is_strike(roll_index):
103 # Edge Case: Second strike in tenth frame
104 if self.is_strike(roll_index + 1):
105 # Edge Case: Third strike in tenth frame
106 score += 10 + 10 + self.rolls[roll_index + 2]
107 else:
108 score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
109 # Edge Case: Spare in tenth frame
110 elif self.is_spare(roll_index):
111 score += 10 + self.rolls[roll_index + 2]
112 # Edge Case: Open tenth frame
113 else:
114 score += self.sum_of_balls_in_frame(roll_index)
115 break
116
117 return score
118
119 def is_game_over(self):
120 """
121 Check if the game is over.
122
123 Returns:
124 bool: True if the game is over, False otherwise.
125 """
126 # Count frames
127 roll_index = 0
128 frames_completed = 0
129
130 # Process first 9 frames
131 while frames_completed < 9 and roll_index < len(self.rolls):
132 if self.is_strike(roll_index):
133 roll_index += 1
134 else:
135 roll_index += 2
136 frames_completed += 1
137
138 # Process 10th frame
139 if frames_completed == 9:
140 # Check if we have enough rolls to determine if 10th frame is complete
141 if roll_index < len(self.rolls):
142 # If first roll of 10th frame is a strike
143 if self.rolls[roll_index] == 10:
144 # Need two more rolls
145 return len(self.rolls) >= roll_index + 3
146 # If we have two rolls in 10th frame
147 elif roll_index + 1 < len(self.rolls):
148 # If it's a spare
149 if self.rolls[roll_index] + self.rolls[roll_index + 1] == 10:
150 # Need one more roll
151 return len(self.rolls) >= roll_index + 3
152 else:
153 # Open frame
154 return len(self.rolls) >= roll_index + 2
155 else:
156 # Only first roll of 10th frame exists
157 return False
158 else:
159 # No rolls in 10th frame yet
160 return False
161
162 # If we haven't completed 9 frames, game is not over
163 return False
164
165 def is_strike(self, roll_index):
166 """
167 Check if a roll is a strike.
168
169 Args:
170 roll_index (int): Index of the roll to check.
171
172 Returns:
173 bool: True if the roll is a strike, False otherwise.
174 """
175 # Edge Case: Index out of bounds
176 if roll_index >= len(self.rolls):
177 return False
178 return self.rolls[roll_index] == 10
179
180 def is_spare(self, roll_index):
181 """
182 Check if a frame is a spare.
183
184 Args:
185 roll_index (int): Index of the first roll in the frame.
186
187 Returns:
188 bool: True if the frame is a spare, False otherwise.
189 """
190 # Edge Case: Index out of bounds
191 if roll_index + 1 >= len(self.rolls):
192 return False
193 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
194
195 def strike_bonus(self, roll_index):
196 """
197 Calculate the bonus for a strike.
198
199 Args:
200 roll_index (int): Index of the strike roll.
201
202 Returns:
203 int: The bonus points for the strike.
204 """
205 # Edge Case: Not enough rolls for bonus calculation
206 if roll_index + 2 >= len(self.rolls):
207 return 0
208 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
209
210 def spare_bonus(self, roll_index):
211 """
212 Calculate the bonus for a spare.
213
214 Args:
215 roll_index (int): Index of the first roll in the spare frame.
216
217 Returns:
218 int: The bonus points for the spare.
219 """
220 # Edge Case: Not enough rolls for bonus calculation
221 if roll_index + 2 >= len(self.rolls):
222 return 0
223 return self.rolls[roll_index + 2]
224
225 def sum_of_balls_in_frame(self, roll_index):
226 """
227 Calculate the sum of pins knocked down in a frame.
228
229 Args:
230 roll_index (int): Index of the first roll in the frame.
231
232 Returns:
233 int: The sum of pins knocked down in the frame.
234 """
235 # Edge Case: Not enough rolls in frame
236 if roll_index + 1 >= len(self.rolls):
237 return 0
238 return self.rolls[roll_index] + self.rolls[roll_index + 1]
239
240 def is_tenth_frame(self):
241 """
242 Check if we are currently in the tenth frame.
243
244 Returns:
245 bool: True if we are in the tenth frame, False otherwise.
246 """
247 # Count completed frames
248 roll_index = 0
249 frames_completed = 0
250
251 while roll_index < len(self.rolls) and frames_completed < 9:
252 if self.is_strike(roll_index):
253 roll_index += 1
254 else:
255 roll_index += 2
256 frames_completed += 1
257
258 # If we've completed 9 frames, we're in the 10th frame
259 return frames_completed == 9
260
261 def get_tenth_frame_start_index(self):
262 """
263 Get the starting index of the tenth frame in the rolls array.
264
265 Returns:
266 int: The index where the tenth frame starts.
267 """
268 # Count frames to find where the 10th frame starts
269 roll_index = 0
270 frames_completed = 0
271
272 while frames_completed < 9:
273 if roll_index < len(self.rolls) and self.is_strike(roll_index):
274 roll_index += 1
275 elif roll_index + 1 < len(self.rolls):
276 roll_index += 2
277 else:
278 break
279 frames_completed += 1
280
281 return roll_index
282
283 # Handled Edge Cases: Invalid pin count, game already over, game not complete, strike in regular frames,
284 # spare in regular frames, open frame in regular frames, strike in tenth frame, second strike in tenth frame,
285 # third strike in tenth frame, spare in tenth frame, open tenth frame, index out of bounds,
286 # not enough rolls for bonus calculation, not enough rolls in 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.