seahawk

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 game is over.
17 """
18 # Validate pin count
19 if pins < 0:
20 raise ValueError("invalid pin count")
21
22 # Check if game is already over
23 if self._is_game_over():
24 raise IndexError("game already complete")
25
26 # For the 10th frame, special rules apply for fill balls
27 if len(self.rolls) >= 18: # In 10th frame (rolls 18, 19, 20+)
28 # Validate that fill balls don't exceed 10 pins
29 if len(self.rolls) == 20: # Third roll in 10th frame
30 # If previous two rolls were strikes, this roll is valid up to 10
31 if self.rolls[18] == 10 and self.rolls[19] == 10:
32 if pins > 10:
33 raise ValueError("invalid fill balls")
34 # If previous roll was a strike and this is a spare completion
35 elif self.rolls[18] == 10 and self.rolls[19] != 10:
36 if self.rolls[19] + pins > 10:
37 raise ValueError("invalid fill balls")
38 # If previous two rolls complete a spare, this shouldn't happen
39 elif self.rolls[18] != 10 and self.rolls[18] + self.rolls[19] == 10:
40 if pins > 10:
41 raise ValueError("invalid fill balls")
42 elif len(self.rolls) == 19: # Second roll in 10th frame
43 # If first roll was a strike, second roll can be up to 10
44 if self.rolls[18] == 10:
45 if pins > 10:
46 raise ValueError("invalid fill balls")
47 # If first two rolls are not a strike, check they don't exceed 10
48 elif self.rolls[18] + pins > 10:
49 raise ValueError("invalid fill balls")
50
51 # General pin count validation (for non-10th frame or when not a fill ball)
52 if pins > 10:
53 raise ValueError("invalid pin count")
54
55 # For frames 1-9, validate that two rolls don't exceed 10
56 if len(self.rolls) % 2 == 1 and len(self.rolls) < 18: # Second roll of a frame (not 10th frame)
57 if self.rolls[-1] != 10 and self.rolls[-1] + pins > 10:
58 raise ValueError("invalid pin count")
59
60 self.rolls.append(pins)
61
62 def _is_game_over(self):
63 """
64 Determine if the game is over.
65
66 Returns:
67 bool: True if the game is over, False otherwise.
68 """
69 # Count frames
70 frame_count = 0
71 roll_index = 0
72
73 # Count first 9 frames
74 while frame_count < 9 and roll_index < len(self.rolls):
75 if self.rolls[roll_index] == 10: # Strike
76 frame_count += 1
77 roll_index += 1
78 elif roll_index + 1 < len(self.rolls): # Have at least 2 rolls
79 frame_count += 1
80 roll_index += 2
81 else:
82 # Not enough rolls to complete this frame
83 return False
84
85 # Handle 10th frame
86 if frame_count == 9:
87 # Check if we have enough rolls for the 10th frame
88 remaining_rolls = len(self.rolls) - roll_index
89
90 # If we don't have any rolls for 10th frame yet
91 if roll_index >= len(self.rolls):
92 return False
93
94 # If we have at least 2 rolls for 10th frame
95 if remaining_rolls >= 2:
96 # Check if we actually have 2 rolls (not just 1)
97 if roll_index + 1 < len(self.rolls):
98 first_roll = self.rolls[roll_index]
99 second_roll = self.rolls[roll_index + 1]
100
101 # 10th frame is open (2 rolls, sum < 10)
102 if first_roll + second_roll < 10:
103 return remaining_rolls == 2
104
105 # 10th frame is spare (2 rolls, sum = 10) or strike (first roll = 10)
106 # Need to check if we have the required fill balls
107 if first_roll == 10 or first_roll + second_roll == 10:
108 # Need a third roll (fill ball)
109 return remaining_rolls >= 3
110 else:
111 # Open frame, 2 rolls is enough
112 return remaining_rolls == 2
113 else:
114 # Not enough rolls to determine 10th frame
115 return False
116 else:
117 # Not enough rolls for 10th frame
118 return False
119
120 return frame_count >= 10
121
8122 def score(self):
9 pass
123 """
124 Calculate the total score for the game.
125
126 Returns:
127 int: The total score of the game.
128
129 Raises:
130 IndexError: If the game is not yet complete.
131 """
132 # Check if game is complete
133 if not self._is_game_over():
134 raise IndexError("game not yet complete")
135
136 score = 0
137 roll_index = 0
138
139 # Score first 9 frames
140 for frame in range(9):
141 if roll_index >= len(self.rolls):
142 break
143
144 # Strike
145 if self.rolls[roll_index] == 10:
146 # Add 10 plus the next two rolls
147 if roll_index + 2 < len(self.rolls):
148 score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
149 roll_index += 1
150 # Spare
151 elif roll_index + 1 < len(self.rolls) and self.rolls[roll_index] + self.rolls[roll_index + 1] == 10:
152 # Add 10 plus the next roll
153 if roll_index + 2 < len(self.rolls):
154 score += 10 + self.rolls[roll_index + 2]
155 roll_index += 2
156 # Open frame
157 elif roll_index + 1 < len(self.rolls):
158 # Add the sum of the two rolls
159 score += self.rolls[roll_index] + self.rolls[roll_index + 1]
160 roll_index += 2
161 else:
162 # Not enough rolls for this frame
163 break
164
165 # Score 10th frame
166 if roll_index < len(self.rolls):
167 # 10th frame scoring is just the sum of all rolls in the frame
168 # This includes any fill balls
169 tenth_frame_rolls = self.rolls[roll_index:]
170 score += sum(tenth_frame_rolls)
171
172 return score
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.