Finished
11class BowlingGame:
22 def __init__(self):
3 pass
3 self.rolls = []
4 self.frames = [] # Store completed frames
5 self.current_frame_rolls = [] # Rolls in current frame
6 self.current_frame = 0
7 self.is_finished = False
48
59 def roll(self, pins):
6 pass
7
10 # Edge Case: Game already finished
11 if self.is_finished:
12 raise ValueError("cannot roll after game is over")
13
14 # Edge Case: Invalid pin count (negative)
15 if pins < 0:
16 raise ValueError("negative roll is invalid")
17
18 # Edge Case: Invalid pin count (more than 10)
19 if pins > 10:
20 raise ValueError("pin count exceeds pins on the lane")
21
22 # Handle rolls based on current frame
23 if self.current_frame < 9: # Frames 0-8 (regular frames)
24 if len(self.current_frame_rolls) == 0: # First roll of frame
25 self.current_frame_rolls.append(pins)
26 self.rolls.append(pins)
27 # Strike - complete frame immediately
28 if pins == 10:
29 self.frames.append([pins])
30 self.current_frame_rolls = []
31 self.current_frame += 1
32 else: # Second roll of frame
33 # Validate that sum doesn't exceed 10
34 if self.current_frame_rolls[0] + pins > 10:
35 raise ValueError("pin count exceeds pins on the lane")
36
37 self.current_frame_rolls.append(pins)
38 self.rolls.append(pins)
39 self.frames.append(self.current_frame_rolls)
40 self.current_frame_rolls = []
41 self.current_frame += 1
42 else: # Frame 9 (10th frame) - special rules
43 # Validate fill balls according to bowling rules
44 if len(self.current_frame_rolls) == 0:
45 # First roll - any value 0-10 is valid
46 pass
47 elif len(self.current_frame_rolls) == 1:
48 # Second roll validation
49 if self.current_frame_rolls[0] == 10: # First roll was strike
50 # After a strike, second roll can be 0-10
51 pass
52 else:
53 # After a non-strike first roll, second roll + first roll cannot exceed 10
54 if self.current_frame_rolls[0] + pins > 10:
55 raise ValueError("pin count exceeds pins on the lane")
56 elif len(self.current_frame_rolls) == 2:
57 # Third roll validation (fill ball)
58 if self.current_frame_rolls[0] == 10: # First roll was strike
59 if self.current_frame_rolls[1] == 10: # Second roll was also strike
60 # After two strikes, third roll can be 0-10
61 pass
62 else:
63 # After strike + non-strike, third roll + second roll cannot exceed 10
64 if self.current_frame_rolls[1] + pins > 10:
65 raise ValueError("invalid fill balls")
66 elif self.current_frame_rolls[0] + self.current_frame_rolls[1] == 10: # First two rolls were spare
67 # After a spare, third roll can be 0-10
68 pass
69
70 self.current_frame_rolls.append(pins)
71 self.rolls.append(pins)
72
73 # Check if 10th frame is complete
74 if len(self.current_frame_rolls) == 3: # Three rolls completed
75 self.frames.append(self.current_frame_rolls)
76 self.current_frame_rolls = []
77 self.current_frame += 1
78 self.is_finished = True
79 elif len(self.current_frame_rolls) == 2: # Two rolls completed
80 # Check if frame is open (less than 10 pins)
81 frame_sum = sum(self.current_frame_rolls)
82 if frame_sum < 10:
83 self.frames.append(self.current_frame_rolls)
84 self.current_frame_rolls = []
85 self.current_frame += 1
86 self.is_finished = True
87 # If it's a spare or strike, need third roll - continue
88 elif len(self.current_frame_rolls) == 1: # First roll completed
89 # If first roll is a strike, need two more rolls
90 # Just continue to next roll
91 pass
92
893 def score(self):
9 pass
94 # Edge Case: Game not finished yet
95 if not self.is_finished:
96 raise ValueError("Score cannot be taken until the end of the game")
97
98 total_score = 0
99
100 # Score all frames (0-9)
101 for frame_num in range(len(self.frames)):
102 frame = self.frames[frame_num]
103 frame_score = sum(frame)
104
105 if frame_num < 9: # Frames 0-8 (regular frames)
106 if len(frame) == 1 and frame[0] == 10: # Strike
107 # Add bonus: next two rolls
108 next_rolls_sum = 0
109 rolls_counted = 0
110
111 # Look for next rolls in subsequent frames
112 for next_frame_num in range(frame_num + 1, len(self.frames)):
113 next_frame = self.frames[next_frame_num]
114 for roll in next_frame:
115 next_rolls_sum += roll
116 rolls_counted += 1
117 if rolls_counted == 2:
118 break
119 if rolls_counted == 2:
120 break
121
122 frame_score += next_rolls_sum
123
124 elif len(frame) == 2 and sum(frame) == 10: # Spare
125 # Add bonus: next one roll
126 next_roll = 0
127
128 # Look for next roll in subsequent frames
129 for next_frame_num in range(frame_num + 1, len(self.frames)):
130 next_frame = self.frames[next_frame_num]
131 if len(next_frame) > 0:
132 next_roll = next_frame[0]
133 break
134
135 frame_score += next_roll
136
137 total_score += frame_score
138
139 return total_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.