future

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 or if rolling after game is over.
17 """
18 # Validate pin count
19 if pins < 0 or pins > 10:
20 raise ValueError("invalid pin count")
21
22 # Check if game is already over
23 if self._is_game_over():
24 raise ValueError("cannot roll after game is over")
25
26 # For frames 1-9, validate that we don't exceed 10 pins in a frame
27 # But don't apply this validation to bonus balls in the 10th frame
28 current_frame = self._get_current_frame_index()
29 if (len(self.rolls) > 0 and
30 self._is_in_second_roll_of_frame() and
31 current_frame < 9): # Only apply to frames 1-9, not 10th frame bonus balls
32 # This is the second roll of a frame, validate that sum doesn't exceed 10
33 if self.rolls[-1] + pins > 10:
34 raise ValueError("invalid pin count")
35
36 self.rolls.append(pins)
37
38 def _is_in_second_roll_of_frame(self):
39 """Check if we are currently in the second roll of a frame (not after a strike)"""
40 if len(self.rolls) == 0:
41 return False
42
43 # Count frames to see if the last roll was the first roll of a frame
44 frame_count = 0
45 roll_index = 0
46
47 # Process frames 1-9
48 while roll_index < len(self.rolls) and frame_count < 9:
49 if self.rolls[roll_index] == 10: # Strike
50 frame_count += 1
51 roll_index += 1
52 else:
53 # Two rolls for this frame
54 frame_count += 1
55 roll_index += 2
56
57 # If we've processed all rolls and they form complete frames,
58 # then the next roll is the first roll of the next frame
59 # If we haven't processed all rolls, we're in the middle of a frame
60 return roll_index > len(self.rolls) or (roll_index < len(self.rolls) and self.rolls[roll_index - 1] != 10)
61
862 def score(self):
9 pass
63 """
64 Calculate the total score for the game.
65
66 Returns:
67 int: The total score of the game.
68
69 Raises:
70 ValueError: If the game is not yet complete.
71 """
72 if not self._is_game_complete():
73 raise ValueError("game not complete")
74
75 score = 0
76 roll_index = 0
77
78 # Score frames 1-9
79 for frame in range(10):
80 if roll_index >= len(self.rolls):
81 break
82
83 # Strike frame (first roll is 10)
84 if self.rolls[roll_index] == 10:
85 if frame < 9: # Frames 1-9
86 # Strike - add 10 plus next two rolls
87 if roll_index + 2 < len(self.rolls):
88 score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
89 roll_index += 1
90 else: # 10th frame
91 # In 10th frame, just sum all remaining rolls
92 while roll_index < len(self.rolls):
93 score += self.rolls[roll_index]
94 roll_index += 1
95 else:
96 # Not a strike - check if there are two rolls available
97 if roll_index + 1 < len(self.rolls):
98 frame_score = self.rolls[roll_index] + self.rolls[roll_index + 1]
99 # Spare frame
100 if frame_score == 10:
101 if frame < 9: # Frames 1-9
102 # Spare - add 10 plus next roll
103 if roll_index + 2 < len(self.rolls):
104 score += 10 + self.rolls[roll_index + 2]
105 else: # 10th frame
106 # In 10th frame, just sum the rolls
107 score += frame_score
108 if roll_index + 2 < len(self.rolls):
109 score += self.rolls[roll_index + 2]
110 else:
111 # Open frame
112 score += frame_score
113 roll_index += 2
114 else:
115 roll_index += 1
116
117 return score
118
119 def _get_current_frame_index(self):
120 """Calculate the current frame index (0-9)"""
121 frame_index = 0
122 roll_index = 0
123
124 while roll_index < len(self.rolls) and frame_index < 9:
125 # Strike - one roll for this frame
126 if self.rolls[roll_index] == 10:
127 frame_index += 1
128 roll_index += 1
129 else:
130 # Two rolls for this frame
131 frame_index += 1
132 roll_index += 2
133
134 return frame_index
135
136 def _is_game_over(self):
137 """Check if the game is over (10 frames completed with necessary fill balls)"""
138 return self._is_game_complete()
139
140 def _is_game_complete(self):
141 """Check if the game is complete and ready to be scored"""
142 # Need at least 10 rolls (all strikes) and at most 21 rolls
143 if len(self.rolls) < 10 or len(self.rolls) > 21:
144 return False
145
146 # Count frames
147 frame_count = 0
148 roll_index = 0
149
150 # Process frames 1-9
151 while roll_index < len(self.rolls) and frame_count < 9:
152 if self.rolls[roll_index] == 10: # Strike
153 frame_count += 1
154 roll_index += 1
155 else:
156 if roll_index + 1 < len(self.rolls):
157 frame_count += 1
158 roll_index += 2
159 else:
160 return False # Incomplete frame
161
162 # Process 10th frame
163 if frame_count == 9 and roll_index < len(self.rolls):
164 # Check 10th frame
165 if self.rolls[roll_index] == 10: # Strike in 10th frame
166 # Need two more rolls
167 return roll_index + 2 < len(self.rolls)
168 else:
169 # Second roll of 10th frame
170 if roll_index + 1 < len(self.rolls):
171 if self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: # Spare
172 # Need one more roll
173 return roll_index + 2 < len(self.rolls)
174 else: # Open frame
175 return True
176 else:
177 return False # Incomplete 10th frame
178 elif frame_count == 10:
179 return True
180
181 return False
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.