unicode

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 # Edge Case: Invalid pin count (negative or more than 10)
19 if pins < 0 or pins > 10:
20 raise ValueError("Pins must be between 0 and 10")
21
22 # Check if game is already complete
23 if self._is_game_complete():
24 raise ValueError("Game is already complete")
25
26 # Check if this roll would exceed 10 pins in a frame (except 10th frame special cases)
27 if not self._is_valid_roll(pins):
28 raise ValueError("Cannot knock down more pins than remaining")
29
30 self.rolls.append(pins)
31
32 def _is_valid_roll(self, pins):
33 """Check if the current roll is valid according to bowling rules."""
34 if len(self.rolls) == 0:
35 return True
36
37 # If we're in the 10th frame, we need special handling
38 if self._is_in_tenth_frame():
39 return self._is_valid_tenth_frame_roll(pins)
40
41 # For regular frames (1-9), check if this would exceed 10 pins
42 # We need to track frames properly considering strikes
43 frame_count = 0
44 roll_index = 0
45
46 while frame_count < 9 and roll_index < len(self.rolls):
47 if self.rolls[roll_index] == 10: # Strike
48 frame_count += 1
49 roll_index += 1
50 else:
51 if roll_index + 1 < len(self.rolls):
52 frame_count += 1
53 roll_index += 2
54 else:
55 # This is the second roll of a frame
56 first_roll = self.rolls[roll_index]
57 return first_roll + pins <= 10
58
59 # If we've completed 9 frames, this is the first roll of the 10th frame
60 return True
61
62 def _is_in_tenth_frame(self):
63 """Check if we're currently in the 10th frame."""
64 frame_count = 0
65 roll_index = 0
66
67 while frame_count < 9 and roll_index < len(self.rolls):
68 if self.rolls[roll_index] == 10: # Strike
69 frame_count += 1
70 roll_index += 1
71 else:
72 if roll_index + 1 < len(self.rolls):
73 frame_count += 1
74 roll_index += 2
75 else:
76 # Incomplete frame before 10th
77 return False
78
79 return frame_count >= 9
80
81 def _is_valid_tenth_frame_roll(self, pins):
82 """Check if a roll in the 10th frame is valid."""
83 tenth_frame_rolls = self._get_tenth_frame_rolls()
84
85 if len(tenth_frame_rolls) == 0:
86 # First roll of 10th frame
87 return True
88 elif len(tenth_frame_rolls) == 1:
89 # Second roll of 10th frame
90 if tenth_frame_rolls[0] == 10: # First was a strike
91 return True # Can roll anything after a strike
92 else:
93 return tenth_frame_rolls[0] + pins <= 10
94 elif len(tenth_frame_rolls) == 2:
95 # Third roll of 10th frame
96 if tenth_frame_rolls[0] == 10: # First was a strike
97 if tenth_frame_rolls[1] == 10: # Second was also a strike
98 return True # Can roll anything
99 else:
100 return True # Can roll anything
101 elif tenth_frame_rolls[0] + tenth_frame_rolls[1] == 10: # Spare
102 return True # Can roll anything
103 else:
104 # Open frame, shouldn't get here
105 return False
106 else:
107 # Already have 3 rolls in 10th frame
108 return False
109
110 def _get_tenth_frame_rolls(self):
111 """Get the rolls that belong to the 10th frame."""
112 frame_count = 0
113 roll_index = 0
114
115 # Find where 10th frame starts
116 while frame_count < 9 and roll_index < len(self.rolls):
117 if self.rolls[roll_index] == 10: # Strike
118 frame_count += 1
119 roll_index += 1
120 else:
121 if roll_index + 1 < len(self.rolls):
122 frame_count += 1
123 roll_index += 2
124 else:
125 break
126
127 # Return rolls from 10th frame onwards
128 return self.rolls[roll_index:]
129
130 def _is_game_complete(self):
131 """Check if the game is complete."""
132 # Count completed frames
133 frame_count = 0
134 roll_index = 0
135
136 # Process first 9 frames
137 while frame_count < 9 and roll_index < len(self.rolls):
138 if self.rolls[roll_index] == 10: # Strike
139 frame_count += 1
140 roll_index += 1
141 else:
142 if roll_index + 1 < len(self.rolls):
143 frame_count += 1
144 roll_index += 2
145 else:
146 # Incomplete frame before 10th
147 return False
148
149 # If we haven't completed 9 frames, game is not complete
150 if frame_count < 9:
151 return False
152
153 # Process 10th frame
154 if roll_index >= len(self.rolls):
155 # No rolls in 10th frame yet
156 return False
157
158 tenth_frame_rolls = self.rolls[roll_index:]
159
160 if len(tenth_frame_rolls) == 0:
161 return False
162 elif len(tenth_frame_rolls) == 1:
163 # Only one roll in 10th frame
164 return False
165 elif len(tenth_frame_rolls) == 2:
166 # Two rolls in 10th frame
167 if tenth_frame_rolls[0] == 10: # Strike
168 # Need third roll
169 return False
170 elif tenth_frame_rolls[0] + tenth_frame_rolls[1] == 10: # Spare
171 # Need third roll
172 return False
173 else: # Open frame
174 return True
175 elif len(tenth_frame_rolls) >= 3:
176 # Three rolls in 10th frame, game is complete
177 return True
178
179 return False
180
8181 def score(self):
9 pass
182 """
183 Calculate the total score for the game.
184
185 Returns:
186 int: The total score of the game.
187
188 Raises:
189 ValueError: If the game is not yet complete.
190 """
191 # Check if game is complete
192 if not self._is_game_complete():
193 raise ValueError("Game is not yet complete")
194
195 score = 0
196 roll_index = 0
197
198 # Score first 9 frames
199 for frame in range(9):
200 if self.rolls[roll_index] == 10: # Strike
201 score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
202 roll_index += 1
203 elif self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare
204 score += 10 + self.rolls[roll_index + 2]
205 roll_index += 2
206 else: # Open frame
207 score += self.rolls[roll_index] + self.rolls[roll_index + 1]
208 roll_index += 2
209
210 # Score 10th frame
211 if roll_index < len(self.rolls):
212 # 10th frame scoring is simply the sum of all rolls in the frame
213 tenth_frame_rolls = self.rolls[roll_index:]
214 score += sum(tenth_frame_rolls)
215
216 return score
Test NameStatus
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_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_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.