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: Game already completed
24 if self.is_game_complete():
25 raise ValueError("Game has already ended")
26
27 self.rolls.append(pins)
28
29 # Edge Case: Validate frame score doesn't exceed 10 (except for strikes)
30 if len(self.rolls) >= 2:
31 # Check if this is the second roll of a frame (not a strike)
32 roll_index = len(self.rolls) - 2
33 if not self.is_strike(roll_index):
34 # Check if we're in frames 1-9 or frame 10
35 frame_count = 0
36 temp_index = 0
37 while temp_index < roll_index and frame_count < 10:
38 if self.is_strike(temp_index):
39 temp_index += 1
40 else:
41 temp_index += 2
42 frame_count += 1
43
44 # Only validate frame score for frames 1-9, not frame 10
45 if frame_count < 9 and self.rolls[-2] + self.rolls[-1] > 10:
46 raise ValueError("Cannot knock down more than 10 pins in a frame")
47
848 def score(self):
9 pass
49 """
50 Calculate the total score for the game.
51
52 Returns:
53 int: The total score of the game.
54
55 Raises:
56 ValueError: If the game is not yet complete.
57 """
58 # Edge Case: Game not yet complete
59 if not self.is_game_complete():
60 raise ValueError("Game is not yet complete")
61
62 total_score = 0
63 roll_index = 0
64
65 # Score frames 1-9
66 for frame in range(9):
67 # Edge Case: Strike in regular frames (1-9)
68 if self.is_strike(roll_index):
69 total_score += 10 + self.strike_bonus(roll_index)
70 roll_index += 1
71 # Edge Case: Spare in regular frames (1-9)
72 elif self.is_spare(roll_index):
73 total_score += 10 + self.spare_bonus(roll_index)
74 roll_index += 2
75 # Edge Case: Open frame in regular frames (1-9)
76 else:
77 total_score += self.sum_of_balls_in_frame(roll_index)
78 roll_index += 2
79
80 # Score frame 10 (special rules - no bonuses, just sum the rolls)
81 total_score += self.sum_of_balls_in_frame(roll_index)
82
83 return total_score
84
85 def is_game_complete(self):
86 """
87 Check if the game is complete.
88
89 Returns:
90 bool: True if the game is complete, False otherwise.
91 """
92 # Game cannot be complete with fewer than 12 rolls (10 open frames = 20 rolls)
93 # But we need to handle special cases like strikes/spares in 10th frame
94
95 roll_index = 0
96 frame_count = 0
97
98 # Count frames 1-9
99 while frame_count < 9 and roll_index < len(self.rolls):
100 if self.is_strike(roll_index):
101 roll_index += 1
102 else:
103 roll_index += 2
104 frame_count += 1
105
106 # If we haven't processed 9 frames, game is not complete
107 if frame_count < 9:
108 return False
109
110 # Handle frame 10
111 if roll_index >= len(self.rolls):
112 return False
113
114 # Check 10th frame completion
115 tenth_frame_start = roll_index
116
117 if self.is_strike(tenth_frame_start):
118 # Strike in 10th frame: need exactly 2 more rolls
119 return len(self.rolls) == tenth_frame_start + 3
120 elif tenth_frame_start + 1 < len(self.rolls) and self.rolls[tenth_frame_start] + self.rolls[tenth_frame_start + 1] == 10:
121 # Spare in 10th frame: need exactly 1 more roll
122 return len(self.rolls) == tenth_frame_start + 3
123 else:
124 # Open frame in 10th frame: need exactly 2 rolls for this frame
125 return len(self.rolls) == tenth_frame_start + 2
126
127 def is_strike(self, roll_index):
128 """
129 Check if a roll is a strike.
130
131 Args:
132 roll_index (int): Index of the roll to check.
133
134 Returns:
135 bool: True if the roll is a strike, False otherwise.
136 """
137 # Edge Case: Index out of bounds
138 if roll_index >= len(self.rolls):
139 return False
140 return self.rolls[roll_index] == 10
141
142 def is_spare(self, roll_index):
143 """
144 Check if a frame is a spare.
145
146 Args:
147 roll_index (int): Index of the first roll in the frame.
148
149 Returns:
150 bool: True if the frame is a spare, False otherwise.
151 """
152 # Edge Case: Index out of bounds
153 if roll_index + 1 >= len(self.rolls):
154 return False
155 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
156
157 def strike_bonus(self, roll_index):
158 """
159 Calculate the bonus for a strike.
160
161 Args:
162 roll_index (int): Index of the strike roll.
163
164 Returns:
165 int: The bonus points for the strike.
166 """
167 # Edge Case: Not enough rolls for bonus calculation
168 if roll_index + 2 >= len(self.rolls):
169 return 0
170 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
171
172 def spare_bonus(self, roll_index):
173 """
174 Calculate the bonus for a spare.
175
176 Args:
177 roll_index (int): Index of the first roll in the spare frame.
178
179 Returns:
180 int: The bonus points for the spare.
181 """
182 # Edge Case: Not enough rolls for bonus calculation
183 if roll_index + 2 >= len(self.rolls):
184 return 0
185 return self.rolls[roll_index + 2]
186
187 def sum_of_balls_in_frame(self, roll_index):
188 """
189 Calculate the sum of pins knocked down in a frame.
190
191 Args:
192 roll_index (int): Index of the first roll in the frame.
193
194 Returns:
195 int: The sum of pins knocked down in the frame.
196 """
197 # For the 10th frame, we need to sum all remaining rolls
198 # Determine if this is the 10th frame by counting frames
199 temp_index = 0
200 frame_count = 0
201 while temp_index < roll_index and frame_count < 10:
202 if self.is_strike(temp_index):
203 temp_index += 1
204 else:
205 temp_index += 2
206 frame_count += 1
207
208 # If this is the 10th frame, sum all remaining rolls
209 if frame_count == 9: # 10th frame (0-indexed as 9)
210 total = 0
211 for i in range(roll_index, len(self.rolls)):
212 total += self.rolls[i]
213 return total
214
215 # For frames 1-9, normal frame scoring
216 # Edge Case: Not enough rolls in frame
217 if roll_index + 1 >= len(self.rolls):
218 return self.rolls[roll_index] if roll_index < len(self.rolls) else 0
219 return self.rolls[roll_index] + self.rolls[roll_index + 1]
220
221 # Handled Edge Cases: Invalid pin count, game already completed, frame score exceeding 10, game not complete for scoring, strike/spare in regular frames, open frames, index out of bounds, insufficient rolls for bonus calculation
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.