Bowling Game Kata C#

Preview:

DESCRIPTION

The Bowling Game Kata using C# to demonstrate Test Driven Development.

Citation preview

ADAPTED FOR C# 4.0(With Visual Studio 2010)

By Dan Stewart

dan@stewshack.com

SCORING BOWLING.

The game consists of 10 frames as shown above. In each frame the player has two opportunities to knock down 10 pins. The score for the frame is the total number of pins knocked down, plus bonuses for strikes and spares.

A spare is when the player knocks down all 10 pins in two tries. The bonus for that frame is the number of pins knocked down by the next roll. So in frame 3 above, the score is 10 (the total number knocked down) plus a bonus of 5 (the number of pins knocked down on the next roll.)

A strike is when the player knocks down all 10 pins on his first try. The bonus for that frame is the value of the next two balls rolled.

In the tenth frame a player who rolls a spare or strike is allowed to roll the extra balls to complete the frame. However no more than three balls can be rolled in tenth frame.

A QUICK DESIGN SESSION

Clearly we need the Game class.

Game

+ roll(pins : int)+ score() : int

A QUICK DESIGN SESSION

A game has 10 frames.

Game 10 Frame

+ roll(pins : int)+ score() : int

A QUICK DESIGN SESSION

A frame has 1 or two rolls.

Game 10 Frame 1 ..2 Roll

+ roll(pins : int)+ score() : int

- pins : int

A QUICK DESIGN SESSION

The tenth frame has two or three rolls.It is different from all the other frames.

Game 10 Frame 1 ..2 Roll

+ roll(pins : int)+ score() : int

- pins : int

1

Tenth Frame

Game 10 Frame 1 ..2 Roll

+ roll(pins : int)+ score() : int

+ score : int - pins : int

1

Tenth Frame

A QUICK DESIGN SESSION

The score function mustinclude all the frames, and calculate all their scores.

A QUICK DESIGN SESSIONThe score for a spare or a strike depends on the frame’s successor

Next frame

Game 10 Frame 1 ..2 Roll

+ roll(pins : int)+ score() : int

+ score : int - pins : int

1

Tenth Frame

BEGIN.

Create a console application named BowlingGame

Add a MSTest project named BowlingGameTest to the solution

BEGIN.

Add a unit test named GameTest to the project

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BowlingGameTest{ [TestClass] public class GameTest { private TestContext testContextInstance; public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } }

[TestMethod] public void TestMethod1() { } }}

THE FIRST TEST. [TestMethod] public void TestGutterGame() { Game g = new Game(); }

THE FIRST TEST.namespace BowlingGame{ public class Game { }}

[TestMethod] public void TestGutterGame() { Game g = new Game(); }

THE FIRST TEST. [TestMethod] public void TestGutterGame() { Game g = new Game(); }

public class Game{}

THE FIRST TEST.[TestMethod]public void TestGutterGame(){ Game g = new Game();

for (int i = 0; i < 20; i++) { g.Roll(0); }}

public class Game{}

THE FIRST TEST.public class Game{ public void Roll(int p) { throw new System.NotImplementedException(); }}

[TestMethod]public void TestGutterGame(){ Game g = new Game();

for (int i = 0; i < 20; i++) { g.Roll(0); }}

THE FIRST TEST.[TestMethod]public void TestGutterGame(){ Game g = new Game();

for (int i = 0; i < 20; i++) { g.Roll(0); }

Assert.AreEqual(0, g.Score());}

public class Game{ public void Roll(int p) { throw new System.NotImplementedException(); }}

THE FIRST TEST.

Failed TestGutterGame threw exception

[TestMethod]public void TestGutterGame(){ Game g = new Game();

for (int i = 0; i < 20; i++) { g.Roll(0); }

Assert.AreEqual(0, g.Score());}

public class Game{ public void Roll(int p) { throw new System.NotImplementedException(); }

public object Score() { throw new System.NotImplementedException(); }}

THE FIRST TEST.[TestMethod]public void TestGutterGame(){ Game g = new Game();

for (int i = 0; i < 20; i++) { g.Roll(0); }

Assert.AreEqual(0, g.Score);}

public class Game{ private int score;

public void Roll(int pins) { }

public int Score() { return score; }}

THE SECOND TEST.[TestMethod]public void TestAllOnes(){ Game g = new Game(); for (int i = 0; i < 20; i++) { g.Roll(1); }

Assert.AreEqual(20, g.Score());}

public class Game{ private int score;

public void Roll(int pins) { }

public int Score() { return score; }}

THE SECOND TEST.- Game creation is duplicated- roll loop is duplicated

[TestMethod]public void TestAllOnes(){ Game g = new Game(); for (int i = 0; i < 20; i++) { g.Roll(1); }

Assert.AreEqual(20, g.Score());}

public class Game{ private int score;

public void Roll(int pins) { }

public int Score() { return score; }}

THE SECOND TEST.- Game creation is duplicated- roll loop is duplicated

Assert.AreEqual failed. Expected:<20>. Actual:<0>.

[TestMethod]public void TestAllOnes(){ Game g = new Game(); for (int i = 0; i < 20; i++) { g.Roll(1); }

Assert.AreEqual(20, g.Score());}

public class Game{ private int score;

public void Roll(int pins) { }

public int Score() { return score; }}

THE SECOND TEST.- roll loop is duplicated

private Game g;

[TestInitialize]public void Initialize() { g = new Game();}

[TestCleanup]public void Cleanup(){ g = null;}

[TestMethod]public void TestGutterGame(){ for (int i = 0; i < 20; i++) { g.Roll(0); }

Assert.AreEqual(0, g.Score());}

[TestMethod]public void TestAllOnes(){ for (int i = 0; i < 20; i++) { g.Roll(1); }

Assert.AreEqual(20, g.Score());}

public class Game{ private int score;

public void Roll(int pins) { score += pins; }

public int Score() { return score; }}

THE SECOND TEST.- roll loop is duplicated

[TestMethod]public void TestGutterGame(){ int rolls = 20; int pins = 0; for (int i = 0; i < rolls; i++) { g.Roll(pins); }

Assert.AreEqual(0, g.Score());}

public class Game{ private int score;

public void Roll(int pins) { score += pins; }

public int Score() { return score; }}

THE SECOND TEST.- roll loop is duplicated

[TestMethod]public void TestGutterGame(){ int rolls = 20; int pins = 0; RollMany(rolls, pins); for (int i = 0; i < rolls; i++) { g.Roll(pins); }

Assert.AreEqual(0, g.Score());}

public class Game{ private int score;

public void Roll(int pins) { score += pins; }

public int Score() { return score; }}

[TestMethod]public void TestGutterGame(){ RollMany(20, 0);

Assert.AreEqual(0, g.Score());}

private void RollMany(int rolls, int pins){ for (int i = 0; i < rolls; i++) { g.Roll(pins); }}

THE SECOND TEST.public class Game{ private int score;

public void Roll(int pins) { score += pins; }

public int Score() { return score; }}

THE SECOND TEST.[TestMethod]public void TestGutterGame(){ RollMany(20, 0);

Assert.AreEqual(0, g.Score());}

[TestMethod]public void TestAllOnes(){ RollMany(20, 1);

Assert.AreEqual(20, g.Score());}

private void RollMany(int rolls, int pins){ for (int i = 0; i < rolls; i++) { g.Roll(pins); }}

public class Game{ private int score;

public void Roll(int pins) { score += pins; }

public int Score() { return score; }}

THE THIRD TEST.- ugly comment in test.

[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0);

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

Failed Assert.AreEqual Expected:<16>. Actual:<13>

public class Game{ private int score;

public void Roll(int pins) { score += pins; }

public int Score() { return score; }}

public class Game{ private int score;

public void Roll(int pins) { score += pins; }

public int Score() { return score; }}

THE THIRD TEST.

tempted to use flag to remember previous roll. So design must be wrong.

- ugly comment in test.

[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0);

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

Failed Assert.AreEqual Expected:<16>. Actual:<13>

public class Game{ private int score;

public void Roll(int pins) { score += pins; }

public int Score() { return score; }}

THE THIRD TEST. Roll() calculates score, but name does not imply that.

Score() does not calculate score, but name implies that it does.

Design is wrong. Responsibilities are misplaced.

- ugly comment in test.

[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0);

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

Failed Assert.AreEqual Expected:<16>. Actual:<13>

THE THIRD TEST.[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); //Assert.AreEqual(16, g.Score()); Assert.Inconclusive();}

- ugly comment in test.

public class Game{ private int score;

public void Roll(int pins) { score += pins; }

public int Score() { return score; }}

THE THIRD TEST.public class Game{ private int score; private int[] rolls = new int[21]; private int currentRoll;

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

public int Score() { return score; }}

- ugly comment in test.

[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); //Assert.AreEqual(16, g.Score()); Assert.Inconclusive();}

THE THIRD TEST.public class Game{ private int[] rolls = new int[21]; private int currentRoll;

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

public int Score() { int score = 0;

for (int i = 0; i < rolls.Length; i++) { score += rolls[i]; }

return score; }}

- ugly comment in test.

[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); //Assert.AreEqual(16, g.Score()); Assert.Inconclusive();}

THE THIRD TEST.- ugly comment in test.

[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0);

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

public class Game{ private int[] rolls = new int[21]; private int currentRoll;

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

public int Score() { int score = 0;

for (int i = 0; i < rolls.Length; i++) { score += rolls[i]; }

return score; }}

Failed Assert.AreEqual Expected:<16>. Actual:<13>

public int Score(){ int score = 0;

for (int i = 0; i < rolls.Length; i++) { // spare if (rolls[i] + rolls[i+1] == 10) { score += ... }

score += rolls[i]; }

return score;}

THE THIRD TEST.

This isn’t going to work because i might not refer to the first ball of the frame.

Design is still wrong.

Need to walk through array two balls (one frame) at a time.

- ugly comment in test.

[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0);

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

Failed Assert.AreEqual Expected:<16>. Actual:<13>

public class Game{ private int[] rolls = new int[21]; private int currentRoll;

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

public int Score() { int score = 0;

for (int i = 0; i < rolls.Length; i++) { score += rolls[i]; }

return score; }}

THE THIRD TEST.- ugly comment in test.

[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0);

//Assert.AreEqual(16, g.Score()); Assert.Inconclusive();}

THE THIRD TEST.public int Score(){ int score = 0; int roll = 0;

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

return score;}

- ugly comment in test.

[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); //Assert.AreEqual(16, g.Score()); Assert.Inconclusive();}

THE THIRD TEST.- ugly comment in test.

[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0);

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

public int Score(){ int score = 0; int roll = 0;

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

return score;}

Failed Assert.AreEqual Expected:<16>. Actual:<13>

THE THIRD TEST.public int Score(){ int score = 0; int roll = 0;

for (int frame = 0; frame < 10; frame++) { // spare if (rolls[roll] + rolls[roll + 1] == 10) { score += 10 + rolls[roll + 2]; } else { score += rolls[roll] + rolls[roll + 1]; }

roll += 2; }

return score;}

- ugly comment in test.

[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0);

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

public int Score(){ int score = 0; int roll = 0;

for (int frame = 0; frame < 10; frame++) { // spare if (rolls[roll] + rolls[roll + 1] == 10) { score += 10 + rolls[roll + 2]; } else { score += rolls[roll] + rolls[roll + 1]; }

roll += 2; }

return score;}

THE THIRD TEST.- ugly comment in test.- ugly comment in

conditional.

[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0);

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

public int Score(){ int score = 0; int roll = 0;

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

roll += 2; }

return score;}

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

THE THIRD TEST.- ugly comment in test.

[TestMethod]public void TestOneSpare(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0);

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

THE THIRD TEST.[TestMethod]public void TestOneSpare(){ RollSpare(); g.Roll(3); RollMany(17, 0);

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

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

public int Score(){ int score = 0; int roll = 0;

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

roll += 2; }

return score;}

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

THE FOURTH TEST.- ugly comment in testOneStrike.

[TestMethod]public void TestOneStrike(){ g.Roll(10); // strike g.Roll(3); g.Roll(4); RollMany(16, 0); Assert.AreEqual(24, g.Score());}

public int Score(){ int score = 0; int roll = 0;

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

roll += 2; }

return score;}

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

Failed Assert.AreEqual Expected:<24>. Actual:<17>

THE FOURTH TEST.- ugly comment in

testOneStrike.- ugly comment in

conditional.- ugly expressions. public int Score()

{ int score = 0; int roll = 0;

for (int frame = 0; frame < 10; frame++) { if (rolls[roll] == 10) // strike { score += 10 + rolls[roll + 1] + rolls[roll + 2]; roll++; } else if (IsSpare(roll)) { score += 10 + rolls[roll + 2]; roll += 2; } else { score += rolls[roll] + rolls[roll + 1]; roll += 2; } }

return score;}

[TestMethod]public void TestOneStrike(){ g.Roll(10); // strike g.Roll(3); g.Roll(4); RollMany(16, 0); Assert.AreEqual(24, g.Score());}

THE FOURTH TEST.- ugly comment in

testOneStrike.- ugly comment in

conditional. public int Score(){ int score = 0; int roll = 0;

for (int frame = 0; frame < 10; frame++) { if (rolls[roll] == 10) // strike { score += 10 + StrikeBonus(roll); roll++; } else if (IsSpare(roll)) { score += 10 + SpareBonus(roll); roll += 2; } else { score += SumOfBallsInFrame(roll); roll += 2; } }

return score;}

private int SumOfBallsInFrame(int roll){ return rolls[roll] + rolls[roll + 1];}

private int SpareBonus(int roll){ return rolls[roll + 2];}

private int StrikeBonus(int roll){ return rolls[roll + 1] + rolls[roll + 2];}

[TestMethod]public void TestOneStrike(){ g.Roll(10); // strike g.Roll(3); g.Roll(4); RollMany(16, 0); Assert.AreEqual(24, g.Score());}

THE FOURTH TEST.public int Score(){ int score = 0; int roll = 0;

for (int frame = 0; frame < 10; frame++) { if (IsStrike(roll)) { score += 10 + StrikeBonus(roll);

roll++; } else if (IsSpare(roll)) { score += 10 + SpareBonus(roll); roll += 2;

} else { score += SumOfBallsInFrame(roll); roll += 2; } }

return score;}

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

- ugly comment in testOneStrike.

[TestMethod]public void TestOneStrike(){ g.Roll(10); // strike g.Roll(3); g.Roll(4); RollMany(16, 0); Assert.AreEqual(24, g.Score());}

THE FOURTH TEST.[TestMethod]public void TestOneStrike(){ RollStrike(); g.Roll(3); g.Roll(4); RollMany(16, 0); Assert.AreEqual(24, g.Score());}

private void RollStrike(){ g.Roll(10);}

public int Score(){ int score = 0; int roll = 0;

for (int frame = 0; frame < 10; frame++) { if (IsStrike(roll)) { score += 10 + StrikeBonus(roll);

roll++; } else if (IsSpare(roll)) { score += 10 + SpareBonus(roll); roll += 2;

} else { score += SumOfBallsInFrame(roll); roll += 2; } }

return score;}

THE FIFTH TEST.[TestMethod]public void TestPerfectGame(){ RollMany(12, 10); Assert.AreEqual(300, g.Score());}

public int Score(){ int score = 0; int roll = 0;

for (int frame = 0; frame < 10; frame++) { if (IsStrike(roll)) { score += 10 + StrikeBonus(roll);

roll++; } else if (IsSpare(roll)) { score += 10 + SpareBonus(roll); roll += 2;

} else { score += SumOfBallsInFrame(roll); roll += 2; } }

return score;}

QUESTIONS?

Recommended