Finished
11class BowlingGame:
22 def __init__(self):
3 pass
3 self.rolls = []
44
55 def roll(self, pins):
6 pass
6 # Edge Case: Invalid pin count (negative or over 10)
7 if pins < 0 or pins > 10:
8 raise ValueError("pins must be between 0 and 10")
9
10 # Edge Case: Game already completed
11 if self.is_game_complete():
12 raise ValueError("cannot roll after game is over")
13
14 # Edge Case: Two rolls in a frame cannot exceed 10 points
15 if not self.is_valid_roll(pins):
16 raise ValueError("invalid roll")
17
18 self.rolls.append(pins)
719
820 def score(self):
9 pass
21 # Edge Case: Game not yet completed
22 if not self.is_game_complete():
23 raise ValueError("game not yet complete")
24
25 score = 0
26 roll_index = 0
27
28 for frame in range(10):
29 if frame < 9: # Frames 1-9
30 if self.is_strike(roll_index):
31 score += 10 + self.strike_bonus(roll_index)
32 roll_index += 1
33 elif self.is_spare(roll_index):
34 score += 10 + self.spare_bonus(roll_index)
35 roll_index += 2
36 else:
37 score += self.sum_of_balls_in_frame(roll_index)
38 roll_index += 2
39 else: # Frame 10 (special case)
40 frame_10_rolls = self.get_frame_10_rolls()
41 score += sum(frame_10_rolls)
42
43 return score
44
45 def is_valid_roll(self, pins):
46 """Check if the current roll is valid based on frame rules"""
47 if len(self.rolls) == 0:
48 return True
49
50 # Find current frame
51 frame_index, roll_in_frame = self.get_current_frame_and_roll()
52
53 # 10th frame has special rules
54 if frame_index == 9:
55 return self.is_valid_10th_frame_roll(pins, roll_in_frame)
56
57 # Frames 1-9
58 if roll_in_frame == 0:
59 return True # First roll of frame can be 0-10
60 else:
61 # Second roll - can't exceed 10 total in frame
62 frame_start = len(self.rolls) - 1
63 return self.rolls[frame_start] + pins <= 10
64
65 def get_current_frame_and_roll(self):
66 """Get current frame index (0-9) and roll in frame (0 or 1)"""
67 frame_index = 0
68 roll_index = 0
69
70 while frame_index < 9 and roll_index < len(self.rolls):
71 if self.rolls[roll_index] == 10: # Strike
72 frame_index += 1
73 roll_index += 1
74 else:
75 if roll_index + 1 < len(self.rolls):
76 frame_index += 1
77 roll_index += 2
78 else:
79 # We're in the middle of a frame
80 return frame_index, 1
81
82 if frame_index >= 9:
83 # We're in 10th frame
84 rolls_in_10th = len(self.rolls) - roll_index
85 return 9, rolls_in_10th
86 else:
87 return frame_index, 0
88
89 def is_valid_10th_frame_roll(self, pins, roll_in_frame):
90 """Validate rolls in the 10th frame"""
91 frame_10_rolls = self.get_frame_10_rolls()
92
93 if roll_in_frame == 0:
94 return True # First roll can be 0-10
95 elif roll_in_frame == 1:
96 if frame_10_rolls[0] == 10: # First roll was strike
97 return True # Second roll after strike can be 0-10
98 else:
99 return frame_10_rolls[0] + pins <= 10 # Can't exceed 10
100 elif roll_in_frame == 2:
101 # Third roll only if first was strike or first two were spare
102 if frame_10_rolls[0] == 10: # First was strike
103 if frame_10_rolls[1] == 10: # Second was also strike
104 return True # Third can be 0-10
105 else:
106 return frame_10_rolls[1] + pins <= 10 # Can't exceed 10
107 else: # First two were spare
108 return True # Third roll after spare can be 0-10
109 else:
110 return False # Shouldn't get here
111
112 def is_game_complete(self):
113 """Check if the game is complete"""
114 if len(self.rolls) < 12: # Minimum rolls (all strikes)
115 return False
116
117 frame_index, roll_index = 0, 0
118
119 # Count frames
120 while frame_index < 10 and roll_index < len(self.rolls):
121 if frame_index < 9: # Frames 1-9
122 if self.rolls[roll_index] == 10: # Strike
123 frame_index += 1
124 roll_index += 1
125 else:
126 if roll_index + 1 < len(self.rolls):
127 frame_index += 1
128 roll_index += 2
129 else:
130 return False # Incomplete frame
131 else: # Frame 10
132 frame_10_rolls = self.get_frame_10_rolls()
133 if len(frame_10_rolls) == 0:
134 return False
135 elif len(frame_10_rolls) == 1:
136 return False
137 elif len(frame_10_rolls) == 2:
138 # Complete if no strike/spare, or if strike/spare but no more rolls needed
139 first = frame_10_rolls[0]
140 second = frame_10_rolls[1]
141 if first == 10 or first + second == 10:
142 return False # Need third roll
143 else:
144 return True # Open frame, complete
145 else: # 3 rolls
146 return True
147
148 # If we processed all 10 frames, game is complete
149 # But only if we actually have 10 frames worth of rolls
150 if frame_index >= 10:
151 return True
152 else:
153 return False
154
155 def get_frame_10_rolls(self):
156 # Get the rolls that belong to the 10th frame
157 frame_10_rolls = []
158 roll_index = 0
159
160 for frame in range(9): # First 9 frames
161 if roll_index >= len(self.rolls):
162 break
163 if self.is_strike(roll_index):
164 roll_index += 1
165 else:
166 roll_index += 2
167
168 # Now roll_index points to the start of 10th frame
169 # Collect 10th frame rolls
170 while roll_index < len(self.rolls):
171 frame_10_rolls.append(self.rolls[roll_index])
172 roll_index += 1
173
174 return frame_10_rolls
175
176 def is_strike(self, roll_index):
177 return self.rolls[roll_index] == 10
178
179 def is_spare(self, roll_index):
180 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
181
182 def strike_bonus(self, roll_index):
183 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
184
185 def spare_bonus(self, roll_index):
186 return self.rolls[roll_index + 2]
187
188 def sum_of_balls_in_frame(self, roll_index):
189 return self.rolls[roll_index] + self.rolls[roll_index + 1]
190
191 # Handled Edge Cases: invalid pin counts (negative or over 10), game already completed, invalid roll after strike, game not yet completed when scoring, incomplete game when scoring, not enough rolls for strike bonus calculation, not enough rolls for spare bonus calculation
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.