awesome

Finished
11class BowlingGame:
22 def __init__(self):
3 pass
4
3 self.rolls = []
4 self.current_roll = 0
5
56 def roll(self, pins):
6 pass
7
7 # Edge Case: Number of pins must be between 0 and 10
8 if pins < 0 or pins > 10:
9 raise ValueError("Pins must be between 0 and 10")
10
11 # Edge Case: Cannot roll after game is over
12 if self.is_game_over():
13 raise ValueError("Game is already complete")
14
15 # Edge Case: Cannot knock down more than 10 pins in a frame
16 if (len(self.rolls) % 2 == 1 and
17 not self.is_strike(len(self.rolls) - 1) and
18 self.rolls[-1] + pins > 10):
19 raise ValueError("Cannot knock down more than 10 pins in a frame")
20
21 # Edge Case: Validate fill balls in 10th frame
22 if len(self.rolls) >= 19: # We might be in the 10th frame
23 # Check if we're dealing with 10th frame fill balls
24 frame_count = 0
25 roll_index = 0
26
27 # Count frames up to 9
28 while frame_count < 9 and roll_index < len(self.rolls):
29 if self.is_strike(roll_index):
30 roll_index += 1
31 else:
32 roll_index += 2
33 frame_count += 1
34
35 # If we're in the 10th frame
36 if frame_count == 9 and roll_index < len(self.rolls):
37 # Strike in 10th frame - validate fill balls
38 if self.is_strike(roll_index):
39 if len(self.rolls) == roll_index + 2: # About to add second fill ball
40 if self.rolls[roll_index + 1] + pins > 10 and not self.is_strike(roll_index + 1):
41 raise ValueError("Fill balls cannot exceed 10 pins")
42
43 self.rolls.append(pins)
44 self.current_roll += 1
45
846 def score(self):
9 pass
47 # Edge Case: Cannot score incomplete game
48 if not self.is_game_over():
49 raise ValueError("Game is not complete yet")
50
51 total_score = 0
52 roll_index = 0
53
54 for frame in range(10):
55 # Edge Case: Strike (first roll of frame knocks down all 10 pins)
56 if self.is_strike(roll_index):
57 # Add 10 plus the next two rolls
58 total_score += 10 + self.strike_bonus(roll_index)
59 roll_index += 1
60 # Edge Case: Spare (both rolls of frame knock down all 10 pins)
61 elif self.is_spare(roll_index):
62 # Add 10 plus the next roll
63 total_score += 10 + self.spare_bonus(roll_index)
64 roll_index += 2
65 # Edge Case: Open frame (less than 10 pins knocked down in frame)
66 else:
67 # Add the sum of both rolls
68 total_score += self.sum_of_balls_in_frame(roll_index)
69 roll_index += 2
70
71 return total_score
72
73 def is_game_over(self):
74 # Need at least 12 rolls for a game with strikes in first 9 frames and 3 rolls in 10th
75 if len(self.rolls) < 12:
76 return False
77
78 # For first 9 frames, calculate normally
79 frame_count = 0
80 roll_index = 0
81
82 # Process first 9 frames
83 while frame_count < 9 and roll_index < len(self.rolls):
84 if self.is_strike(roll_index):
85 roll_index += 1
86 else:
87 roll_index += 2
88 frame_count += 1
89
90 # If we haven't completed 9 frames, game is not over
91 if frame_count < 9:
92 return False
93
94 # Now check 10th frame
95 # At this point, roll_index points to the start of 10th frame
96 if roll_index >= len(self.rolls):
97 return False
98
99 # Check 10th frame
100 if self.is_strike(roll_index):
101 # Strike in 10th frame - need 2 more rolls
102 return len(self.rolls) >= roll_index + 3
103 elif (roll_index + 1 < len(self.rolls) and
104 self.rolls[roll_index] + self.rolls[roll_index + 1] == 10):
105 # Spare in 10th frame - need 1 more roll
106 return len(self.rolls) >= roll_index + 3
107 else:
108 # Open frame in 10th frame - need exactly 2 rolls
109 return len(self.rolls) >= roll_index + 2
110
111 def is_strike(self, roll_index):
112 return self.rolls[roll_index] == 10
113
114 def is_spare(self, roll_index):
115 return self.rolls[roll_index] + self.rolls[roll_index + 1] == 10
116
117 def strike_bonus(self, roll_index):
118 return self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
119
120 def spare_bonus(self, roll_index):
121 return self.rolls[roll_index + 2]
122
123 def sum_of_balls_in_frame(self, roll_index):
124 return self.rolls[roll_index] + self.rolls[roll_index + 1]
125
126 def _is_fill_ball(self):
127 """Determine if the current roll is a fill ball in the 10th frame."""
128 # Count completed frames
129 frame_count = 0
130 roll_index = 0
131
132 # Process first 9 frames
133 while frame_count < 9 and roll_index < len(self.rolls):
134 if self.is_strike(roll_index):
135 roll_index += 1
136 else:
137 roll_index += 2
138 frame_count += 1
139
140 # If we haven't completed 9 frames, this is not a fill ball
141 if frame_count < 9:
142 return False
143
144 # Check if we're in the 10th frame fill balls
145 # roll_index now points to the start of 10th frame
146 if roll_index >= len(self.rolls):
147 return False
148
149 # Check 10th frame type
150 if self.is_strike(roll_index):
151 # Strike in 10th frame - rolls at roll_index+1 and roll_index+2 are fill balls
152 return len(self.rolls) > roll_index + 1
153 elif (roll_index + 1 < len(self.rolls) and
154 self.rolls[roll_index] + self.rolls[roll_index + 1] == 10):
155 # Spare in 10th frame - roll at roll_index+2 is a fill ball
156 return len(self.rolls) > roll_index + 2
157 else:
158 # Open frame in 10th frame - no fill balls
159 return False
160
161 # Handled Edge Cases: Pins out of range, rolling after game over, scoring incomplete game, strike, spare, open frame, 10th frame special cases
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.