rockstar

Finished
11class BowlingGame:
22 def __init__(self):
3 pass
4
3 """
4 Initialize a new bowling game.
5 """
6 self.rolls = []
7 self.current_frame = 0
8 self.current_roll_in_frame = 0
9
510 def roll(self, pins):
6 pass
7
11 """
12 Record a roll in the game.
13
14 Args:
15 pins (int): Number of pins knocked down in this roll.
16
17 Raises:
18 ValueError: If the number of pins is invalid.
19 IndexError: If the game is already complete.
20 """
21 # Edge Case: Invalid pin count (negative or more than 10)
22 if pins < 0 or pins > 10:
23 raise ValueError("Pins must be between 0 and 10")
24
25 # Edge Case: Game already complete
26 if self.is_game_complete():
27 raise IndexError("Game is complete")
28
29 # Edge Case: Adding pins that would exceed 10 in a frame (except in special tenth frame cases)
30 if self.current_frame < 9: # Regular frames (1-9)
31 if self.current_roll_in_frame == 1: # Second roll of a frame
32 if self.rolls[-1] + pins > 10:
33 raise ValueError("Invalid frame")
34 elif self.current_frame == 9: # Tenth frame - special rules
35 self.validate_tenth_frame_roll(pins)
36
37 self.rolls.append(pins)
38 self.update_frame_state()
39
40 def validate_tenth_frame_roll(self, pins):
41 """Validate rolls in the tenth frame according to special rules."""
42 # Calculate where the 10th frame starts by counting rolls in first 9 frames
43 tenth_frame_start = self.get_tenth_frame_start_index()
44 rolls_in_tenth = len(self.rolls) - tenth_frame_start
45
46 if rolls_in_tenth == 0:
47 # First roll - always valid (0-10 already checked)
48 pass
49 elif rolls_in_tenth == 1:
50 # Second roll
51 if self.rolls[tenth_frame_start] == 10: # First was a strike
52 # Can roll 0-10 after a strike
53 pass
54 else:
55 # First wasn't a strike, so second roll can't exceed remaining pins
56 if self.rolls[tenth_frame_start] + pins > 10:
57 raise ValueError("Invalid frame")
58 elif rolls_in_tenth == 2:
59 # Third roll (fill ball) - only allowed if first was strike or first+second was spare
60 first_roll = self.rolls[tenth_frame_start]
61 second_roll = self.rolls[tenth_frame_start + 1]
62
63 if first_roll == 10: # First was strike
64 # After a strike, second roll can be anything 0-10
65 # If second roll was also a strike, third roll can be anything 0-10
66 # If second roll wasn't a strike, third roll can be anything 0-10 (no pin validation needed)
67 pass
68 elif first_roll + second_roll == 10: # First+second was spare
69 # After a spare, third roll can be anything 0-10
70 pass
71 else:
72 # No strike or spare - shouldn't get third roll
73 raise ValueError("invalid fill balls")
74
75 def get_tenth_frame_start_index(self):
76 """Calculate the index where the 10th frame starts."""
77 roll_index = 0
78
79 # Process first 9 frames to find where 10th frame starts
80 for frame in range(9):
81 if roll_index >= len(self.rolls):
82 break
83
84 if self.rolls[roll_index] == 10: # Strike
85 roll_index += 1
86 else:
87 if roll_index + 1 < len(self.rolls):
88 roll_index += 2
89 else:
90 break
91
92 return roll_index
93
94 def update_frame_state(self):
95 """Update frame state after a roll."""
96 if self.current_frame < 9: # Regular frames
97 if self.rolls[-1] == 10: # Strike - advance to next frame immediately
98 self.current_frame += 1
99 self.current_roll_in_frame = 0
100 else:
101 self.current_roll_in_frame += 1
102 if self.current_roll_in_frame == 2: # Two rolls completed
103 self.current_frame += 1
104 self.current_roll_in_frame = 0
105 # Tenth frame doesn't need special frame state tracking
106
107 def is_game_complete(self):
108 """Check if the game is complete according to bowling rules."""
109 # Need at least 12 rolls for a complete game (9 open frames + 1 strike in 10th + 2 fill balls)
110 if len(self.rolls) < 12:
111 return False
112
113 # Count frames properly
114 frame_count = 0
115 roll_index = 0
116
117 # Process first 9 frames
118 for frame in range(9):
119 if roll_index >= len(self.rolls):
120 return False
121
122 if self.rolls[roll_index] == 10: # Strike
123 roll_index += 1 # Only one roll in strike frame
124 else:
125 if roll_index + 1 >= len(self.rolls):
126 return False
127 if self.rolls[roll_index] + self.rolls[roll_index + 1] > 10:
128 # Invalid frame - should not happen but let's be safe
129 return False
130 roll_index += 2 # Two rolls in regular frame
131
132 # Process 10th frame
133 if roll_index >= len(self.rolls):
134 return False
135
136 first_roll = self.rolls[roll_index]
137
138 # 10th frame can have 2 or 3 rolls depending on strike/spare
139 if first_roll == 10: # Strike in 10th frame
140 if roll_index + 1 >= len(self.rolls):
141 return False
142 second_roll = self.rolls[roll_index + 1]
143
144 if roll_index + 2 >= len(self.rolls):
145 return False
146 # Third roll is always needed after a strike in 10th frame
147 return True
148
149 if roll_index + 1 >= len(self.rolls):
150 return False
151
152 second_roll = self.rolls[roll_index + 1]
153
154 if first_roll + second_roll > 10:
155 # Invalid frame - should not happen but let's be safe
156 return False
157
158 if first_roll + second_roll == 10: # Spare in 10th frame
159 # Need a third roll for spare
160 if roll_index + 2 >= len(self.rolls):
161 return False
162 return True
163 else: # Open frame in 10th
164 # No third roll needed
165 return True
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 # Edge Case: Game not yet complete
178 if not self.is_game_complete():
179 raise IndexError("Game is not complete")
180
181 score = 0
182 roll_index = 0
183
184 # Score first 9 frames
185 for frame in range(9):
186 if self.rolls[roll_index] == 10: # Strike
187 score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
188 roll_index += 1
189 elif self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare
190 score += 10 + self.rolls[roll_index + 2]
191 roll_index += 2
192 else: # Open frame
193 score += self.rolls[roll_index] + self.rolls[roll_index + 1]
194 roll_index += 2
195
196 # Score 10th frame
197 # 10th frame scoring is simply the sum of all rolls in the frame
198 tenth_frame_start = roll_index
199 for i in range(tenth_frame_start, len(self.rolls)):
200 score += self.rolls[i]
201
202 return score
203 # Handled Edge Cases: Invalid pin count, game already completed, exceeding 10 pins in a frame,
204 # game not complete, tenth frame strike/spare handling, invalid fill balls
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.