49

Bowling Game Kata C#

Embed Size (px)

DESCRIPTION

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

Citation preview

Page 1: Bowling Game Kata C#
Page 2: Bowling Game Kata C#

ADAPTED FOR C# 4.0(With Visual Studio 2010)

By Dan Stewart

[email protected]

Page 3: Bowling Game Kata C#

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.

Page 4: Bowling Game Kata C#

A QUICK DESIGN SESSION

Clearly we need the Game class.

Game

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

Page 5: Bowling Game Kata C#

A QUICK DESIGN SESSION

A game has 10 frames.

Game 10 Frame

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

Page 6: Bowling Game Kata C#

A QUICK DESIGN SESSION

A frame has 1 or two rolls.

Game 10 Frame 1 ..2 Roll

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

- pins : int

Page 7: Bowling Game Kata C#

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

Page 8: Bowling Game Kata C#

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.

Page 9: Bowling Game Kata C#

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

Page 10: Bowling Game Kata C#

BEGIN.

Create a console application named BowlingGame

Add a MSTest project named BowlingGameTest to the solution

Page 11: Bowling Game Kata C#

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() { } }}

Page 12: Bowling Game Kata C#

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

Page 13: Bowling Game Kata C#

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

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

Page 14: Bowling Game Kata C#

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

public class Game{}

Page 15: Bowling Game Kata C#

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

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

public class Game{}

Page 16: Bowling Game Kata C#

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); }}

Page 17: Bowling Game Kata C#

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(); }}

Page 18: Bowling Game Kata C#

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(); }}

Page 19: Bowling Game Kata C#

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; }}

Page 20: Bowling Game Kata C#

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; }}

Page 21: Bowling Game Kata C#

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; }}

Page 22: Bowling Game Kata C#

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; }}

Page 23: Bowling Game Kata C#

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; }}

Page 24: Bowling Game Kata C#

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; }}

Page 25: Bowling Game Kata C#

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; }}

Page 26: Bowling Game Kata C#

[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; }}

Page 27: Bowling Game Kata C#

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; }}

Page 28: Bowling Game Kata C#

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; }}

Page 29: Bowling Game Kata C#

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>

Page 30: Bowling Game Kata C#

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>

Page 31: Bowling Game Kata C#

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; }}

Page 32: Bowling Game Kata C#

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();}

Page 33: Bowling Game Kata C#

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();}

Page 34: Bowling Game Kata C#

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>

Page 35: Bowling Game Kata C#

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>

Page 36: Bowling Game Kata C#

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();}

Page 37: Bowling Game Kata C#

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();}

Page 38: Bowling Game Kata C#

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>

Page 39: Bowling Game Kata C#

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());}

Page 40: Bowling Game Kata C#

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());}

Page 41: Bowling Game Kata C#

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());}

Page 42: Bowling Game Kata C#

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;}

Page 43: Bowling Game Kata C#

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>

Page 44: Bowling Game Kata C#

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());}

Page 45: Bowling Game Kata C#

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());}

Page 46: Bowling Game Kata C#

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());}

Page 47: Bowling Game Kata C#

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;}

Page 48: Bowling Game Kata C#

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;}

Page 49: Bowling Game Kata C#

QUESTIONS?