sn62

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 pins is negative or greater than 10
17 ValueError: If game is already over
18 """
19 # Validate pin count
20 if pins < 0:
21 raise ValueError("Pins cannot be negative")
22
23 if pins > 10:
24 raise ValueError("Pins cannot be more than 10")
25
26 # Check if game is already over
27 if self._is_game_over():
28 raise ValueError("Game is already complete")
29
30 # Check if this roll would exceed 10 pins in current frame
31 if self._would_exceed_frame_pins(pins):
32 raise ValueError("Pins in a frame cannot exceed 10")
33
34 self.rolls.append(pins)
35
836 def score(self):
9 pass
37 """
38 Calculate the total score for the game.
39
40 Returns:
41 int: Total score for the game
42
43 Raises:
44 ValueError: If the game is not yet complete
45 """
46 if not self._is_game_over():
47 raise ValueError("Game is not complete")
48
49 total_score = 0
50 roll_index = 0
51
52 # Score first 9 frames
53 for frame in range(9):
54 if roll_index >= len(self.rolls):
55 raise ValueError("Game is not complete")
56
57 # Strike
58 if self.rolls[roll_index] == 10:
59 if roll_index + 2 >= len(self.rolls):
60 raise ValueError("Game is not complete")
61 total_score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
62 roll_index += 1
63 else:
64 # Two rolls in frame
65 if roll_index + 1 >= len(self.rolls):
66 raise ValueError("Game is not complete")
67
68 frame_score = self.rolls[roll_index] + self.rolls[roll_index + 1]
69
70 # Spare
71 if frame_score == 10:
72 if roll_index + 2 >= len(self.rolls):
73 raise ValueError("Game is not complete")
74 total_score += 10 + self.rolls[roll_index + 2]
75 else:
76 # Open frame
77 total_score += frame_score
78 roll_index += 2
79
80 # Score tenth frame
81 if roll_index >= len(self.rolls):
82 raise ValueError("Game is not complete")
83
84 # Tenth frame can have 2 or 3 rolls
85 while roll_index < len(self.rolls):
86 total_score += self.rolls[roll_index]
87 roll_index += 1
88
89 return total_score
90
91 def _is_game_over(self):
92 """Check if the game is over."""
93 if len(self.rolls) < 12: # Minimum rolls needed
94 return False
95
96 # Count complete frames
97 roll_index = 0
98 frame_count = 0
99
100 # First 9 frames
101 for frame in range(9):
102 if roll_index >= len(self.rolls):
103 return False
104
105 if self.rolls[roll_index] == 10: # Strike
106 roll_index += 1
107 else:
108 if roll_index + 1 >= len(self.rolls):
109 return False
110 roll_index += 2
111 frame_count += 1
112
113 # Tenth frame
114 if roll_index >= len(self.rolls):
115 return False
116
117 # Tenth frame handling
118 if self.rolls[roll_index] == 10: # First roll is strike
119 if roll_index + 2 >= len(self.rolls):
120 return False
121 roll_index += 3 # Strike gets 2 bonus rolls
122 else:
123 if roll_index + 1 >= len(self.rolls):
124 return False
125 if self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare
126 if roll_index + 2 >= len(self.rolls):
127 return False
128 roll_index += 3 # Spare gets 1 bonus roll
129 else: # Open frame
130 roll_index += 2
131 frame_count += 1
132
133 return frame_count == 10 and roll_index == len(self.rolls)
134
135 def _would_exceed_frame_pins(self, pins):
136 """Check if adding pins would exceed 10 in current frame."""
137 if len(self.rolls) == 0:
138 return False
139
140 # If we're at the start of a frame (after a complete frame)
141 if self._is_at_frame_start():
142 return False
143
144 # Check if current frame would exceed 10 pins
145 if len(self.rolls) >= 1:
146 # For tenth frame, we can have more than 10 total (due to fill balls)
147 frame_number = self._get_current_frame()
148 if frame_number == 10:
149 # In tenth frame, we need to check if it's a valid fill ball situation
150 return False # Simplification for now
151
152 # For frames 1-9, check if adding this roll exceeds 10 in the frame
153 last_roll = self.rolls[-1]
154 if last_roll == 10: # Previous was a strike, so this is a new frame
155 return False
156 else: # Previous was not a strike, so this is second roll of frame
157 return last_roll + pins > 10
158
159 return False
160
161 def _is_at_frame_start(self):
162 """Check if we're at the start of a frame."""
163 if len(self.rolls) == 0:
164 return True
165
166 roll_index = 0
167 frame_count = 0
168
169 # Count frames up to current position
170 for frame in range(9):
171 if roll_index >= len(self.rolls):
172 return frame_count == len(self.rolls)
173
174 if self.rolls[roll_index] == 10: # Strike
175 roll_index += 1
176 else:
177 if roll_index + 1 >= len(self.rolls):
178 return False
179 roll_index += 2
180 frame_count += 1
181
182 # We're at the tenth frame
183 return roll_index == len(self.rolls)
184
185 def _get_current_frame(self):
186 """Get the current frame number (1-10)."""
187 if len(self.rolls) == 0:
188 return 1
189
190 roll_index = 0
191 frame_count = 0
192
193 # Count frames up to current position
194 for frame in range(9):
195 if roll_index >= len(self.rolls):
196 return frame_count + 1
197
198 if self.rolls[roll_index] == 10: # Strike
199 roll_index += 1
200 else:
201 if roll_index + 1 >= len(self.rolls):
202 return frame_count + 1
203 roll_index += 2
204 frame_count += 1
205
206 # We're in the tenth frame
207 return 10
Test NameStatus
test_consecutive_spares_each_get_a_one_roll_bonus
Fail
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_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.