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 or pins > 10:
21 raise ValueError("Pins must be between 0 and 10")
22
23 # Edge Case: Too many pins in a frame
24 if self.current_roll > 0 and self.rolls[-1] != 10: # Not a strike
25 if self.current_roll % 2 == 1 and self.rolls[-1] + pins > 10:
26 raise ValueError("Pins must be between 0 and 10")
27
28 # Edge Case: Game is already complete
29 if self.is_game_complete():
30 raise ValueError("Game is already over")
31
32 self.rolls.append(pins)
33 self.current_roll += 1
34
35 # Edge Case: Game became complete with this roll, check if additional rolls are invalid
36 if self.is_game_complete():
37 # Check if there are too many rolls
38 if len(self.rolls) > 21:
39 raise ValueError("Game is already over")
40 elif len(self.rolls) == 21:
41 # Special case for the 10th frame - check if the third roll is valid
42 # If the second roll was a strike, the third roll is valid
43 # If the first two rolls were a spare, the third roll is valid
44 # Otherwise, it's invalid
45 tenth_frame_rolls = self.rolls[18:]
46 if len(tenth_frame_rolls) == 3:
47 # If first roll was a strike
48 if tenth_frame_rolls[0] == 10:
49 # Second roll can be anything 0-10
50 # Third roll depends on second roll
51 if tenth_frame_rolls[1] == 10:
52 # Two strikes, third roll can be 0-10
53 if tenth_frame_rolls[2] < 0 or tenth_frame_rolls[2] > 10:
54 raise ValueError("Pins must be between 0 and 10")
55 else:
56 # Second roll wasn't a strike, so first two rolls sum to <= 10
57 if tenth_frame_rolls[1] + tenth_frame_rolls[2] > 10:
58 raise ValueError("invalid fill balls")
59 else: # First roll wasn't a strike
60 # Must be a spare (first two rolls sum to 10)
61 if tenth_frame_rolls[0] + tenth_frame_rolls[1] == 10:
62 # Third roll can be 0-10
63 if tenth_frame_rolls[2] < 0 or tenth_frame_rolls[2] > 10:
64 raise ValueError("Pins must be between 0 and 10")
65 else:
66 # This shouldn't happen if we got here correctly
67 raise ValueError("Invalid tenth frame")
68
869 def score(self):
9 pass
70 """
71 Calculate the total score for the game.
72
73 Returns:
74 int: The total score of the game.
75
76 Raises:
77 ValueError: If the game is not yet complete.
78 """
79 # Edge Case: Game is not complete
80 if not self.is_game_complete():
81 raise ValueError("Game is not yet over")
82
83 score = 0
84 roll_index = 0
85
86 for frame in range(10):
87 # Edge Case: Incomplete game (should not happen with the check above, but for safety)
88 if roll_index >= len(self.rolls):
89 raise ValueError("Incomplete game")
90
91 if self.is_strike(roll_index): # Strike
92 # Edge Case: Not enough rolls for strike bonus
93 if roll_index + 2 >= len(self.rolls):
94 raise ValueError("Incomplete game")
95
96 score += 10 + self.strike_bonus(roll_index)
97 roll_index += 1
98 elif self.is_spare(roll_index): # Spare
99 # Edge Case: Not enough rolls for spare bonus
100 if roll_index + 2 >= len(self.rolls):
101 raise ValueError("Incomplete game")
102
103 score += 10 + self.spare_bonus(roll_index)
104 roll_index += 2
105 else: # Open frame
106 score += self.sum_of_balls_in_frame(roll_index)
107 roll_index += 2
108
109 return score
110
111 def is_game_complete(self):
112 """
113 Check if the game is complete.
114
115 Returns:
116 bool: True if the game is complete, False otherwise.
117 """
118 # A game is complete when 10 frames have been played
119 # For frames 1-9, each frame takes 1 or 2 rolls
120 # For frame 10, it can take 2 or 3 rolls depending on strikes/spares
121
122 if len(self.rolls) < 12: # Minimum possible rolls (10 strikes + 2 fill balls)
123 # But we need to check if we actually have 10 complete frames
124 pass
125
126 # Count the number of complete frames
127 frame_count = 0
128 roll_index = 0
129
130 # Count frames 1-9
131 while frame_count < 9 and roll_index < len(self.rolls):
132 if self.rolls[roll_index] == 10: # Strike
133 frame_count += 1
134 roll_index += 1
135 else:
136 # Need two rolls for a non-strike frame
137 if roll_index + 1 < len(self.rolls):
138 # Check if the frame is valid
139 if self.rolls[roll_index] + self.rolls[roll_index + 1] <= 10:
140 frame_count += 1
141 roll_index += 2
142 else:
143 # Invalid frame, game can't be complete
144 return False
145 else:
146 # Incomplete frame
147 return False
148
149 # Handle 10th frame
150 if frame_count == 9 and roll_index < len(self.rolls):
151 # We have 9 complete frames, now check the 10th frame
152 remaining_rolls = self.rolls[roll_index:]
153
154 if len(remaining_rolls) >= 1:
155 if remaining_rolls[0] == 10: # First roll is a strike
156 # Need exactly 2 more rolls (fill balls)
157 return len(remaining_rolls) >= 3
158 elif len(remaining_rolls) >= 2:
159 if remaining_rolls[0] + remaining_rolls[1] == 10: # Spare
160 # Need exactly 1 more roll (fill ball)
161 return len(remaining_rolls) >= 3
162 elif remaining_rolls[0] + remaining_rolls[1] < 10: # Open frame
163 # No fill balls needed
164 return len(remaining_rolls) >= 2
165
166 return False
167
168 def is_strike(self, roll_index):
169 """
170 Check if the roll at roll_index is a strike.
171
172 Args:
173 roll_index (int): Index of the roll to check.
174
175 Returns:
176 bool: True if the roll is a strike, False otherwise.
177 """
178 return self.rolls[roll_index] == 10
179
180 def is_spare(self, roll_index):
181 """
182 Check if the frame starting at roll_index is a spare.
183
184 Args:
185 roll_index (int): Index of the first roll of the frame.
186
187 Returns:
188 bool: True if the frame is a spare, False otherwise.
189 """
190 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
191
192 def strike_bonus(self, roll_index):
193 """
194 Calculate the bonus for a strike.
195
196 Args:
197 roll_index (int): Index of the strike roll.
198
199 Returns:
200 int: The bonus points for the strike.
201 """
202 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
203
204 def spare_bonus(self, roll_index):
205 """
206 Calculate the bonus for a spare.
207
208 Args:
209 roll_index (int): Index of the first roll of the spare frame.
210
211 Returns:
212 int: The bonus points for the spare.
213 """
214 return self.rolls[roll_index + 2]
215
216 def sum_of_balls_in_frame(self, roll_index):
217 """
218 Calculate the sum of pins knocked down in a frame.
219
220 Args:
221 roll_index (int): Index of the first roll of the frame.
222
223 Returns:
224 int: The sum of pins knocked down in the frame.
225 """
226 return self.rolls[roll_index] + self.rolls[roll_index + 1]
227
228 # Handled Edge Cases: Invalid number of pins, too many pins in a frame, game already complete,
229 # invalid fill balls in 10th frame, game not complete when scoring,
230 # incomplete game, not enough rolls for 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.