@ngel

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 = 1
8 self.frame_rolls = []
9 self.frames = []
10
11 def is_game_complete(self):
12 """
13 Check if the game is complete.
14
15 Returns:
16 bool: True if the game is complete, False otherwise
17 """
18 if len(self.frames) < 10:
19 return False
20
21 # Check 10th frame completion
22 tenth_frame = self.frames[9]
23 if len(tenth_frame) == 2:
24 # If it's not a strike or spare, game is complete
25 return tenth_frame[0] != 10 and sum(tenth_frame) != 10
26 elif len(tenth_frame) == 3:
27 # If it has 3 rolls, game is complete
28 return True
29 else:
30 # Less than 2 rolls in 10th frame
31 return False
32
533 def roll(self, pins):
6 pass
7
34 """
35 Record a roll in the game.
36
37 Args:
38 pins (int): Number of pins knocked down in this roll
39
40 Raises:
41 ValueError: If pins is negative or greater than 10
42 IndexError: If game is already over
43 """
44 # Edge Case: Invalid number of pins
45 if pins < 0:
46 raise ValueError("Pins cannot be negative")
47
48 # Edge Case: Too many pins
49 if pins > 10:
50 raise ValueError("Pin count exceeds pins on the lane")
51
52 # Edge Case: Game already completed
53 if self.is_game_complete():
54 raise IndexError("Game is already complete")
55
56 # Handle 10th frame specially (when we have 9 complete frames)
57 if len(self.frames) == 9:
58 # Validate 10th frame pin counts
59 if len(self.frame_rolls) == 0:
60 # First roll - always valid
61 self.frame_rolls.append(pins)
62 elif len(self.frame_rolls) == 1:
63 # Second roll validation
64 if self.frame_rolls[0] == 10: # First was strike
65 # Second roll after strike - always valid
66 self.frame_rolls.append(pins)
67 else:
68 # Regular frame validation - can't exceed 10
69 if self.frame_rolls[0] + pins > 10:
70 raise ValueError("Cannot knock down more than 10 pins in a frame")
71 self.frame_rolls.append(pins)
72 elif len(self.frame_rolls) == 2:
73 # Third roll validation - only allowed if first was strike or first two were spare
74 if self.frame_rolls[0] == 10: # First was strike
75 if self.frame_rolls[1] == 10: # Second was also strike
76 # Third roll after two strikes - always valid
77 self.frame_rolls.append(pins)
78 else:
79 # Second wasn't strike - third roll can't exceed 10 with second
80 if self.frame_rolls[1] + pins > 10:
81 raise ValueError("Cannot knock down more than 10 pins in a frame")
82 self.frame_rolls.append(pins)
83 elif sum(self.frame_rolls[:2]) == 10: # First two were spare
84 # Third roll after spare - always valid
85 self.frame_rolls.append(pins)
86 else:
87 # Open frame - shouldn't get here
88 raise IndexError("Cannot throw bonus with an open tenth frame")
89 else:
90 # Already have 3 rolls - game should be complete
91 raise ValueError("invalid fill balls")
92
93 # Check frame completion
94 if len(self.frame_rolls) == 2:
95 # If it's not a strike or spare, frame is complete
96 if self.frame_rolls[0] != 10 and sum(self.frame_rolls) != 10:
97 self.frames.append(self.frame_rolls[:])
98 self.frame_rolls = []
99 elif len(self.frame_rolls) == 3:
100 # Frame complete with 3 rolls
101 self.frames.append(self.frame_rolls[:])
102 self.frame_rolls = []
103 return
104
105 # Regular frame handling
106 if len(self.frame_rolls) == 0:
107 self.frame_rolls.append(pins)
108 if pins == 10: # Strike
109 self.frames.append([pins])
110 self.frame_rolls = []
111 else: # Second roll in frame
112 # Edge Case: Frame total cannot exceed 10
113 if self.frame_rolls[0] + pins > 10:
114 raise ValueError("Cannot knock down more than 10 pins in a frame")
115 self.frame_rolls.append(pins)
116 self.frames.append(self.frame_rolls[:])
117 self.frame_rolls = []
118
8119 def score(self):
9 pass
120 """
121 Calculate the total score for the game.
122
123 Returns:
124 int: The total score of the game
125
126 Raises:
127 IndexError: If the game is not yet complete
128 """
129 # Edge Case: Game not complete
130 if not self.is_game_complete():
131 raise IndexError("Game is not yet complete")
132
133 total_score = 0
134
135 # Score first 9 frames
136 for i in range(9):
137 frame = self.frames[i]
138
139 if frame[0] == 10: # Strike
140 # Add next two rolls
141 frame_score = 10
142 next_rolls = self.get_next_rolls(i, 2)
143 frame_score += sum(next_rolls)
144 elif sum(frame) == 10: # Spare
145 # Add next roll
146 frame_score = 10
147 next_rolls = self.get_next_rolls(i, 1)
148 frame_score += sum(next_rolls)
149 else: # Open frame
150 frame_score = sum(frame)
151
152 total_score += frame_score
153
154 # Add tenth frame score (just sum all rolls in 10th frame)
155 total_score += sum(self.frames[9])
156
157 return total_score
158
159 def get_next_rolls(self, frame_index, count):
160 """
161 Get the next N rolls after a given frame for scoring strikes/spares.
162
163 Args:
164 frame_index (int): Index of the current frame
165 count (int): Number of rolls to get
166
167 Returns:
168 list: List of the next N rolls
169 """
170 rolls = []
171
172 # Look at subsequent frames to get rolls
173 for i in range(frame_index + 1, len(self.frames)):
174 frame = self.frames[i]
175 for roll in frame:
176 rolls.append(roll)
177 if len(rolls) == count:
178 return rolls
179
180 return rolls
181
182 # Handled Edge Cases: Invalid number of pins, too many pins, game already completed,
183 # tenth frame special handling, frame total exceeding 10, game not complete,
184 # tenth frame incomplete
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.