fortitude

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.
17 """
18 # Validate pin count
19 if pins < 0:
20 raise ValueError("Pins must be between 0 and 10")
21
22 if pins > 10:
23 raise ValueError("Pins must be between 0 and 10")
24
25 # Check if game is already over BEFORE adding the roll
26 if self.is_game_complete():
27 raise ValueError("Game has already ended")
28
29 # For frames 1-9, check normal frame rules
30 roll_index = len(self.rolls)
31
32 # If we're in frames 1-9 (rolls 0-17)
33 if roll_index < 18:
34 # If this is the second roll of a frame (odd index)
35 if roll_index % 2 == 1:
36 # Sum of both rolls must be <= 10
37 # But if the first roll was a strike (10), this shouldn't happen as strikes are single rolls
38 if self.rolls[roll_index - 1] != 10 and self.rolls[roll_index - 1] + pins > 10:
39 raise ValueError("Cannot knock down more than 10 pins in a frame")
40 # If we're in the 10th frame (rolls 18+)
41 elif roll_index >= 18:
42 # If this is the second roll of the 10th frame
43 if roll_index == 19:
44 # If first roll was a strike, second roll can be anything 0-10
45 if len(self.rolls) > 18 and self.rolls[18] != 10 and self.rolls[18] + pins > 10:
46 raise ValueError("Cannot knock down more than 10 pins in a frame")
47 # If this is the third roll of the 10th frame
48 elif roll_index == 20:
49 # Check if we have valid conditions for a third roll
50 if len(self.rolls) > 18 and len(self.rolls) > 19:
51 tenth_first = self.rolls[18]
52 tenth_second = self.rolls[19]
53
54 # If first two were strikes, third can be anything 0-10
55 if not (tenth_first == 10 and tenth_second == 10):
56 # If first was strike, second wasn't, check the sum
57 if tenth_first == 10 and tenth_second + pins > 10:
58 raise ValueError("Cannot knock down more than 10 pins in a frame")
59 # If first wasn't strike but we have a third roll, it means we had a spare
60 # In that case, the third roll can be anything 0-10
61
62 self.rolls.append(pins)
63
864 def score(self):
9 pass
65 """
66 Calculate the total score for the game.
67
68 Returns:
69 int: The total score of the game.
70
71 Raises:
72 ValueError: If the game is not yet complete.
73 """
74 # Edge Case: Game is not complete
75 if not self.is_game_complete():
76 raise ValueError("Game is not yet complete")
77
78 score = 0
79 roll_index = 0
80
81 for frame in range(10):
82 if self.is_strike(roll_index): # Strike
83 score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
84 roll_index += 1
85 elif self.is_spare(roll_index): # Spare
86 score += 10 + self.rolls[roll_index + 2]
87 roll_index += 2
88 else: # Open frame
89 score += self.rolls[roll_index] + self.rolls[roll_index + 1]
90 roll_index += 2
91
92 return score
93
94 def is_strike(self, roll_index):
95 """
96 Check if a roll is a strike.
97
98 Args:
99 roll_index (int): Index of the roll to check.
100
101 Returns:
102 bool: True if the roll is a strike, False otherwise.
103 """
104 return self.rolls[roll_index] == 10
105
106 def is_spare(self, roll_index):
107 """
108 Check if a frame is a spare.
109
110 Args:
111 roll_index (int): Index of the first roll of the frame.
112
113 Returns:
114 bool: True if the frame is a spare, False otherwise.
115 """
116 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
117
118 def is_game_complete(self):
119 """
120 Check if the game is complete.
121
122 Returns:
123 bool: True if the game is complete, False otherwise.
124 """
125 # Need at least 10 frames worth of rolls
126 if len(self.rolls) < 10:
127 return False
128
129 # Count how many frames we have
130 roll_index = 0
131 frame_count = 0
132
133 # Count frames 1-9
134 while frame_count < 9 and roll_index < len(self.rolls):
135 if self.rolls[roll_index] == 10: # Strike
136 frame_count += 1
137 roll_index += 1
138 else: # Open frame or spare
139 if roll_index + 1 < len(self.rolls):
140 frame_count += 1
141 roll_index += 2
142 else:
143 break
144
145 # If we don't have 9 complete frames, game is not complete
146 if frame_count < 9:
147 return False
148
149 # Check 10th frame
150 if roll_index >= len(self.rolls):
151 return False
152
153 # 10th frame
154 if self.rolls[roll_index] == 10: # Strike in 10th frame
155 # Need two more rolls (fill balls)
156 return roll_index + 3 <= len(self.rolls)
157 elif roll_index + 1 < len(self.rolls) and self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare in 10th frame
158 # Need one more roll (fill ball)
159 return roll_index + 3 <= len(self.rolls)
160 elif roll_index + 1 < len(self.rolls): # Open frame in 10th frame
161 # No extra rolls needed
162 return roll_index + 2 <= len(self.rolls)
163 else:
164 return False
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.