xas

Finished
11class BowlingGame:
22 def __init__(self):
3 pass
3 """
4 Initialize a new bowling game.
5 """
6 self.rolls = []
47
58 def roll(self, pins):
6 pass
9 """
10 Record a roll in the game.
11
12 Args:
13 pins (int): Number of pins knocked down in this roll.
14
15 Raises:
16 ValueError: If the number of pins is invalid.
17 """
18 # Edge Case: Invalid number of pins (negative or more than 10)
19 if pins < 0:
20 raise ValueError("Pins must be between 0 and 10")
21 if 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("Game is already complete")
27
28 self.rolls.append(pins)
29
30 # Edge Case: Too many pins in a frame (except in the 10th frame)
31 if not self._is_tenth_frame() and len(self.rolls) >= 2 and len(self.rolls) % 2 == 0:
32 if self.rolls[-2] + self.rolls[-1] > 10 and self.rolls[-2] != 10:
33 raise ValueError("Cannot knock down more than 10 pins in a frame")
34
35 # Edge Case: Invalid fill balls in 10th frame
36 if self._is_tenth_frame():
37 self._validate_tenth_frame_fill_balls()
738
839 def score(self):
9 pass
40 """
41 Calculate the total score for the game.
42
43 Returns:
44 int: The total score of the game.
45
46 Raises:
47 ValueError: If the game is not yet complete.
48 """
49 # Edge Case: Game is not complete
50 if not self._is_game_over():
51 raise IndexError("Game is not yet complete")
52
53 score = 0
54 roll_index = 0
55
56 for frame in range(10):
57 # Edge Case: Strike in regular frames (1-9)
58 if self._is_strike(roll_index) and frame < 9:
59 score += 10 + self._strike_bonus(roll_index)
60 roll_index += 1
61 # Edge Case: Spare in regular frames (1-9)
62 elif self._is_spare(roll_index) and frame < 9:
63 score += 10 + self._spare_bonus(roll_index)
64 roll_index += 2
65 # Edge Case: Open frame in regular frames (1-9)
66 elif frame < 9:
67 score += self.rolls[roll_index] + self.rolls[roll_index + 1]
68 roll_index += 2
69 # Edge Case: Tenth frame handling
70 else: # 10th frame
71 if self._is_strike(roll_index):
72 # Edge Case: Tenth frame is a strike
73 score += self._tenth_frame_score(roll_index, 3)
74 elif self._is_spare(roll_index):
75 # Edge Case: Tenth frame is a spare
76 score += self._tenth_frame_score(roll_index, 3)
77 else:
78 # Edge Case: Tenth frame is open
79 score += self._tenth_frame_score(roll_index, 2)
80
81 return score
82
83 def _is_strike(self, roll_index):
84 """
85 Check if a roll is a strike.
86
87 Args:
88 roll_index (int): Index of the roll to check.
89
90 Returns:
91 bool: True if the roll is a strike, False otherwise.
92 """
93 if roll_index >= len(self.rolls):
94 return False
95 return self.rolls[roll_index] == 10
96
97 def _is_spare(self, roll_index):
98 """
99 Check if two rolls form a spare.
100
101 Args:
102 roll_index (int): Index of the first roll in the frame.
103
104 Returns:
105 bool: True if the frame is a spare, False otherwise.
106 """
107 if roll_index + 1 >= len(self.rolls):
108 return False
109 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
110
111 def _strike_bonus(self, roll_index):
112 """
113 Calculate the bonus for a strike.
114
115 Args:
116 roll_index (int): Index of the strike roll.
117
118 Returns:
119 int: The bonus points for the strike.
120 """
121 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
122
123 def _spare_bonus(self, roll_index):
124 """
125 Calculate the bonus for a spare.
126
127 Args:
128 roll_index (int): Index of the first roll in the spare frame.
129
130 Returns:
131 int: The bonus points for the spare.
132 """
133 return self.rolls[roll_index + 2]
134
135 def _is_game_over(self):
136 """
137 Check if the game is over (10 frames completed).
138
139 Returns:
140 bool: True if the game is over, False otherwise.
141 """
142 # Count frames
143 roll_index = 0
144 frame_count = 0
145
146 for frame in range(10):
147 if frame < 9: # Frames 1-9
148 # Check bounds before accessing rolls
149 if roll_index >= len(self.rolls):
150 return False
151 if self._is_strike(roll_index):
152 roll_index += 1
153 else:
154 # Check bounds for second roll
155 if roll_index + 1 >= len(self.rolls):
156 return False
157 roll_index += 2
158 frame_count += 1
159 else: # 10th frame
160 # Check bounds for first roll of 10th frame
161 if roll_index >= len(self.rolls):
162 return False
163 # Edge Case: 10th frame is a strike
164 if self._is_strike(roll_index):
165 # Needs two more rolls
166 if roll_index + 2 >= len(self.rolls):
167 return False
168 # Edge Case: 10th frame is a spare
169 elif (roll_index + 1 < len(self.rolls) and
170 self.rolls[roll_index] + self.rolls[roll_index + 1] == 10):
171 # Needs one more roll
172 if roll_index + 2 >= len(self.rolls):
173 return False
174 # Edge Case: 10th frame is open
175 elif roll_index + 1 >= len(self.rolls):
176 return False
177 frame_count += 1
178
179 return frame_count == 10
180
181 def _is_tenth_frame(self):
182 """
183 Check if we're currently in the tenth frame.
184
185 Returns:
186 bool: True if we're in the tenth frame, False otherwise.
187 """
188 # Count completed frames
189 roll_index = 0
190 frame_count = 0
191
192 while roll_index < len(self.rolls) and frame_count < 10:
193 if frame_count < 9: # Frames 1-9
194 if self._is_strike(roll_index):
195 roll_index += 1
196 else:
197 roll_index += 2
198 else: # 10th frame
199 # We're in the process of completing the 10th frame
200 return True
201 frame_count += 1
202
203 return False
204
205 def _tenth_frame_score(self, roll_index, num_rolls):
206 """
207 Calculate the score for the tenth frame.
208
209 Args:
210 roll_index (int): Index of the first roll in the tenth frame.
211 num_rolls (int): Number of rolls in the tenth frame.
212
213 Returns:
214 int: The score of the tenth frame.
215 """
216 frame_score = 0
217 for i in range(num_rolls):
218 if roll_index + i < len(self.rolls):
219 frame_score += self.rolls[roll_index + i]
220 return frame_score
221
222 def _validate_tenth_frame_fill_balls(self):
223 """
224 Validate that fill balls in the tenth frame are valid.
225
226 Raises:
227 ValueError: If fill balls are invalid.
228 """
229 # Count completed frames to determine if we're in fill balls
230 roll_index = 0
231 frame_count = 0
232
233 # Count frames 1-9
234 while frame_count < 9 and roll_index < len(self.rolls):
235 if self._is_strike(roll_index):
236 roll_index += 1
237 else:
238 roll_index += 2
239 frame_count += 1
240
241 # Now we're at the 10th frame
242 if frame_count == 9 and roll_index < len(self.rolls):
243 # Check if 10th frame is a strike
244 if self._is_strike(roll_index):
245 # First fill ball
246 if roll_index + 1 < len(self.rolls):
247 first_fill = self.rolls[roll_index + 1]
248 if first_fill < 0 or first_fill > 10:
249 raise ValueError("Pins must be between 0 and 10")
250
251 # Second fill ball - check if it exists and validate sum with first fill
252 if roll_index + 2 < len(self.rolls):
253 second_fill = self.rolls[roll_index + 2]
254 if second_fill < 0 or second_fill > 10:
255 raise ValueError("Pins must be between 0 and 10")
256 # If first fill ball is not a strike, the sum can't exceed 10
257 if first_fill != 10 and first_fill + second_fill > 10:
258 raise ValueError("invalid fill balls")
259
260 # Check if 10th frame is a spare (first two rolls sum to 10 but first isn't 10)
261 elif (roll_index + 1 < len(self.rolls) and
262 self.rolls[roll_index] + self.rolls[roll_index + 1] == 10):
263 # One fill ball
264 if roll_index + 2 < len(self.rolls):
265 fill_ball = self.rolls[roll_index + 2]
266 if fill_ball < 0 or fill_ball > 10:
267 raise ValueError("Pins must be between 0 and 10")
268
269 # Handled Edge Cases: Invalid pin count, game already complete, too many pins in a frame,
270 # game not complete, strike in regular frames, spare in regular frames,
271 # open frame in regular frames, tenth frame is a strike, tenth frame is a spare,
272 # tenth frame is open
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.