Jay Grossman
Jay Grossman
Published on:

Scoring Bowling Game in Python

Scoring Bowling Game in Python

A few years back, I remember one of my former coworkers used to ask candidates to write a program to score a bowling game. I remember that it usually took folks a little bit to get all the pieces.


Jenna bowling a strike in her first match.

My 15 year old daughter Jenna recently starting bowling and joined her school's JV bowling team. She's been having a lot of fun and I've been enjoying watching her bowl. So I got inspired and thought it would be fun to write a script to complete my former coworker's challenge:

Rules for scoring a bowling match

Strike

If you knock down all 10 pins in the first shot of a frame, you get a strike.
How to score: A strike earns 10 points plus the sum of your next two shots.

Spare

If you knock down all 10 pins using both shots of a frame, you get a spare.
How to score: A spare earns 10 points plus the sum of your next one shot.

Open Frame

If you do not knock down all 10 pins using both shots of your frame (9 or fewer pins knocked down), you have an open frame.
How to score: An open frame only earns the number of pins knocked down.

The 10th Frame

The 10th frame is a bit different:
If you roll a strike in the first shot of the 10th frame, you get 2 more shots.
If you roll a spare in the first two shots of the 10th frame, you get 1 more shot.
If you leave the 10th frame open after two shots, the game is over and you do not get an additional shot.
How to score: The score for the 10th frame is the total number of pins knocked down in the 10th frame.

The assignment

You have the following array of data which represents a bowling match. The array is composed of 10 “frames”. Each frame has one or two elements corresponding to the number of pins knocked down in each shot. The array is structured as follows:

1
2
3
4
5
6
7
8
9
10
11
12
[
  [1, 9], # First frame
  [7, 3], # Second frame
  [8, 2], # Third frame
  [9, 1], # Fourth frame
  [8, 2], # Fifth frame
  [9, 0], # Sixth frame
  [9, 1], # Seventh frame
  [6, 4], # Eighth frame
  [7, 2], # Ninth frame
  [6, 2]  # Tenth frame
]

Write a function that calculates the score of a 10 frame bowling match given a two dimensional array representing the pins knocked down per frame.

The script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class BowlingGame:
    def __init__(self):
        self.rolls = []

    def roll(self, pins):
        self.rolls.append(pins)

    def score(self):
        total_score = 0
        roll_index = 0

        for _ in range(10):
            if self.rolls[roll_index] == 10:  # Strike
                total_score += 10 + self.rolls[roll_index + 1] + self.rolls[roll_index + 2]
                roll_index += 1
            elif self.rolls[roll_index] + self.rolls[roll_index + 1] == 10: 
             # Spare
                total_score += 10 + self.rolls[roll_index + 2]
                roll_index += 2
            else:
                total_score += self.rolls[roll_index] + self.rolls[roll_index + 1]
                roll_index += 2

        return total_score

Testing it works

First I created a few easy tests where all the rolls are the same. Then I used the first picture above that came from a recent session when I took Jenna bowling at a nearby bowling place (Bowlero).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class TestBowlingGame(unittest.TestCase):
    def setUp(self):
        self.game = BowlingGame()

    def roll_many(self, rolls, pins):
        for _ in range(rolls):
            self.game.roll(pins)

    def test_gutter_game(self):
        self.roll_many(20, 0)
        self.assertEqual(self.game.score(), 0)

    def test_all_ones_game(self):
        self.roll_many(20, 1)
        self.assertEqual(self.game.score(), 20)

    def test_all_fives_game(self):
        self.roll_many(21, 5)
        self.assertEqual(self.game.score(), 150)

    def test_perfect_game(self):
        self.roll_many(12, 10)
        self.assertEqual(self.game.score(), 300)

    def test_test_jennas_game(self):
        frames = [
            [1, 9], # First frame
            [7, 3], # Second frame
            [8, 2], # Third frame
            [9, 1], # Fourth frame
            [8, 2], # Fifth frame
            [9, 0], # Sixth frame
            [9, 1], # Seventh frame
            [6, 4], # Eighth frame
            [7, 2], # Ninth frame
            [6, 2]  # Tenth frame
        ]
        for frame in frames:
            for shot in frame:
                self.game.roll(shot)
        self.assertEqual(self.game.score(), 150)

if __name__ == '__main__':
    unittest.main(argv=[''], verbosity=2, exit=False)