JiaYou

Finished
11class BowlingGame:
2 """A class to track and score a bowling game."""
3
24 def __init__(self):
3 pass
4
5 def roll(self, pins):
6 pass
7
8 def score(self):
9 pass
5 """Initialize a new bowling game."""
6 self.rolls = []
7 self.game_over = False
8 self.current_frame_pins = 0 # Track pins knocked down in current frame
9
10 def roll(self, pins: int):
11 """Record a roll of the ball.
12
13 Args:
14 pins: Number of pins knocked down
15
16 Raises:
17 ValueError: If pins are invalid or game is over
18 """
19 # Validate pin count
20 if pins < 0:
21 raise ValueError("Pins must be between 0 and 10")
22 if pins > 10:
23 raise ValueError("Pins must be between 0 and 10")
24
25 # Check if game is already over
26 if self.game_over:
27 raise IndexError("Cannot roll after game is over")
28
29 # Validate frame pin count (not applicable to 10th frame)
30 frame_number = self._get_current_frame_number()
31 if frame_number < 10 and len(self.rolls) > 0:
32 # For frames 1-9, check if this roll would exceed 10 pins in the frame
33 # Get the current frame's first roll
34 current_frame_first_roll_index = self._get_frame_start_index(frame_number)
35 if current_frame_first_roll_index < len(self.rolls):
36 first_roll_pins = self.rolls[current_frame_first_roll_index]
37 # If first roll was a strike, this is the start of a new frame
38 if first_roll_pins != 10:
39 # This is the second roll of the frame
40 if first_roll_pins + pins > 10:
41 raise ValueError("Cannot knock down more than 10 pins in a frame")
42
43 # Add roll to list
44 self.rolls.append(pins)
45
46 # Check if game is now over
47 if self._is_game_over():
48 self.game_over = True
49
50 def _get_frame_start_index(self, frame_number: int) -> int:
51 """Get the index of the first roll of the specified frame."""
52 if frame_number <= 1:
53 return 0
54
55 roll_index = 0
56
57 # Count through frames 1 to (frame_number - 1)
58 for frame in range(frame_number - 1):
59 if roll_index >= len(self.rolls):
60 break
61
62 # Strike
63 if self.rolls[roll_index] == 10:
64 roll_index += 1
65 else:
66 # Need two rolls for open frame or spare
67 if roll_index + 1 >= len(self.rolls):
68 break
69 roll_index += 2
70
71 return roll_index
72
73 def _get_current_frame_number(self) -> int:
74 """Get the current frame number (1-10)."""
75 if len(self.rolls) == 0:
76 return 1
77
78 # Count complete frames
79 roll_index = 0
80 frame_count = 0
81
82 # First 9 frames
83 for frame in range(9):
84 if roll_index >= len(self.rolls):
85 break
86
87 # Strike
88 if self.rolls[roll_index] == 10:
89 roll_index += 1
90 else:
91 # Need two rolls for open frame or spare
92 if roll_index + 1 >= len(self.rolls):
93 break
94 roll_index += 2
95 frame_count += 1
96
97 # Current frame (1-10)
98 return frame_count + 1
99
100 def score(self) -> int:
101 """Calculate and return the total score for the game.
102
103 Returns:
104 Total score for the game
105
106 Raises:
107 IndexError: If game is not yet finished
108 """
109 # Check if game is finished
110 if not self._is_game_over():
111 raise IndexError("Game is not yet finished")
112
113 total_score = 0
114 roll_index = 0
115
116 # Score first 9 frames
117 for frame in range(9):
118 if roll_index >= len(self.rolls):
119 break
120
121 # Strike
122 if self.rolls[roll_index] == 10:
123 total_score += 10
124 if roll_index + 1 < len(self.rolls) and roll_index + 2 < len(self.rolls):
125 total_score += self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
126 roll_index += 1
127 else:
128 # Open frame or spare
129 frame_score = self.rolls[roll_index] + self.rolls[roll_index + 1]
130 total_score += frame_score
131 # Spare bonus
132 if frame_score == 10 and roll_index + 2 < len(self.rolls):
133 total_score += self.rolls[roll_index + 2]
134 roll_index += 2
135
136 # Score 10th frame
137 while roll_index < len(self.rolls):
138 total_score += self.rolls[roll_index]
139 roll_index += 1
140
141 return total_score
142
143 def _is_game_over(self) -> bool:
144 """Check if the game is over."""
145 if len(self.rolls) < 10:
146 return False
147
148 # Count complete frames
149 roll_index = 0
150 frame_count = 0
151
152 # First 9 frames
153 for frame in range(9):
154 if roll_index >= len(self.rolls):
155 return False
156
157 # Strike
158 if self.rolls[roll_index] == 10:
159 roll_index += 1
160 else:
161 # Need two rolls for open frame or spare
162 if roll_index + 1 >= len(self.rolls):
163 return False
164 roll_index += 2
165 frame_count += 1
166
167 # 10th frame
168 if roll_index >= len(self.rolls):
169 return False
170
171 # 10th frame can have 2 or 3 rolls
172 tenth_frame_rolls = 0
173 frame_start = roll_index
174
175 # First roll of 10th frame
176 tenth_frame_rolls += 1
177
178 # Second roll of 10th frame
179 if roll_index + 1 < len(self.rolls):
180 tenth_frame_rolls += 1
181
182 # Third roll if first two are strike or spare
183 if (self.rolls[roll_index] == 10 or
184 self.rolls[roll_index] + self.rolls[roll_index + 1] == 10) and \
185 roll_index + 2 < len(self.rolls):
186 tenth_frame_rolls += 1
187
188 return tenth_frame_rolls >= 2 and (
189 # Regular frame (2 rolls)
190 (tenth_frame_rolls == 2 and self.rolls[frame_start] + self.rolls[frame_start + 1] < 10) or
191 # Spare or strike with fill balls
192 (tenth_frame_rolls == 3)
193 )
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.