sn62

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 or game is already complete.
17 """
18 # Validate pin count
19 if pins < 0 or pins > 10:
20 raise ValueError("invalid pin count")
21
22 # Check if game is already complete
23 if self.is_game_complete():
24 raise IndexError("cannot roll after game")
25
26 # Validate frame rules for frames 1-9
27 frame_index = len(self.rolls) // 2
28 if frame_index < 9: # Frames 1-9
29 if len(self.rolls) % 2 == 1: # Second roll of frame
30 # Check if sum exceeds 10 (unless first roll was a strike)
31 if self.rolls[-1] != 10 and self.rolls[-1] + pins > 10:
32 raise ValueError("invalid pin count")
33
34 # Validate 10th frame rules
35 if len(self.rolls) >= 18: # In 10th frame (rolls 18+)
36 if len(self.rolls) == 18: # First roll of 10th frame
37 pass # Any value 0-10 is valid
38 elif len(self.rolls) == 19: # Second roll of 10th frame
39 # If first roll was a strike, any value 0-10 is valid
40 # Otherwise, sum must not exceed 10
41 if len(self.rolls) > 18 and self.rolls[18] != 10 and self.rolls[18] + pins > 10:
42 raise ValueError("invalid pin count")
43 elif len(self.rolls) == 20: # Third roll of 10th frame (fill ball)
44 # Validate fill ball based on previous rolls
45 if len(self.rolls) > 19 and len(self.rolls) > 18 and self.rolls[18] == 10 and self.rolls[19] == 10: # Two strikes
46 # Any value 0-10 is valid for fill ball
47 if pins > 10:
48 raise ValueError("invalid pin count")
49 elif len(self.rolls) > 19 and len(self.rolls) > 18 and self.rolls[18] == 10: # First roll strike, second not strike
50 # Fill ball after strike + non-strike
51 if self.rolls[19] + pins > 10 and self.rolls[19] != 10:
52 raise ValueError("invalid fill balls")
53 if pins > 10:
54 raise ValueError("invalid pin count")
55 elif len(self.rolls) > 19 and len(self.rolls) > 18 and self.rolls[18] + self.rolls[19] == 10: # Spare in 10th frame
56 # Fill ball after spare
57 if pins > 10:
58 raise ValueError("invalid pin count")
59 else:
60 # This shouldn't happen - invalid state
61 raise ValueError("invalid fill balls")
62
63 self.rolls.append(pins)
64
865 def score(self):
9 pass
66 """
67 Calculate the total score for the game.
68
69 Returns:
70 int: The total score for the game.
71
72 Raises:
73 IndexError: If the game is not yet complete.
74 """
75 if not self.is_game_complete():
76 raise IndexError("game not yet complete")
77
78 total_score = 0
79 roll_index = 0
80
81 for frame in range(10):
82 if frame < 9: # Frames 1-9
83 if self.is_strike(roll_index): # Strike
84 total_score += 10 + self.strike_bonus(roll_index)
85 roll_index += 1
86 elif self.is_spare(roll_index): # Spare
87 total_score += 10 + self.spare_bonus(roll_index)
88 roll_index += 2
89 else: # Open frame
90 total_score += self.sum_of_balls_in_frame(roll_index)
91 roll_index += 2
92 else: # 10th frame
93 # For the 10th frame, we just sum the pins knocked down
94 # since any bonus is already included in the frame score
95 remaining_rolls = len(self.rolls) - roll_index
96 if remaining_rolls >= 3: # Strike in 10th frame
97 total_score += self.rolls[roll_index] + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
98 elif remaining_rolls == 2: # Spare or open in 10th frame
99 total_score += self.rolls[roll_index] + self.rolls[roll_index + 1]
100 elif remaining_rolls == 1: # Only one roll left
101 total_score += self.rolls[roll_index]
102
103 return total_score
104
105 def is_game_complete(self):
106 """
107 Check if the game is complete.
108
109 Returns:
110 bool: True if the game is complete, False otherwise.
111 """
112 # Need at least 10 frames
113 if len(self.rolls) < 10:
114 return False
115
116 # For a perfect game (12 strikes), we have 12 rolls total
117 # For a game with all spares, we have 21 rolls total (10 frames * 2 rolls + 1 bonus)
118 # For a standard game, we have 20 rolls total (10 frames * 2 rolls)
119
120 # Count frames to determine completion
121 roll_index = 0
122 frame_count = 0
123
124 # Process frames 1-9
125 while frame_count < 9 and roll_index < len(self.rolls):
126 if roll_index < len(self.rolls) and self.rolls[roll_index] == 10: # Strike
127 roll_index += 1 # One roll for strike
128 else: # Open frame or spare
129 if roll_index + 1 < len(self.rolls):
130 roll_index += 2 # Two rolls for open frame or spare
131 else:
132 return False # Incomplete frame
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 for 10th frame yet
142
143 # 10th frame
144 if roll_index < len(self.rolls) and self.rolls[roll_index] == 10: # Strike in 10th frame
145 # Need two more rolls
146 if roll_index + 2 >= len(self.rolls):
147 return False # Need more rolls
148 elif roll_index + 1 < len(self.rolls) and self.rolls[roll_index] + self.rolls[roll_index + 1] == 10:
149 # Spare in 10th frame
150 if roll_index + 2 >= len(self.rolls):
151 return False # Need one more roll
152 elif roll_index + 1 < len(self.rolls):
153 # Open frame in 10th frame
154 pass # No additional rolls needed
155 else:
156 return False # Incomplete 10th frame
157
158 return True
159
160 def is_strike(self, roll_index):
161 """
162 Check if the roll at roll_index is a strike.
163
164 Args:
165 roll_index (int): Index of the roll to check.
166
167 Returns:
168 bool: True if it's a strike, False otherwise.
169 """
170 return self.rolls[roll_index] == 10
171
172 def is_spare(self, roll_index):
173 """
174 Check if the frame starting at roll_index is a spare.
175
176 Args:
177 roll_index (int): Index of the first roll of the frame.
178
179 Returns:
180 bool: True if it's a spare, False otherwise.
181 """
182 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
183
184 def strike_bonus(self, roll_index):
185 """
186 Calculate the bonus for a strike.
187
188 Args:
189 roll_index (int): Index of the strike roll.
190
191 Returns:
192 int: The bonus points for the strike.
193 """
194 # Need next two rolls for bonus
195 if roll_index + 2 >= len(self.rolls):
196 return 0
197 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
198
199 def spare_bonus(self, roll_index):
200 """
201 Calculate the bonus for a spare.
202
203 Args:
204 roll_index (int): Index of the first roll of the spare frame.
205
206 Returns:
207 int: The bonus points for the spare.
208 """
209 # Need next one roll for bonus
210 if roll_index + 2 >= len(self.rolls):
211 return 0
212 return self.rolls[roll_index + 2]
213
214 def sum_of_balls_in_frame(self, roll_index):
215 """
216 Calculate the sum of pins knocked down in a frame.
217
218 Args:
219 roll_index (int): Index of the first roll of the frame.
220
221 Returns:
222 int: The sum of pins knocked down in the frame.
223 """
224 return self.rolls[roll_index] + self.rolls[roll_index + 1]
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.