let it go

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: Check frame pin limits (not applicable to 10th frame)
24 # We need to check this before adding the roll
25 if len(self.rolls) >= 1 and not self.is_tenth_frame():
26 # Get the last roll
27 last_roll = self.rolls[-1]
28 # If the last roll was not a strike, check if adding this roll would exceed 10 pins
29 if last_roll != 10 and last_roll + pins > 10:
30 raise ValueError("Cannot knock down more than 10 pins in a frame")
31
32 # Add the roll
33 self.rolls.append(pins)
34
35 # Edge Case: Game was already complete before this roll
36 # We need to check this after adding the roll to see if we just completed the game
37 # or if we're trying to roll after completion
38 if len(self.rolls) > 1 and self.was_game_complete_before_last_roll():
39 # Remove the roll before raising exception
40 self.rolls.pop()
41 raise IndexError("Game is already complete")
42
843 def score(self):
9 pass
44 """
45 Calculate the total score for the game.
46
47 Returns:
48 int: The total score of the game.
49
50 Raises:
51 ValueError: If the game is not yet complete.
52 """
53 # Edge Case: Game is not complete yet
54 if not self.is_game_complete():
55 raise IndexError("Game is not yet complete")
56
57 total_score = 0
58 roll_index = 0
59
60 for frame in range(10):
61 # Edge Case: Strike in regular frames (1-9)
62 if self.is_strike(roll_index) and frame < 9:
63 total_score += 10 + self.strike_bonus(roll_index)
64 roll_index += 1
65 # Edge Case: Spare in regular frames (1-9)
66 elif self.is_spare(roll_index) and frame < 9:
67 total_score += 10 + self.spare_bonus(roll_index)
68 roll_index += 2
69 # Edge Case: Open frame in regular frames (1-9)
70 elif frame < 9:
71 total_score += self.sum_of_balls_in_frame(roll_index)
72 roll_index += 2
73 # Edge Case: Tenth frame scoring
74 else: # 10th frame
75 # In 10th frame, we need to handle all rolls differently
76 # The 10th frame score is simply the sum of all rolls in it
77 if self.is_strike(roll_index):
78 # Strike in 10th frame: use regular strike scoring (10 + next two rolls)
79 total_score += 10 + self.strike_bonus(roll_index)
80 roll_index += 1
81 elif self.is_spare(roll_index):
82 # Spare in 10th frame: count the two rolls and bonus roll
83 total_score += 10
84 roll_index += 2
85 if roll_index < len(self.rolls):
86 total_score += self.rolls[roll_index]
87 roll_index += 1
88 else:
89 # Open frame in 10th frame: just count the two rolls
90 total_score += self.sum_of_balls_in_frame(roll_index)
91 roll_index += 2
92
93 return total_score
94
95 def is_tenth_frame(self):
96 """
97 Check if the current roll (after adding) would be in the 10th frame.
98
99 Returns:
100 bool: True if the roll would be in the 10th frame, False otherwise.
101 """
102 # Calculate what frame the next roll would be in
103 roll_index = 0
104 frame_count = 0
105
106 # Count how many complete frames we have so far
107 while frame_count < 9 and roll_index < len(self.rolls):
108 if self.is_strike(roll_index):
109 frame_count += 1
110 roll_index += 1
111 else:
112 if roll_index + 1 < len(self.rolls):
113 frame_count += 1
114 roll_index += 2
115 else:
116 break
117
118 # If we have 9 complete frames, the next roll would be in the 10th frame
119 return frame_count >= 9
120
121 def was_game_complete_before_last_roll(self):
122 """
123 Check if the game was complete before the last roll was added.
124
125 Returns:
126 bool: True if the game was complete before the last roll, False otherwise.
127 """
128 if len(self.rolls) <= 1:
129 return False
130
131 # Temporarily remove the last roll to check if the game was complete before it
132 last_roll = self.rolls.pop()
133 was_complete = self.is_game_complete()
134 # Add the roll back
135 self.rolls.append(last_roll)
136
137 return was_complete
138
139 def is_game_complete(self):
140 """
141 Check if the game is complete (10 frames have been played).
142
143 Returns:
144 bool: True if the game is complete, False otherwise.
145 """
146 # Count frames by iterating through rolls
147 roll_index = 0
148 frame_count = 0
149
150 while frame_count < 10 and roll_index < len(self.rolls):
151 if frame_count < 9: # Frames 1-9
152 if self.is_strike(roll_index):
153 frame_count += 1
154 roll_index += 1
155 elif roll_index + 1 < len(self.rolls):
156 frame_count += 1
157 roll_index += 2
158 else:
159 # Incomplete frame
160 return False
161 else: # Frame 10
162 # For 10th frame, check completion based on what was rolled
163 tenth_frame_rolls = len(self.rolls) - roll_index
164
165 # Debug print
166 # print(f"DEBUG: 10th frame - roll_index={roll_index}, len(self.rolls)={len(self.rolls)}, tenth_frame_rolls={tenth_frame_rolls}")
167
168 if self.is_strike(roll_index):
169 # Strike in 10th frame needs 2 more rolls
170 # print(f"DEBUG: Strike in 10th frame, needs 3 rolls, has {tenth_frame_rolls}")
171 return tenth_frame_rolls >= 3
172 elif roll_index + 1 < len(self.rolls) and self.is_spare(roll_index):
173 # Spare in 10th frame needs 1 more roll
174 # print(f"DEBUG: Spare in 10th frame, needs 3 rolls, has {tenth_frame_rolls}")
175 return tenth_frame_rolls >= 3
176 elif roll_index + 1 < len(self.rolls):
177 # Open frame in 10th frame needs exactly 2 rolls
178 # print(f"DEBUG: Open frame in 10th frame, needs 2 rolls, has {tenth_frame_rolls}")
179 return tenth_frame_rolls >= 2
180 else:
181 # Only one roll so far
182 # print(f"DEBUG: Only one roll in 10th frame")
183 return False
184
185 return frame_count >= 10
186
187 def is_strike(self, roll_index):
188 """
189 Check if a roll is a strike.
190
191 Args:
192 roll_index (int): Index of the roll to check.
193
194 Returns:
195 bool: True if the roll is a strike, False otherwise.
196 """
197 # Edge Case: Index out of bounds
198 if roll_index >= len(self.rolls):
199 return False
200 return self.rolls[roll_index] == 10
201
202 def is_spare(self, roll_index):
203 """
204 Check if two rolls form a spare.
205
206 Args:
207 roll_index (int): Index of the first roll to check.
208
209 Returns:
210 bool: True if the two rolls form a spare, False otherwise.
211 """
212 # Edge Case: Index out of bounds
213 if roll_index + 1 >= len(self.rolls):
214 return False
215 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
216
217 def strike_bonus(self, roll_index):
218 """
219 Calculate the bonus for a strike.
220
221 Args:
222 roll_index (int): Index of the strike roll.
223
224 Returns:
225 int: The bonus points for the strike.
226 """
227 # Edge Case: Not enough rolls for bonus calculation
228 if roll_index + 1 >= len(self.rolls):
229 return 0
230 if roll_index + 2 >= len(self.rolls):
231 return self.rolls[roll_index + 1]
232 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
233
234 def spare_bonus(self, roll_index):
235 """
236 Calculate the bonus for a spare.
237
238 Args:
239 roll_index (int): Index of the first roll in the spare frame.
240
241 Returns:
242 int: The bonus points for the spare.
243 """
244 # Edge Case: Not enough rolls for bonus calculation
245 if roll_index + 2 >= len(self.rolls):
246 return 0
247 return self.rolls[roll_index + 2]
248
249 def sum_of_balls_in_frame(self, roll_index):
250 """
251 Calculate the sum of pins knocked down in a frame.
252
253 Args:
254 roll_index (int): Index of the first roll in the frame.
255
256 Returns:
257 int: The sum of pins knocked down in the frame.
258 """
259 # Edge Case: Not enough rolls in frame
260 if roll_index >= len(self.rolls):
261 return 0
262 if roll_index + 1 >= len(self.rolls):
263 return self.rolls[roll_index]
264 return self.rolls[roll_index] + self.rolls[roll_index + 1]
265
266 # Handled Edge Cases: Invalid pin count, game already complete, game not complete for scoring,
267 # strike in regular frames, spare in regular frames, open frame in regular frames,
268 # tenth frame scoring, strike in 10th frame, spare in 10th frame, open frame in 10th frame,
269 # index out of bounds, not enough rolls for bonus calculation, not enough rolls in frame
Test NameStatus
test_consecutive_spares_each_get_a_one_roll_bonus
Fail
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_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.