fortitude

Finished
11class BowlingGame:
22 def __init__(self):
3 pass
4
3 """
4 Initialize a new bowling game.
5 """
6 self.rolls = []
7 self.current_roll = 0
8
59 def roll(self, pins):
6 pass
7
10 """
11 Record a roll in the game.
12
13 Args:
14 pins (int): Number of pins knocked down in this roll.
15
16 Raises:
17 ValueError: If the number of pins is invalid.
18 """
19 # Edge Case: Invalid pin count (negative or more than 10)
20 if pins < 0 or pins > 10:
21 raise ValueError("Pins must be between 0 and 10")
22
23 # Edge Case: Invalid pin count for current frame (more than 10 in non-strike frame)
24 # Check if this is a second roll in frames 1-9
25 total_rolls = len(self.rolls)
26 if total_rolls % 2 == 1 and total_rolls < 18: # Second roll in frames 1-9 (before 10th frame)
27 first_roll = self.rolls[-1] # First roll of current frame
28 # Only validate if first roll wasn't a strike (10 pins)
29 if first_roll != 10 and first_roll + pins > 10:
30 raise ValueError("Cannot knock down more than 10 pins in a frame")
31
32 # Edge Case: Game already completed
33 if self.is_game_complete():
34 raise IndexError("Game is already complete")
35
36 self.rolls.append(pins)
37 self.current_roll += 1
38
839 def score(self):
9 pass
40 """
41 Calculate the total score for the game.
42
43 Returns:
44 int: The total score of the game.
45
46 Raises:
47 ValueError: If the game is not yet complete.
48 """
49 # Edge Case: Game not complete
50 if not self.is_game_complete():
51 raise IndexError("Game is not yet complete")
52
53 total_score = 0
54 roll_index = 0
55
56 for frame in range(10):
57 # Edge Case: Strike in regular frames (1-9)
58 if self.is_strike(roll_index) and frame < 9:
59 total_score += 10 + self.strike_bonus(roll_index)
60 roll_index += 1
61 # Edge Case: Spare in regular frames (1-9)
62 elif self.is_spare(roll_index) and frame < 9:
63 total_score += 10 + self.spare_bonus(roll_index)
64 roll_index += 2
65 # Edge Case: Open frame in regular frames (1-9)
66 elif frame < 9:
67 total_score += self.sum_of_balls_in_frame(roll_index)
68 roll_index += 2
69 # Edge Case: Tenth frame handling
70 else: # frame == 9 (10th frame)
71 # Strike in 10th frame
72 if self.is_strike(roll_index):
73 # Edge Case: Not enough rolls for strike bonus in 10th frame
74 if roll_index + 2 >= len(self.rolls):
75 raise ValueError("Not enough rolls to calculate strike bonus in 10th frame")
76 total_score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
77 # Spare in 10th frame
78 elif self.is_spare(roll_index):
79 # Edge Case: Not enough rolls for spare bonus in 10th frame
80 if roll_index + 2 >= len(self.rolls):
81 raise ValueError("Not enough rolls to calculate spare bonus in 10th frame")
82 total_score += 10 + self.rolls[roll_index + 2]
83 # Open frame in 10th frame
84 else:
85 # Edge Case: Not enough rolls in 10th frame
86 if roll_index + 1 >= len(self.rolls):
87 raise ValueError("Not enough rolls in 10th frame")
88 total_score += self.sum_of_balls_in_frame(roll_index)
89
90 # Edge Case: Extra rolls after 10th frame is complete
91 if not self.is_strike(roll_index) and not self.is_spare(roll_index):
92 if roll_index + 2 < len(self.rolls):
93 raise ValueError("Cannot roll after game is over")
94 elif self.is_spare(roll_index):
95 if roll_index + 3 < len(self.rolls):
96 raise ValueError("Cannot roll after game is over")
97 elif self.is_strike(roll_index):
98 if roll_index + 3 < len(self.rolls):
99 raise ValueError("Cannot roll after game is over")
100
101 break
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 roll_count = len(self.rolls)
113 if roll_count < 12: # Minimum rolls needed (all strikes except last frame)
114 return False
115
116 # Count frames
117 frame_count = 0
118 roll_index = 0
119
120 while frame_count < 10 and roll_index < roll_count:
121 if frame_count == 9: # 10th frame
122 # Strike gets 2 more rolls
123 if self.rolls[roll_index] == 10:
124 roll_index += 3
125 # Spare gets 1 more roll
126 elif roll_index + 1 < roll_count and self.rolls[roll_index] + self.rolls[roll_index + 1] == 10:
127 roll_index += 3
128 # Open frame
129 else:
130 roll_index += 2
131 frame_count += 1
132 else: # Frames 1-9
133 if self.rolls[roll_index] == 10: # Strike
134 roll_index += 1
135 else:
136 roll_index += 2
137 frame_count += 1
138
139 return roll_index <= roll_count and frame_count == 10
140
141 def is_strike(self, roll_index):
142 """
143 Check if a roll is a strike.
144
145 Args:
146 roll_index (int): Index of the roll to check.
147
148 Returns:
149 bool: True if the roll is a strike, False otherwise.
150 """
151 # Edge Case: Index out of bounds
152 if roll_index >= len(self.rolls):
153 return False
154 return self.rolls[roll_index] == 10
155
156 def is_spare(self, roll_index):
157 """
158 Check if two rolls form a spare.
159
160 Args:
161 roll_index (int): Index of the first roll to check.
162
163 Returns:
164 bool: True if the two rolls form a spare, False otherwise.
165 """
166 # Edge Case: Index out of bounds or not enough rolls
167 if roll_index + 1 >= len(self.rolls):
168 return False
169 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
170
171 def strike_bonus(self, roll_index):
172 """
173 Calculate the bonus for a strike.
174
175 Args:
176 roll_index (int): Index of the strike roll.
177
178 Returns:
179 int: The bonus points for the strike.
180 """
181 # Edge Case: Not enough rolls for strike bonus
182 if roll_index + 2 >= len(self.rolls):
183 return 0
184 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
185
186 def spare_bonus(self, roll_index):
187 """
188 Calculate the bonus for a spare.
189
190 Args:
191 roll_index (int): Index of the first roll in the spare frame.
192
193 Returns:
194 int: The bonus points for the spare.
195 """
196 # Edge Case: Not enough rolls for spare bonus
197 if roll_index + 2 >= len(self.rolls):
198 return 0
199 return self.rolls[roll_index + 2]
200
201 def sum_of_balls_in_frame(self, roll_index):
202 """
203 Calculate the sum of pins knocked down in a frame.
204
205 Args:
206 roll_index (int): Index of the first roll in the frame.
207
208 Returns:
209 int: The sum of pins knocked down in the frame.
210 """
211 # Edge Case: Not enough rolls in frame
212 if roll_index + 1 >= len(self.rolls):
213 return 0
214 # Edge Case: Invalid frame (more than 10 pins)
215 if self.rolls[roll_index] + self.rolls[roll_index + 1] > 10:
216 raise ValueError("Invalid frame: more than 10 pins knocked down")
217 return self.rolls[roll_index] + self.rolls[roll_index + 1]
218
219 # Handled Edge Cases: Invalid pin count, game already completed, game not complete,
220 # strike in regular frames, spare in regular frames, open frame in regular frames,
221 # tenth frame handling, not enough rolls for strike bonus in 10th frame,
222 # not enough rolls for spare bonus in 10th frame, not enough rolls in 10th frame,
223 # extra rolls after 10th frame is complete, index out of bounds, not enough rolls,
224 # not enough rolls for strike bonus, not enough rolls for spare bonus,
225 # not enough rolls in frame, invalid frame (more than 10 pins)
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.