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("invalid pin count")
22
23 # Edge Case: Too many pins in a frame (except 10th frame special cases)
24 if len(self.rolls) > 0 and not self.is_game_complete():
25 # Check if this is the second roll of a frame (not 10th frame)
26 roll_index = 0
27 frame_count = 0
28
29 # Find which frame we are currently in
30 while roll_index < len(self.rolls) and frame_count < 10:
31 if frame_count == 9: # 10th frame - special rules apply
32 break
33 elif self.is_strike(roll_index):
34 roll_index += 1
35 frame_count += 1
36 elif roll_index + 1 < len(self.rolls):
37 roll_index += 2
38 frame_count += 1
39 else:
40 # We are in the middle of a frame (second roll not yet taken)
41 if frame_count < 9 and self.rolls[roll_index] + pins > 10:
42 raise ValueError("invalid pin count")
43 break
44
45 # Edge Case: Game already completed
46 if self.is_game_complete():
47 raise IndexError("cannot throw bonus with an open tenth frame")
48
49 self.rolls.append(pins)
50 self.current_roll += 1
51
852 def score(self):
9 pass
53 """
54 Calculate the total score for the game.
55
56 Returns:
57 int: The total score of the game.
58
59 Raises:
60 ValueError: If the game is not yet complete.
61 """
62 # Edge Case: Game not complete
63 if not self.is_game_complete():
64 raise IndexError("game is not yet complete")
65
66 score = 0
67 roll_index = 0
68
69 for frame in range(10):
70 # Edge Case: Strike in regular frames (1-9)
71 if self.is_strike(roll_index) and frame < 9:
72 score += 10 + self.strike_bonus(roll_index)
73 roll_index += 1
74 # Edge Case: Spare in regular frames (1-9)
75 elif self.is_spare(roll_index) and frame < 9:
76 score += 10 + self.spare_bonus(roll_index)
77 roll_index += 2
78 # Edge Case: Open frame in regular frames (1-9)
79 elif frame < 9:
80 score += self.sum_of_balls_in_frame(roll_index)
81 roll_index += 2
82 # Edge Case: Tenth frame handling
83 else: # 10th frame
84 score += self.sum_of_balls_in_frame(roll_index)
85 roll_index += 2
86
87 # Edge Case: Strike in tenth frame
88 if self.is_strike(roll_index - 2):
89 # Add bonus rolls for strike in 10th frame
90 if roll_index < len(self.rolls):
91 score += self.rolls[roll_index]
92 roll_index += 1
93 if roll_index < len(self.rolls):
94 score += self.rolls[roll_index]
95 # Edge Case: Spare in tenth frame
96 elif self.is_spare(roll_index - 2):
97 # Add bonus roll for spare in 10th frame
98 if roll_index < len(self.rolls):
99 score += self.rolls[roll_index]
100
101 return score
102
103 def is_game_complete(self):
104 """
105 Check if the game is complete (10 frames played).
106
107 Returns:
108 bool: True if the game is complete, False otherwise.
109 """
110 # Count the number of completed frames
111 roll_index = 0
112 frames_completed = 0
113
114 # Process frames 1-9
115 while frames_completed < 9 and roll_index < len(self.rolls):
116 if self.is_strike(roll_index):
117 roll_index += 1
118 elif roll_index + 1 < len(self.rolls):
119 roll_index += 2
120 else:
121 # Incomplete frame
122 return False
123 frames_completed += 1
124
125 # Process 10th frame
126 if frames_completed == 9 and roll_index < len(self.rolls):
127 # 10th frame is special - can have 2 or 3 rolls
128 if self.is_strike(roll_index):
129 # Strike in 10th frame - need 2 more rolls
130 if roll_index + 2 < len(self.rolls):
131 roll_index += 3
132 frames_completed += 1
133 else:
134 # Not enough rolls for complete 10th frame
135 return False
136 elif roll_index + 1 < len(self.rolls) and self.is_spare(roll_index):
137 # Spare in 10th frame - need 1 more roll
138 if roll_index + 2 < len(self.rolls):
139 roll_index += 3
140 frames_completed += 1
141 else:
142 # Not enough rolls for complete 10th frame
143 return False
144 elif roll_index + 1 < len(self.rolls):
145 # Open frame in 10th frame - 2 rolls
146 roll_index += 2
147 frames_completed += 1
148 else:
149 # Not enough rolls for complete 10th frame
150 return False
151
152 # Game is complete if we've completed 10 frames
153 return frames_completed == 10
154
155 def is_strike(self, roll_index):
156 """
157 Check if a roll is a strike.
158
159 Args:
160 roll_index (int): Index of the roll to check.
161
162 Returns:
163 bool: True if the roll is a strike, False otherwise.
164 """
165 # Edge Case: Index out of bounds
166 if roll_index >= len(self.rolls):
167 return False
168 return self.rolls[roll_index] == 10
169
170 def is_spare(self, roll_index):
171 """
172 Check if two rolls form a spare.
173
174 Args:
175 roll_index (int): Index of the first roll to check.
176
177 Returns:
178 bool: True if the two rolls form a spare, False otherwise.
179 """
180 # Edge Case: Index out of bounds
181 if roll_index + 1 >= len(self.rolls):
182 return False
183 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
184
185 def strike_bonus(self, roll_index):
186 """
187 Calculate the bonus for a strike.
188
189 Args:
190 roll_index (int): Index of the strike roll.
191
192 Returns:
193 int: The bonus points for the strike.
194 """
195 # Edge Case: Not enough rolls for bonus calculation
196 if roll_index + 2 >= len(self.rolls):
197 return 0
198 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
199
200 def spare_bonus(self, roll_index):
201 """
202 Calculate the bonus for a spare.
203
204 Args:
205 roll_index (int): Index of the first roll in the spare frame.
206
207 Returns:
208 int: The bonus points for the spare.
209 """
210 # Edge Case: Not enough rolls for bonus calculation
211 if roll_index + 2 >= len(self.rolls):
212 return 0
213 return self.rolls[roll_index + 2]
214
215 def get_current_frame(self):
216 """
217 Get the current frame number (0-indexed).
218
219 Returns:
220 int: Current frame number (0-9).
221 """
222 roll_index = 0
223 frame_count = 0
224
225 while roll_index < len(self.rolls) and frame_count < 10:
226 if frame_count == 9: # 10th frame
227 return 9
228 elif self.is_strike(roll_index):
229 roll_index += 1
230 frame_count += 1
231 elif roll_index + 1 < len(self.rolls):
232 roll_index += 2
233 frame_count += 1
234 else:
235 break
236
237 return frame_count
238
239 def sum_of_balls_in_frame(self, roll_index):
240 """
241 Calculate the sum of pins knocked down in a frame.
242
243 Args:
244 roll_index (int): Index of the first roll in the frame.
245
246 Returns:
247 int: The sum of pins knocked down in the frame.
248 """
249 # Edge Case: Not enough rolls in frame
250 if roll_index >= len(self.rolls):
251 return 0
252 if roll_index + 1 >= len(self.rolls):
253 return self.rolls[roll_index]
254 return self.rolls[roll_index] + self.rolls[roll_index + 1]
255
256 # Handled Edge Cases: Invalid pin count, game already completed, game not complete,
257 # strike in regular frames, spare in regular frames, open frame in regular frames,
258 # tenth frame handling, strike in tenth frame, spare in tenth frame, open frame in tenth frame,
259 # index out of bounds, not enough rolls for bonus calculation, not enough rolls in frame
Test NameStatus
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_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_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.