Sunday, November 8, 2009

C# Bowling Kata

I figured that I would start this blog with my version of the bowling scoring kata, as done in C#. For those unfamiliar with it, you can read about it and see a quick presentation of it (in PowerPoint) at Robert C. Martin's site. For those unfamiliar with Bob Martin, his books Clean Code and Agile Principles, Patterns and Practices (available with Java examples as well, but I'm a .NET guy primarily these days) are well worth a read.

The main idea behind code katas is similar to martial arts katas, meaning they are exercises meant to be repeated in order to become better at certain concepts, motions and so on, until they're so ingrained that they're near instinctual.

I'm not totally sold on the concept when it comes to programming, though that may be short-sighted on my part. While doing this daily or weekly or however frequently I choose to practice it, I'm not sure it will make me much better at anything other than writing bowling scorers. The conceptual leaps necessary to write it are really only made the first time, and I'm not sure how well they translate to other problems. Regularly solving a variety of problems seem like better use of practice time, but as I said, I may be missing something.

I'm also planning on diving into Haskell this week to try a new language that works differently from what I'm used to, and I figured that small standardised exercises would be a good way of illustrating some conceptual differences between languages. So, now that I've done this in C#, look for a version in Haskell in the near future.

As a side note, this solution isn't perfect. There's a temporal dependency in the Score method and adding the first two balls of the current index will happen twice unless you roll a strike. But I didn't see a handy way fixing those without cluttering the code more than I thought it was worth.

For completeness, I made the scorer a private member of the test class, and extracted the ability to roll an arbitrary number of the same number of pins into its own method, as below.
private Scorer scorer = new Scorer();

[SetUp]
public void SetUp()
{
    scorer = new Scorer();
}

private void RollSamePinsForAllBalls(int pins, int rolls)
{
    for (var i = 0; i < rolls; i++)
        scorer.Roll(pins);
}
My first test was for a complete series of gutterballs with an expected final score of zero. I'd like to think that I am better bowler than that, but it seemed a good first case.
[Test]
public void Score_AllRollsAreZero_Zero()
{
    RollSamePinsForAllBalls(0, 20);
    var score = scorer.Score();
    Assert.AreEqual(0, score);
}
Making it pass was simple enough.
public int Score()
{
    return 0;
}

public void Roll(int roll)
{
            
}
Next, I figured I'd work on my precision rolling, knocking off only one pin per roll, with a matching test expecting a grand total of twenty points.
[Test]
public void Score_AllFramesAreOnes_Twenty()
{
    RollSamePinsForAllBalls(1, 20);
    var score = scorer.Score();
    Assert.AreEqual(20, score);
}
At this point, the Score method actually had to do some thinking, so I scored without any consideration for strikes and spares. My thought at this stage was that I'd be able to do this with some clever manipulation of the rolls list to figure out strike and spare scoring later.
private readonly IList<int> rolls = new List<int>();

public int Score
{
    var score = 0;

    foreach(var pins in rolls)
        score += pins;

    return score;
}

public void Roll(int pins)
{
    rolls.Add(pins);
}
So far so good. The next test case was my dream game, a perfect three hundred, rolling twelve strikes in a row.
[Test]
public void Score_AllRollsAreStrikes_ThreeHundred()
{
    RollSamePinsForAllBalls(10, 12);
    var score = scorer.Score();
    Assert.AreEqual(300, score);
}
This is where I first realized that trying to score a game based on patterns in the list of individual rolls just wasn't going to hold up. I would show the code, but after fixing it, I found myself having a hard time finding back to my original train of thought and reproducing it. Perhaps it suffices to say that it was an ugly exercise in index tracking and list manipulation that even in the end didn't give the right answers.

After a couple of attempts at parsing the list backwards, figuring out which index should be my start index and so on, I realized that a higher level of control was required. I switched over to counting frames and tracking the index based on the first ball of each frame, and the rest came together.
private readonly IList<int> rolls = new List<int>();

public int Score()
{
    var score = 0;
    var currentFirstBall = 0;

    for (var frame = 1; frame <= 10; frame++)
    {
        if(rolls[currentFirstBall] == 10)
        {
            score += rolls[currentFirstBall] + rolls[currentFirstBall + 1] 
                + rolls[currentFirstBall + 2];
            currentFirstBall++;
        }
        else
        {
            score += rolls[currentFirstBall] + rolls[currentFirstBall + 1];
            currentFirstBall += 2;
        }
    }

    return score;
}
It could use some refactoring, but it worked. Adding another case for spares was simple once the pattern was in place. Testing with all fives should give an all spares result.
[Test]
public void Score_AllRollsAreSpares_OneHundredFifty()
{
    RollSamePinsForAllBalls(5, 21);
    var score = scorer.Score();
    Assert.AreEqual(150, score);
}
private readonly IList<int> rolls = new List<int>();

public int Score()
{
    var score = 0;
    var currentFirstBall = 0;

    for (var frame = 1; frame <= 10; frame++)
    {
        if(rolls[currentFirstBall] == 10)
        {
            score += rolls[currentFirstBall] + rolls[currentFirstBall + 1] 
                + rolls[currentFirstBall + 2];
            currentFirstBall++;
        }
        else if(rolls[currentFirstBall] + rolls[currenFirstBall + 1] == 10)
        {
            score += rolls[currentFirstBall] + rolls[currentFirstBall + 1] 
                + rolls[currentFirstBall + 2];
            currentFirstBall += 2;
        }
        else
        {
            score += rolls[currentFirstBall] + rolls[currentFirstBall + 1];
            currentFirstBall += 2;
        }
    }

    return score;
}
Then I added a final bowled game that was a mix of strikes, spares and normal balls, mostly for my own feeling of security, which passed right away.
[Test]
public void Score_RollsAreMixed_OneHundredEightyFour()
{
    scorer.Roll(4);
    scorer.Roll(5);
    scorer.Roll(10);
    scorer.Roll(5);
    scorer.Roll(5);
    scorer.Roll(10);
    scorer.Roll(10);
    scorer.Roll(10);
    scorer.Roll(4);
    scorer.Roll(5);
    scorer.Roll(0);
    scorer.Roll(10);
    scorer.Roll(8);
    scorer.Roll(1);
    scorer.Roll(10);
    scorer.Roll(10);
    scorer.Roll(6);

    var score = scorer.Score();
    Assert.AreEqual(184, score);
}
All that remained after that was refactoring out redundancies and hopefully making the code more readable. I've added the complete test and scorer classes below.
using NUnit.Framework;

namespace BowlingKata
{
    [TestFixture]
    public class ScorerTest
    {
        private Scorer scorer = new Scorer();

        [SetUp]
        public void SetUp()
        {
            scorer = new Scorer();
        }

        private void RollSamePinsForAllBalls(int pins, int rolls)
        {
            for (var i = 0; i < rolls; i++)
                scorer.Roll(pins);
        }

        [Test]
        public void Score_AllRollsAreZero_Zero()
        {
            RollSamePinsForAllBalls(0, 20);
            var score = scorer.Score();
            Assert.AreEqual(0, score);
        }

        [Test]
        public void Score_AllFramesAreOnes_Twenty()
        {
            RollSamePinsForAllBalls(1, 20);
            var score = scorer.Score();
            Assert.AreEqual(20, score);
        }

        [Test]
        public void Score_AllRollsAreStrikes_ThreeHundred()
        {
            RollSamePinsForAllBalls(10, 12);
            var score = scorer.Score();
            Assert.AreEqual(300, score);
        }

        [Test]
        public void Score_AllRollsAreSpares_OneHundredFifty()
        {
            RollSamePinsForAllBalls(5, 21);
            var score = scorer.Score();
            Assert.AreEqual(150, score);
        }

        [Test]
        public void Score_RollsAreMixed_OneHundredEightyFour()
        {
            scorer.Roll(4);
            scorer.Roll(5);
            scorer.Roll(10);
            scorer.Roll(5);
            scorer.Roll(5);
            scorer.Roll(10);
            scorer.Roll(10);
            scorer.Roll(10);
            scorer.Roll(4);
            scorer.Roll(5);
            scorer.Roll(0);
            scorer.Roll(10);
            scorer.Roll(8);
            scorer.Roll(1);
            scorer.Roll(10);
            scorer.Roll(10);
            scorer.Roll(6);

            var score = scorer.Score();
            Assert.AreEqual(184, score);
        }
    }
}
using System.Collections.Generic;

namespace BowlingKata
{
    public class Scorer
    {
        private readonly IList<int> rolls = new List<int>();
        private int score;
        private int currentFirstBall;

        public void Roll(int roll)
        {
            rolls.Add(roll);
        }

        public int Score()
        {
            ScoreAllRolls();
            return score;
        }

        private void ScoreAllRolls()
        {
            for (var frame = 1; frame <= 10; frame++)
                ScoreOneFrame();
        }

        private void ScoreOneFrame()
        {
            if (IsStrike()) AddStrike();
            else if (IsSpare()) AddSpare();
            else AddNormalFrame();
        }

        private bool IsStrike()
        {
            return rolls[currentFirstBall] == 10;
        }

        private void AddStrike()
        {
            score += ScoreRolls(3);
            currentFirstBall++;
        }

        private bool IsSpare()
        {
            return ScoreRolls(2) == 10;
        }

        private void AddSpare()
        {
            score += ScoreRolls(3);
            currentFirstBall += 2;
        }

        private void AddNormalFrame()
        {
            score += ScoreRolls(2);
            currentFirstBall += 2;
        }

        private int ScoreRolls(int numberOfRolls)
        {
            var sumOfRolls = 0;
            for (var i = currentFirstBall; i < currentFirstBall + numberOfRolls; i++)
                sumOfRolls += rolls[i];

            return sumOfRolls;
        }
    }
}