harrison

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 # Validate pin count
20 if pins < 0 or pins > 10:
21 raise ValueError("Pins must be between 0 and 10")
22
23 # Check if game is already over
24 if self._is_game_complete():
25 raise IndexError("Cannot roll after game is over")
26
27 # Validate frame pin count
28 if self.current_roll > 0:
29 # Check if we're in the 10th frame
30 frame_count = self._count_frames()
31
32 if frame_count < 9: # Frames 0-8 (not 10th frame)
33 # Check if this is the second roll of a frame (not after a strike)
34 prev_roll = self.rolls[self.current_roll - 1]
35
36 # If previous roll was NOT a strike, this might be the second roll of a frame
37 if prev_roll != 10:
38 # Check if we're still in the same frame (not after a spare)
39 if self.current_roll == 1: # Second roll of first frame
40 if self.rolls[0] + pins > 10:
41 raise ValueError("Pins must be between 0 and 10")
42 elif self.current_roll >= 2:
43 # Check if the roll before previous was a strike
44 if self.current_roll >= 2 and self.rolls[self.current_roll - 2] == 10:
45 # Previous roll was first roll after a strike, so this is first roll of a new frame
46 # No validation needed
47 pass
48 else:
49 # This is the second roll of a frame
50 if self.rolls[self.current_roll - 1] + pins > 10:
51 raise ValueError("Pins must be between 0 and 10")
52 # If previous roll was a strike, this is first roll of next frame
53 # No validation needed for frame totals
54 elif frame_count == 9: # 10th frame - special rules
55 # In 10th frame, validation is different
56 if self.current_roll >= 18: # We're in the 10th frame
57 tenth_frame_start = 18
58 for i in range(9): # Count rolls in first 9 frames
59 if i < len(self.rolls) and self.rolls[i] == 10:
60 tenth_frame_start -= 1 # Strike uses only 1 roll
61 else:
62 tenth_frame_start -= 2 # Non-strike uses 2 rolls
63
64 rolls_in_tenth = self.current_roll - tenth_frame_start
65
66 if rolls_in_tenth == 1: # Second roll of 10th frame
67 first_roll = self.rolls[tenth_frame_start]
68 if first_roll != 10 and first_roll + pins > 10:
69 raise ValueError("Pins must be between 0 and 10")
70 # Third roll of 10th frame (fill ball) - no validation needed
71 # If we're past the 10th frame, game should be over (handled by _is_game_complete)
72
73 self.rolls.append(pins)
74 self.current_roll += 1
75
876 def score(self):
9 pass
77 """
78 Calculate the total score for the game.
79
80 Returns:
81 int: The total score of the game.
82
83 Raises:
84 IndexError: If the game is not complete.
85 """
86 # Edge Case: Game not complete
87 if not self._is_game_complete():
88 raise IndexError("Score cannot be taken until the end of the game")
89
90 total_score = 0
91 roll_index = 0
92
93 # Score first 9 frames
94 for frame in range(9):
95 if roll_index >= len(self.rolls):
96 break
97
98 # Edge Case: Strike
99 if self.rolls[roll_index] == 10: # Strike
100 total_score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
101 roll_index += 1
102 # Edge Case: Spare
103 elif self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare
104 total_score += 10 + self.rolls[roll_index + 2]
105 roll_index += 2
106 # Edge Case: Open frame
107 else:
108 total_score += self.rolls[roll_index] + self.rolls[roll_index + 1]
109 roll_index += 2
110
111 # Score 10th frame
112 if roll_index < len(self.rolls):
113 tenth_frame_rolls = self.rolls[roll_index:]
114
115 if len(tenth_frame_rolls) >= 1 and tenth_frame_rolls[0] == 10: # Strike in 10th
116 if len(tenth_frame_rolls) == 3:
117 total_score += 10 + tenth_frame_rolls[1] + tenth_frame_rolls[2]
118 else:
119 total_score += 10 # Incomplete, but this shouldn't happen if game is complete
120 elif len(tenth_frame_rolls) >= 2 and tenth_frame_rolls[0] + tenth_frame_rolls[1] == 10: # Spare in 10th
121 if len(tenth_frame_rolls) == 3:
122 total_score += 10 + tenth_frame_rolls[2]
123 else:
124 total_score += 10 # Incomplete, but this shouldn't happen if game is complete
125 else: # Open frame in 10th
126 total_score += tenth_frame_rolls[0] + tenth_frame_rolls[1]
127
128 return total_score
129
130 def _is_game_complete(self):
131 """
132 Check if the game is complete (10 frames have been played).
133
134 Returns:
135 bool: True if the game is complete, False otherwise.
136 """
137 if len(self.rolls) < 12: # Minimum rolls needed (all strikes except last frame)
138 return False
139
140 # Count frames
141 roll_index = 0
142 for frame in range(9): # First 9 frames
143 if roll_index >= len(self.rolls):
144 return False
145 if self.rolls[roll_index] == 10: # Strike
146 roll_index += 1
147 else:
148 if roll_index + 1 >= len(self.rolls):
149 return False
150 roll_index += 2
151
152 # 10th frame
153 if roll_index >= len(self.rolls):
154 return False
155
156 # Count rolls in 10th frame
157 tenth_frame_rolls = self.rolls[roll_index:]
158 if len(tenth_frame_rolls) >= 1 and tenth_frame_rolls[0] == 10: # First roll is strike
159 return len(tenth_frame_rolls) == 3
160 elif len(tenth_frame_rolls) >= 2 and tenth_frame_rolls[0] + tenth_frame_rolls[1] == 10: # Spare
161 return len(tenth_frame_rolls) == 3
162 elif len(tenth_frame_rolls) >= 2: # Open frame
163 return len(tenth_frame_rolls) == 2
164
165 return False
166
167 def _count_frames(self):
168 """
169 Count how many complete frames have been played so far.
170
171 Returns:
172 int: Number of complete frames (0-10)
173 """
174 frame_count = 0
175 roll_index = 0
176
177 # Count through first 9 frames
178 while frame_count < 9 and roll_index < len(self.rolls):
179 if self.rolls[roll_index] == 10: # Strike
180 roll_index += 1
181 else:
182 if roll_index + 1 < len(self.rolls):
183 roll_index += 2
184 else:
185 break # Incomplete frame
186 frame_count += 1
187
188 return frame_count
189
190 def _get_frame_and_roll(self):
191 """
192 Calculate which frame and which roll of the frame we're currently on.
193
194 Returns:
195 tuple: (frame_index, roll_in_frame) where frame_index is 0-9 and roll_in_frame is 0, 1, or 2
196 """
197 frame_index = 0
198 roll_index = 0
199
200 # Count through frames to find current position
201 while frame_index < 9 and roll_index < self.current_roll:
202 if roll_index < len(self.rolls) and self.rolls[roll_index] == 10: # Strike
203 roll_index += 1
204 else:
205 roll_index += 2
206 frame_index += 1
207
208 # If we've reached or passed the 10th frame
209 if frame_index >= 9:
210 frame_index = 9
211 # Find where 10th frame actually starts
212 temp_index = 0
213 for i in range(9): # First 9 frames
214 if temp_index < len(self.rolls) and self.rolls[temp_index] == 10: # Strike
215 temp_index += 1
216 else:
217 temp_index += 2
218 roll_in_frame = self.current_roll - temp_index
219 else:
220 # We're in frames 0-8
221 if roll_index < self.current_roll:
222 roll_in_frame = self.current_roll - roll_index
223 else:
224 roll_in_frame = 0
225
226 return frame_index, roll_in_frame
227
228 # Handled Edge Cases: Game not complete, Strike, Spare, Open frame, 10th frame handling, Strike in 10th frame, Spare in 10th frame, Open frame in 10th frame
229
230# Handled Edge Cases: Negative pins, Too many pins in a single roll, Too many pins in a frame, Game not complete, Strike, Spare, Open frame, 10th frame handling, Strike in 10th frame, Spare in 10th frame, Open frame in 10th 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.