seahawk

Finished
11class BowlingGame:
22 def __init__(self):
3 pass
4
3 """
4 Initialize a new bowling game.
5 """
6 self.rolls = []
7 self.current_frame = 1
8 self.current_roll_in_frame = 1
9
510 def roll(self, pins):
6 pass
7
11 """
12 Record a roll in the game.
13
14 Args:
15 pins (int): Number of pins knocked down in this roll
16
17 Raises:
18 ValueError: If pins is negative or greater than 10
19 IndexError: If trying to roll after the game is over
20 """
21 # Edge Case: Invalid number of pins (negative or more than 10)
22 if pins < 0:
23 raise ValueError("Pins cannot be negative")
24 if pins > 10:
25 raise ValueError("Cannot knock down more than 10 pins in a frame")
26
27 # Edge Case: Trying to roll after game is over
28 if self.is_game_over():
29 raise ValueError("Game has already ended")
30
31 # Edge Case: In a frame, cannot knock down more pins than remaining
32 if (len(self.rolls) >= 1 and
33 len(self.rolls) % 2 == 1 and
34 self.rolls[-1] != 10 and
35 self.rolls[-1] + pins > 10):
36 raise ValueError("Cannot knock down more than 10 pins in a frame")
37
38 # For the 10th frame, we need special handling for fill balls
39 # Use the same frame counting logic as is_game_over()
40 frame_count = 0
41 roll_index = 0
42
43 # Count the first 9 frames
44 while frame_count < 9 and roll_index < len(self.rolls):
45 if self.rolls[roll_index] == 10: # Strike
46 roll_index += 1 # One roll for the frame
47 else: # Open frame or spare
48 roll_index += 2 # Two rolls for the frame
49 frame_count += 1
50
51 # If we're in the 10th frame, handle special validation
52 if frame_count >= 9 and roll_index < len(self.rolls):
53 # 10th frame first roll
54 tenth_frame_first = self.rolls[roll_index]
55 roll_index += 1
56
57 # If it's a strike, we need to validate fill balls
58 if tenth_frame_first == 10:
59 # First fill ball (if exists)
60 if roll_index < len(self.rolls):
61 first_fill_ball = self.rolls[roll_index]
62 roll_index += 1
63
64 # Second fill ball (if exists)
65 if roll_index < len(self.rolls):
66 # We already have both fill balls, so this would be an extra roll
67 raise ValueError("Game has already ended")
68 else:
69 # We're about to add the second fill ball
70 # Validate that if first fill ball wasn't a strike, the sum doesn't exceed 10
71 if first_fill_ball != 10 and first_fill_ball + pins > 10:
72 raise ValueError("Cannot knock down more than 10 pins in a frame")
73 else:
74 # Not a strike, need second roll of 10th frame
75 if roll_index < len(self.rolls):
76 # We already have the second roll, check if it's a spare
77 tenth_frame_second = self.rolls[roll_index]
78 roll_index += 1
79
80 # If it's a spare, we need one fill ball
81 if tenth_frame_first + tenth_frame_second == 10:
82 if roll_index < len(self.rolls):
83 # We already have the fill ball
84 raise ValueError("Game has already ended")
85 else:
86 # We're about to add the fill ball
87 pass # No special validation needed for fill ball after spare
88 else:
89 # Open frame, game should be over
90 raise ValueError("Game has already ended")
91 else:
92 # We're about to add the second roll of 10th frame
93 # Validate that it doesn't exceed 10 pins
94 if tenth_frame_first + pins > 10:
95 raise ValueError("Cannot knock down more than 10 pins in a frame")
96
97 self.rolls.append(pins)
98
899 def score(self):
9 pass
100 """
101 Calculate the total score for the game.
102
103 Returns:
104 int: The total score of the game
105
106 Raises:
107 ValueError: If the game is not yet complete
108 """
109 # Edge Case: Game is not complete yet
110 if not self.is_game_over():
111 raise ValueError("Game is not yet complete")
112
113 score = 0
114 roll_index = 0
115
116 for frame in range(10):
117 # Edge Case: Incomplete game (should not happen with the check above, but for safety)
118 if roll_index >= len(self.rolls):
119 raise IndexError("incomplete game")
120
121 if self.is_strike(roll_index): # Strike
122 # Edge Case: Not enough rolls to calculate strike bonus
123 if roll_index + 2 >= len(self.rolls):
124 raise IndexError("incomplete game")
125 score += 10 + self.strike_bonus(roll_index)
126 roll_index += 1
127 elif self.is_spare(roll_index): # Spare
128 # Edge Case: Not enough rolls to calculate spare bonus
129 if roll_index + 2 >= len(self.rolls):
130 raise IndexError("incomplete game")
131 score += 10 + self.spare_bonus(roll_index)
132 roll_index += 2
133 else: # Open frame
134 score += self.sum_of_balls_in_frame(roll_index)
135 roll_index += 2
136
137 return score
138
139 def is_strike(self, roll_index):
140 """
141 Check if the roll at roll_index is a strike.
142
143 Args:
144 roll_index (int): Index of the roll to check
145
146 Returns:
147 bool: True if it's a strike, False otherwise
148 """
149 return self.rolls[roll_index] == 10
150
151 def is_spare(self, roll_index):
152 """
153 Check if the frame starting at roll_index is a spare.
154
155 Args:
156 roll_index (int): Index of the first roll of the frame
157
158 Returns:
159 bool: True if it's a spare, False otherwise
160 """
161 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
162
163 def sum_of_balls_in_frame(self, roll_index):
164 """
165 Calculate the sum of pins knocked down in a frame.
166
167 Args:
168 roll_index (int): Index of the first roll of the frame
169
170 Returns:
171 int: Sum of pins in the frame
172 """
173 return self.rolls[roll_index] + self.rolls[roll_index + 1]
174
175 def spare_bonus(self, roll_index):
176 """
177 Calculate the bonus for a spare.
178
179 Args:
180 roll_index (int): Index of the first roll of the spare frame
181
182 Returns:
183 int: The bonus points for the spare
184 """
185 return self.rolls[roll_index + 2]
186
187 def strike_bonus(self, roll_index):
188 """
189 Calculate the bonus for a strike.
190
191 Args:
192 roll_index (int): Index of the strike roll
193
194 Returns:
195 int: The bonus points for the strike
196 """
197 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
198
199 def is_game_over(self):
200 """
201 Check if the game is over.
202
203 Returns:
204 bool: True if the game is over, False otherwise
205 """
206 # Count frames properly, accounting for strikes
207 frame_count = 0
208 roll_index = 0
209
210 # Count the first 9 frames
211 while frame_count < 9 and roll_index < len(self.rolls):
212 if self.rolls[roll_index] == 10: # Strike
213 roll_index += 1 # One roll for the frame
214 else: # Open frame or spare
215 roll_index += 2 # Two rolls for the frame
216 frame_count += 1
217
218 # If we haven't completed 9 frames, game is not over
219 if frame_count < 9:
220 return False
221
222 # If we're still in the process of completing the 9th frame
223 if frame_count == 9 and roll_index >= len(self.rolls):
224 return False
225
226 # Now check the 10th frame
227 if roll_index >= len(self.rolls):
228 return False
229
230 # 10th frame first roll
231 tenth_frame_first = self.rolls[roll_index]
232 roll_index += 1
233
234 # If it's a strike, we need two more rolls (fill balls)
235 if tenth_frame_first == 10:
236 # Need two more rolls
237 if roll_index + 1 >= len(self.rolls):
238 return False
239 else:
240 return True # We have all rolls needed for 10th frame
241
242 # If not a strike, need a second roll
243 if roll_index >= len(self.rolls):
244 return False
245
246 # 10th frame second roll
247 tenth_frame_second = self.rolls[roll_index]
248 roll_index += 1
249
250 # If it's a spare, we need one more roll (fill ball)
251 if tenth_frame_first + tenth_frame_second == 10:
252 if roll_index >= len(self.rolls):
253 return False
254 else:
255 return True # We have all rolls needed
256
257 # Otherwise it's an open frame, no more rolls needed
258 return True
259
260 # Handled Edge Cases: Invalid pins count, rolling after game over, invalid fill balls, incomplete game when scoring
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.