let it go

Finished
11class BowlingGame:
22 def __init__(self):
3 pass
4
3 """
4 Initialize a new bowling game.
5 """
6 self.rolls = []
7
58 def roll(self, pins):
6 pass
7
9 """
10 Record a roll in the game.
11
12 Args:
13 pins (int): Number of pins knocked down in this roll.
14
15 Raises:
16 ValueError: If the number of pins is invalid.
17 """
18 # Validate pin count
19 if pins < 0:
20 raise ValueError("Pins must be between 0 and 10")
21 if pins > 10:
22 raise ValueError("Pins must be between 0 and 10")
23
24 # Check if game is already complete
25 if self.is_game_complete():
26 raise ValueError("Game has ended")
27
28 # Check if this roll is valid in the current frame context
29 if len(self.rolls) >= 1:
30 # Check if we're in a regular frame (not 10th frame)
31 if len(self.rolls) < 18:
32 # Check if this is the second roll of a frame
33 # We need to determine if the previous roll was the first roll of a new frame
34 frame_count = 0
35 roll_index = 0
36
37 # Count complete frames
38 while frame_count < 9 and roll_index < len(self.rolls):
39 if self.rolls[roll_index] == 10: # Strike
40 roll_index += 1
41 else:
42 if roll_index + 1 < len(self.rolls):
43 roll_index += 2
44 else:
45 # This is the first roll of an incomplete frame
46 # Check if the sum with this roll would exceed 10
47 if self.rolls[roll_index] + pins > 10:
48 raise ValueError("Cannot knock down more pins than remaining")
49 break
50 frame_count += 1
51 else:
52 # We're in the 10th frame area, handle fill balls
53 # Count frames to determine if we're in fill balls
54 frame_count = 0
55 roll_index = 0
56
57 # Count frames 1-9
58 while frame_count < 9 and roll_index < len(self.rolls):
59 if self.rolls[roll_index] == 10: # Strike
60 roll_index += 1
61 else:
62 roll_index += 2
63 frame_count += 1
64
65 # Now check 10th frame
66 if roll_index < len(self.rolls): # We have rolls in 10th frame
67 if self.rolls[roll_index] == 10: # First roll was strike
68 if roll_index + 1 < len(self.rolls): # Second roll exists
69 # We're about to add the third roll (second fill ball)
70 # If second roll was not a strike, validate sum
71 if self.rolls[roll_index + 1] != 10:
72 if self.rolls[roll_index + 1] + pins > 10:
73 raise ValueError("Cannot knock down more pins than remaining")
74 # If second roll was a strike, no validation needed
75 else:
76 # This is second roll of 10th frame
77 # If first two rolls are strikes, third can be anything
78 pass
79 else: # First roll was not strike
80 if roll_index + 1 < len(self.rolls): # Second roll exists
81 # Check if it's a spare
82 if self.rolls[roll_index] + self.rolls[roll_index + 1] == 10:
83 # This is a fill ball, no validation needed except pin count
84 pass
85 else:
86 # This is second roll of open frame, no third roll should happen
87 pass
88 else:
89 # This is second roll of 10th frame
90 # Check if sum exceeds 10
91 if self.rolls[roll_index] + pins > 10:
92 raise ValueError("Cannot knock down more pins than remaining")
93
94 self.rolls.append(pins)
95
96 def _get_current_frame_index(self):
97 """Get the current frame index (0-based) based on rolls."""
98 # Count complete frames first
99 frame_index = 0
100 roll_index = 0
101
102 # For frames 1-9
103 while frame_index < 9 and roll_index < len(self.rolls):
104 if roll_index < len(self.rolls) and self.rolls[roll_index] == 10: # Strike
105 roll_index += 1
106 else:
107 if roll_index + 1 < len(self.rolls):
108 roll_index += 2
109 else:
110 break
111 frame_index += 1
112
113 return frame_index
114
115 def is_game_complete(self):
116 """Check if the game is complete."""
117 # Need at least 10 frames worth of rolls
118 if len(self.rolls) < 10:
119 return False
120
121 # Count frames
122 frame_count = 0
123 roll_index = 0
124
125 # Count frames 1-9
126 while frame_count < 9 and roll_index < len(self.rolls):
127 if self.rolls[roll_index] == 10: # Strike
128 roll_index += 1
129 else:
130 if roll_index + 1 >= len(self.rolls):
131 return False # Incomplete frame
132 roll_index += 2
133 frame_count += 1
134
135 # If we haven't completed 9 frames, game is not complete
136 if frame_count < 9:
137 return False
138
139 # Check 10th frame
140 if roll_index >= len(self.rolls):
141 return False # No rolls in 10th frame yet
142
143 # 10th frame
144 if self.rolls[roll_index] == 10: # Strike in 10th frame
145 if roll_index + 1 >= len(self.rolls):
146 return False # Need first fill ball
147 if self.rolls[roll_index + 1] == 10: # Second strike
148 if roll_index + 2 >= len(self.rolls):
149 return False # Need second fill ball
150 return True # All rolls complete
151 else: # First strike, second is not strike
152 if roll_index + 2 >= len(self.rolls):
153 return False # Need second fill ball
154 return True # All rolls complete
155 else: # Not a strike in first roll of 10th frame
156 if roll_index + 1 >= len(self.rolls):
157 return False # Need second roll
158 if self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare
159 if roll_index + 2 >= len(self.rolls):
160 return False # Need fill ball
161 return True # All rolls complete
162 else: # Open frame
163 return True # Frame complete
164
165 return False
166
8167 def score(self):
9 pass
168 """
169 Calculate the total score for the game.
170
171 Returns:
172 int: The total score of the game.
173
174 Raises:
175 IndexError: If the game is not yet complete.
176 """
177 # Check if game is complete
178 if not self.is_game_complete():
179 raise IndexError("game not complete")
180
181 total_score = 0
182 roll_index = 0
183
184 for frame in range(10):
185 if frame < 9: # Frames 1-9
186 if self.rolls[roll_index] == 10: # Strike
187 # Add bonus: next two rolls
188 bonus = 0
189 if roll_index + 1 < len(self.rolls):
190 bonus += self.rolls[roll_index + 1]
191 if roll_index + 2 < len(self.rolls):
192 bonus += self.rolls[roll_index + 2]
193 total_score += 10 + bonus
194 roll_index += 1
195 elif self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare
196 # Add bonus: next roll
197 bonus = 0
198 if roll_index + 2 < len(self.rolls):
199 bonus = self.rolls[roll_index + 2]
200 total_score += 10 + bonus
201 roll_index += 2
202 else: # Open frame
203 total_score += self.rolls[roll_index] + self.rolls[roll_index + 1]
204 roll_index += 2
205 else: # 10th frame
206 # For 10th frame, just sum all rolls from roll_index onwards
207 total_score += sum(self.rolls[roll_index:])
208 break
209
210 return total_score
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.