In this blog we will continue testing our Scorer class by adding Spares and Strikes.
Adding Spares
Looking at the rules for spares. If we knock down 10 pins with 2 bowls, we add 10 plus the value of the next bowl. Here is our failing test.
[Test] public void CalculateScoreWithSpares() { _scorer.FrameScore(0, 0); Assert.That(_scorer.Score, Is.EqualTo(0)); _scorer.FrameScore(4, 6); Assert.That(_scorer.Score, Is.EqualTo(10)); _scorer.FrameScore(5, 4); Assert.That(_scorer.Score, Is.EqualTo(24)); }
We need to update our Score property to cater for Spares. Having all these tests assures us that when we change something, we don’t break something else.
public int Score { get { int _currentScore = 0; bool _spare = false; foreach( var _score in _frameScores) { var _bowl1 = _score.Item1; var _bowl2 = _score.Item2; if (_spare) { _currentScore += _bowl1; _spare = false; } if (_bowl1 + _bowl2 == 10 && _bowl1 != 10) _spare = true; _currentScore += _bowl1; _currentScore += _bowl2; } return _currentScore; } }
Running the test now returns success.
Let’s extend the test with more scores.
[Test] public void CalculateScoreWithSpares() { _scorer.FrameScore(0, 0); Assert.That(_scorer.Score, Is.EqualTo(0)); _scorer.FrameScore(4, 6); Assert.That(_scorer.Score, Is.EqualTo(10)); _scorer.FrameScore(5, 4); Assert.That(_scorer.Score, Is.EqualTo(24)); _scorer.FrameScore(5, 5); Assert.That(_scorer.Score, Is.EqualTo(34)); _scorer.FrameScore(9, 0); Assert.That(_scorer.Score, Is.EqualTo(52)); }
Tests still passes.
Refactoring Adding a Spare
Let’s add a method for adding a spare which takes one parameter. This is the value of the first bowl since we can calculate the value of the second bowl ourselves.
public void FrameSpare(int Bowl1) { FrameScore(Bowl1, 10 - Bowl1); }
Now we can replace some of our FrameScore calls with FrameSpare calls and our tests still pass.
[Test] public void CalculateScoreWithSpares() { _scorer.FrameScore(0, 0); Assert.That(_scorer.Score, Is.EqualTo(0)); _scorer.FrameSpare(4); Assert.That(_scorer.Score, Is.EqualTo(10)); _scorer.FrameScore(5, 4); Assert.That(_scorer.Score, Is.EqualTo(24)); _scorer.FrameSpare(5); Assert.That(_scorer.Score, Is.EqualTo(34)); _scorer.FrameScore(9, 0); Assert.That(_scorer.Score, Is.EqualTo(52)); }
Adding Strikes
Looking at the rules for strikes. If we knock down 10 pins with the first bowl, we add 10 plus the value of the next 2 bowls. Here is our failing test.
[Test] public void CalculateScoreWithStrikes() { _scorer.FrameScore(0, 0); Assert.That(_scorer.Score, Is.EqualTo(0)); _scorer.FrameStrike(); Assert.That(_scorer.Score, Is.EqualTo(10)); _scorer.FrameScore(5, 4); Assert.That(_scorer.Score, Is.EqualTo(27)); _scorer.FrameStrike(); Assert.That(_scorer.Score, Is.EqualTo(37)); _scorer.FrameScore(9, 0); Assert.That(_scorer.Score, Is.EqualTo(55)); }
Let’s add the code for a strike.
public int Score { get { int _currentScore = 0; bool _spare = false; bool _strike = false; foreach ( var _score in _frameScores) { var _bowl1 = _score.Item1; var _bowl2 = _score.Item2; if (_spare) { _currentScore += _bowl1; _spare = false; } else if (_strike) { _currentScore += _bowl1; _currentScore += _bowl2; _strike = false; } if (_bowl1 + _bowl2 == 10 && _bowl1 != 10) _spare = true; else if (_bowl1 + _bowl2 == 10 && _bowl1 == 10) _strike = true; _currentScore += _bowl1; _currentScore += _bowl2; } return _currentScore; } }
All the tests pass.
Testing the Perfect Game
The perfect game returns 300 points which is 12 strikes in a row. Let’s add a test.
[Test] public void PerfectGameReturns300() { for (int _bowl = 1; _bowl <= 12; _bowl++) _scorer.FrameStrike(); Assert.That(_scorer.Score, Is.EqualTo(300)); }
Running the test has returned a failure. We expected 300 however; 230 was returned. We need to review our code.
It turns out that our logic was wrong for calculating the score. This happens which is why we write tests. In fact I had to completely rewrite the Score method however; having the other tests in place helped knowing the refactoring of the method worked and did not break anything.
This also highlights another useful fact. If you don’t have good quality tests that cover all use cases, there is no guarantee bugs will not appear in production. Here is the refactored method.
public int Score { get { // Set Framecount to 10 if the 10th Frame had a strike or spare if (_frameCount > 10) _frameCount = 10; Tuple _thisFramePlus1 = null; Tuple _thisFramePlus2 = null; int _currentScore = 0; for (int _bowl = 0; _bowl < _frameCount; _bowl++) { var _currentFrame = _bowl + 1; var _thisFrame = _frameScores[_bowl]; if (_currentFrame == _frameScores.Count) { _thisFramePlus1 = new Tuple(0, 0); _thisFramePlus2 = new Tuple(0, 0); } else if (_currentFrame == _frameScores.Count - 1) { _thisFramePlus1 = _frameScores[_bowl + 1]; _thisFramePlus2 = new Tuple(0, 0); } else { _thisFramePlus1 = _frameScores[_bowl + 1]; _thisFramePlus2 = _frameScores[_bowl + 2]; } var _bowl1 = _thisFrame.Item1; var _bowl2 = _thisFrame.Item2; if (_bowl1 == 10 && _thisFramePlus1.Item1 == 10) { _currentScore += 10 + 10 + _thisFramePlus2.Item1; continue; } if (_bowl1 == 10 && _thisFramePlus1.Item1 != 10) { _currentScore += 10 + _thisFramePlus1.Item1 + _thisFramePlus1.Item2; continue; } if (_bowl1 + _bowl2 == 10) { _currentScore += 10 + _thisFramePlus1.Item1; continue; } _currentScore += _bowl1; _currentScore += _bowl2; } return _currentScore; } }
Now all tests pass.
In the last blog I will finish up the last few tests.