future

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 number of pins (negative or more than 10)
20 if pins < 0:
21 raise ValueError("Pins must be between 0 and 10")
22 if pins > 10:
23 raise ValueError("Pins must be between 0 and 10")
24
25 # Edge Case: Game is already complete
26 if self.is_game_complete():
27 raise IndexError("Game finished")
28
29 # Edge Case: Adding pins that would exceed 10 in a frame
30 if len(self.rolls) > 0:
31 # Calculate which frame we're in and which roll within that frame
32 frame_index = self.get_frame_index(len(self.rolls))
33
34 # For frames 1-9
35 if frame_index < 9:
36 # If this is the second roll of a frame
37 if len(self.rolls) % 2 == 1 and self.rolls[-1] != 10: # Not after a strike
38 if self.rolls[-1] + pins > 10:
39 raise ValueError("Cannot knock down more pins than remaining")
40 # For frame 10
41 elif frame_index == 9:
42 # Check which roll we're on in frame 10
43 rolls_in_frame_10 = self.get_rolls_in_frame_10()
44
45 if rolls_in_frame_10 == 1: # Second roll of frame 10
46 # If first roll was not a strike
47 if self.rolls[-1] != 10:
48 if self.rolls[-1] + pins > 10:
49 raise ValueError("Cannot knock down more pins than remaining")
50 elif rolls_in_frame_10 == 2: # Third roll of frame 10
51 # If we have a strike in frame 10 (first roll of frame 10)
52 frame_10_start_index = len(self.rolls) - rolls_in_frame_10
53 if self.rolls[frame_10_start_index] == 10:
54 # If second roll was not a strike, third roll combined with second cannot exceed 10
55 if self.rolls[-1] != 10 and self.rolls[-1] + pins > 10:
56 raise ValueError("Cannot knock down more pins than remaining")
57 # If second roll was not a strike, third roll cannot be a strike
58 if self.rolls[-1] != 10 and pins == 10:
59 raise ValueError("Cannot knock down more pins than remaining")
60
61 self.rolls.append(pins)
62 self.current_roll += 1
63
64 def get_frame_index(self, roll_count):
65 """
66 Calculate which frame we're in based on the number of rolls.
67
68 Args:
69 roll_count (int): Number of rolls so far.
70
71 Returns:
72 int: Frame index (0-9).
73 """
74 frame_count = 0
75 roll_index = 0
76
77 # Process frames 1-9
78 while frame_count < 9 and roll_index < roll_count:
79 if self.rolls[roll_index] == 10: # Strike
80 roll_index += 1
81 else:
82 if roll_index + 1 < roll_count:
83 roll_index += 2
84 else:
85 roll_index += 1
86 break
87 frame_count += 1
88
89 return frame_count
90
91 def get_rolls_in_frame_10(self):
92 """
93 Calculate how many rolls we've had in frame 10.
94
95 Returns:
96 int: Number of rolls in frame 10 (0, 1, or 2).
97 """
98 # Find the start of frame 10
99 frame_count = 0
100 roll_index = 0
101
102 # Process frames 1-9
103 while frame_count < 9 and roll_index < len(self.rolls):
104 if self.rolls[roll_index] == 10: # Strike
105 roll_index += 1
106 else:
107 roll_index += 2
108 frame_count += 1
109
110 # Return how many rolls we've had in frame 10
111 return len(self.rolls) - roll_index
112
8113 def score(self):
9 pass
114 """
115 Calculate the total score for the game.
116
117 Returns:
118 int: The total score of the game.
119
120 Raises:
121 ValueError: If the game is not yet complete.
122 """
123 # Edge Case: Game is not complete
124 if not self.is_game_complete():
125 raise IndexError("Game not finished")
126
127 total_score = 0
128 roll_index = 0
129
130 for frame in range(10):
131 # Edge Case: Incomplete game (should not happen due to check above, but for safety)
132 if roll_index >= len(self.rolls):
133 raise ValueError("game not complete")
134
135 if self.is_strike(roll_index): # Strike
136 # Edge Case: Not enough rolls to calculate strike bonus
137 if roll_index + 2 >= len(self.rolls):
138 raise ValueError("game not complete")
139
140 total_score += 10 + self.strike_bonus(roll_index)
141 roll_index += 1
142 elif self.is_spare(roll_index): # Spare
143 # Edge Case: Not enough rolls to calculate spare bonus
144 if roll_index + 2 >= len(self.rolls):
145 raise ValueError("game not complete")
146
147 total_score += 10 + self.spare_bonus(roll_index)
148 roll_index += 2
149 else: # Open frame
150 total_score += self.sum_of_balls_in_frame(roll_index)
151 roll_index += 2
152
153 return total_score
154
155 def is_game_complete(self):
156 """
157 Check if the game is complete.
158
159 Returns:
160 bool: True if the game is complete, False otherwise.
161 """
162 # Count complete frames
163 frame_count = 0
164 roll_index = 0
165
166 # Process first 9 frames
167 while frame_count < 9 and roll_index < len(self.rolls):
168 if self.rolls[roll_index] == 10: # Strike
169 roll_index += 1
170 else:
171 # Need two rolls for non-strike
172 if roll_index + 1 >= len(self.rolls):
173 return False
174 roll_index += 2
175 frame_count += 1
176
177 # Process 10th frame
178 if frame_count < 9:
179 return False
180
181 if roll_index >= len(self.rolls):
182 return False
183
184 # 10th frame
185 if self.rolls[roll_index] == 10: # Strike in 10th frame
186 # Need 2 bonus rolls
187 if roll_index + 2 >= len(self.rolls):
188 return False
189 roll_index += 3
190 else:
191 # Need second roll for 10th frame
192 if roll_index + 1 >= len(self.rolls):
193 return False
194 if self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare in 10th frame
195 # Need 1 bonus roll
196 if roll_index + 2 >= len(self.rolls):
197 return False
198 roll_index += 3
199 else: # Open frame in 10th frame
200 roll_index += 2
201
202 return roll_index == len(self.rolls)
203
204 def is_strike(self, roll_index):
205 """
206 Check if the roll at roll_index is a strike.
207
208 Args:
209 roll_index (int): Index of the roll to check.
210
211 Returns:
212 bool: True if the roll is a strike, False otherwise.
213 """
214 return self.rolls[roll_index] == 10
215
216 def is_spare(self, roll_index):
217 """
218 Check if the frame starting at roll_index is a spare.
219
220 Args:
221 roll_index (int): Index of the first roll of the frame.
222
223 Returns:
224 bool: True if the frame is a spare, False otherwise.
225 """
226 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
227
228 def strike_bonus(self, roll_index):
229 """
230 Calculate the bonus for a strike.
231
232 Args:
233 roll_index (int): Index of the strike roll.
234
235 Returns:
236 int: The bonus points for the strike.
237 """
238 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
239
240 def spare_bonus(self, roll_index):
241 """
242 Calculate the bonus for a spare.
243
244 Args:
245 roll_index (int): Index of the first roll of the spare frame.
246
247 Returns:
248 int: The bonus points for the spare.
249 """
250 return self.rolls[roll_index + 2]
251
252 def sum_of_balls_in_frame(self, roll_index):
253 """
254 Calculate the sum of pins knocked down in a frame.
255
256 Args:
257 roll_index (int): Index of the first roll of the frame.
258
259 Returns:
260 int: The sum of pins knocked down in the frame.
261 """
262 return self.rolls[roll_index] + self.rolls[roll_index + 1]
263
264 # Handled Edge Cases: Invalid number of pins, game already complete, exceeding 10 pins in a frame,
265 # game not complete when scoring, incomplete game when scoring, not enough rolls
266 # to calculate strike/spare bonus
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.