Bowling Game Kata Part Four - The Spare Game

If bowling was scored just by knocking down pins, our job would be easy. The first special case is the game where you knock down a few pins on your first throw and then knock down the rest on the next throw. This is called a "spare".

Spare

Spare box When you make a spare, you darken half the score box. You also get a bonus. You take the 10 pins you knocked down in the frame and add the amount of the first roll in the next frame.

In our example above: 10 + 3 = 13. The rest of the game is scored normally.

BowlingGame.Test/GameTest.cs
[Test]
public void Game_Spare_ReturnsScorePlusBonus()
{
    game.Roll(5);
    game.Roll(5); // spare
    game.Roll(3);
    RollMany(17, 0);

    Assert.AreEqual(16,
    game.Score());
}

Let's compile and run the test.

1 test failed "Expected 16 but was 13"

We didn't get our bonus. Our code has no concept of keeping track of frames and adding a bonus to an earlier score.

We need to fix this, but we don't want to break our earlier code. Let's make this test assert inconclusive with a message. That way we can check-in our code, leave work for the day, and not break the build.

BowlingGame.Test/GameTest.cs
[Test]
public void Game_Spare_ReturnsScorePlusBonus()
{
    game.Roll(5);
    game.Roll(5); // spare
    game.Roll(3);
    RollMany(17, 0);

    //Assert.AreEqual(16, game.Score());
    Assert.Inconclusive("A game with a spare is not giving the bonus.");
}

Now when we run the tests we see yellow.

Inconclusive: A game with a spare is not giving the bonus.

Right now our Game is using a variable to hold the score. This score is now conditional on the need to add a bonus. Looking at Uncle Bob's Priority Premise the next step after the use of a variable is an unconditional statement.

Let's start by adding the unconditional statement of keeping track of every roll in every frame.

BowlingGame/Game.cs
namespace BowlingGame
{
    public class Game
    {
        private int score;
        private int[] rolls = new int[21];
        private int currentRoll;

        public void Roll(int pins)
        {
            rolls[currentRoll] = pins;
            currentRoll++;
        }

        public int Score()
        {
            int roll = 0;
            for (int frame = 0; frame < 10; frame++)
            {
                score += rolls[roll] + rolls[roll + 1];
                roll += 2;
            }

            return score;
        }
    }
}

This code passes our existing tests, but it still fails to give a bonus to a spare.

The next step in Uncle Bob's Priority Premise is an "if" statement. We wrote unconditional statements in the form of for loops, now we need a conditional statement to determine if the frame had a spare.

BowlingGame/Game.cs
public int Score()
{
    int roll = 0;
    for (int frame = 0; frame < 10; frame++)
    {
        if (rolls[roll] + rolls[roll + 1] == 10)
        {
            score += 10 + rolls[roll + 2];
        }
        else
        {
            score += rolls[roll] + rolls[roll + 1];
        }
        roll += 2;
    }

    return score;
}

Now we take out the inconclusive assertion and run the tests.

BowlingGame.Test/GameTest.cs
[Test]
public void Game_Spare_ReturnsScorePlusBonus()
{
    game.Roll(5);
    game.Roll(5); // spare
    game.Roll(3);
    RollMany(17, 0);

    Assert.AreEqual(16, game.Score());
}

3 tests passed

Our tests are passing, it's time to refactor.

The line "if (rolls[roll] + rolls[roll + 1] == 10)" means we rolled a spare. Let's extract a method that tells us this.

BowlingGame/Game.cs
public int Score()
{
    int roll = 0;
    for (int frame = 0; frame < 10; frame++)
    {
        if (Spare(roll))
        {
            score += 10 + rolls[roll + 2];
        }
        else
        {
            score += rolls[roll] + rolls[roll + 1];
        }
        roll += 2;
    }

    return score;
}

private bool Spare(int roll)
{
    return rolls[roll] + rolls[roll + 1] == 10;
}

We compile and run the after refactoring.

3 tests passed

Looking at our test we see that these two lines simulate rolling a spare:

game.Roll(5);
game.Roll(5);

Again, we extract these two lines out to a method.

BowlingGame.Test/GameTest.cs
[Test]
public void Game_Spare_ReturnsScorePlusBonus()
{
    RollSpare();
    game.Roll(3);
    RollMany(17, 0);

    Assert.AreEqual(16,
    game.Score());
}

private void RollSpare()
{
    game.Roll(5);
    game.Roll(5);
}