Bowling Game Kata in C# Adapted

Preview:

DESCRIPTION

 

Citation preview

Bowling Game KataAdapted for C# and nUnitBy Dan Stewart (with modifications by Mike

Clement)dan@stewshack.com

mike@softwareontheside.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 class library named BowlingGameKata

Begin.

• Add a test fixture named BowlingGameTests to the projectusing NUnit.Framework;

namespace BowlingGameKata{ [TestFixture] public class BowlingGameTests { [Test] public void GutterGameTest() { } }}

The first test. [Test] public void GutterGameTest() { Game g = new Game(); }

The first test.namespace BowlingGameKata{ public class Game { }}

[Test] public void GutterGameTest() { Game g = new Game(); }

The first test. [Test] public void GutterGameTest() { Game g = new Game(); }

public class Game{}

The first test.[Test]public void GutterGameTest(){ 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(); }}

[Test]public void GutterGameTest(){ Game g = new Game();

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

The first test.[Test]public void GutterGameTest(){ Game g = new Game();

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

Assert.That(g.Score(), Is.EqualTo(0));}

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

The first test.

Failed GutterGameTest threw exception

[Test]public void GutterGameTest(){ Game g = new Game();

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

Assert.That(g.Score(), Is.EqualTo(0));}

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

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

The first test.[Test]public void GutterGameTest(){ Game g = new Game();

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

Assert.That(g.Score(), Is.EqualTo(0));}

public class Game{ private int score;

public void Roll(int pins) { }

public int Score() { return score; }}

The Second test.[Test]public void AllOnesTest(){ Game g = new Game(); for (int i = 0; i < 20; i++) { g.Roll(1); }

Assert.That(g.Score(), Is.EqualTo(20));}

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

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

Assert.That(g.Score(), Is.EqualTo(20));}

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>.

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

Assert.That(g.Score(), Is.EqualTo(20));}

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;

[SetUp]public void Setup() { g = new Game();}

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

Assert.That(g.Score(), Is.EqualTo(0));}

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

Assert.That(g.Score(), Is.EqualTo(20));}

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

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

Assert.That(g.Score(), Is.EqualTo(0));}

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

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

Assert.That(g.Score(), Is.EqualTo(0));}

public class Game{ private int score;

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

public int Score() { return score; }}

[Test]public void GutterGameTest(){ RollMany(20, 0);

Assert.That(g.Score(), Is.EqualTo(0));}

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.[Test]public void GutterGameTest(){ RollMany(20, 0);

Assert.That(g.Score(), Is.EqualTo(0));}

[Test]public void AllOnesTest(){ RollMany(20, 1);

Assert.That(g.Score(), Is.EqualTo(20));}

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.

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

Assert.That(g.Score(), Is.EqualTo(16));}

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.

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

Assert.That(g.Score(), Is.EqualTo(16));}

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.

[Test]public void OneSpareTest(){ 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.[Test]public void OneSpareTest(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); //Assert.That(g.Score(), Is.EqualTo(16));}

- 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.

[Test]public void OneSpareTest(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); //Assert.That(g.Score(), Is.EqualTo(16));}

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.

[Test]public void OneSpareTest(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); //Assert.That(g.Score(), Is.EqualTo(16));}

The Third test.- ugly comment in test.

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

Assert.That(g.Score(), Is.EqualTo(16));}

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.

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

Assert.That(g.Score(), Is.EqualTo(16));}

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.

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

//Assert.That(g.Score(), Is.EqualTo(16)); Assert.Inconclusive();}

The Third test.public int Score(){ int score = 0; int i = 0;

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

return score;}

- ugly comment in test.

[Test]public void OneSpareTest(){ g.Roll(5); g.Roll(5); // spare g.Roll(3); RollMany(17, 0); //Assert.That(g.Score(), Is.EqualTo(16)); Assert.Inconclusive();}

The Third test.- ugly comment in test.

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

Assert.That(g.Score(), Is.EqualTo(16));}

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

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

return score;}

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

The Third test.public int Score(){ int score = 0; int i = 0;

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

return score;}

- ugly comment in test.

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

Assert.That(g.Score(), Is.EqualTo(16));}

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]; roll += 2; } else { score += rolls[roll] + rolls[roll + 1]; roll += 2; } }

return score;}

The Third test.- ugly comment in test.- ugly comment in

conditional.

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

Assert.That(g.Score(), Is.EqualTo(16));}

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

for (int frame = 0; frame < 10; frame++) { if (IsSpare(roll)) { score += 10 + rolls[roll + 2]; 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.

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

Assert.That(g.Score(), Is.EqualTo(16));}

The Third test.[Test]public void OneSpareTest(){ RollSpare(); g.Roll(3); RollMany(17, 0);

Assert.That(g.Score(), Is.EqualTo(16));}

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]; 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 OneStrikeTest.

[Test]public void OneStrikeTest(){ g.Roll(10); // strike g.Roll(3); g.Roll(4); RollMany(16, 0); Assert.That(g.Score(), Is.EqualTo(24));}

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

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

[Test]public void OneStrikeTest(){ g.Roll(10); // strike g.Roll(3); g.Roll(4); RollMany(16, 0); Assert.That(g.Score(), Is.EqualTo(24));}

The Fourth test.- ugly comment in OneStrikeTest.

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

[Test]public void OneStrikeTest(){ g.Roll(10); // strike g.Roll(3); g.Roll(4); RollMany(16, 0); Assert.That(g.Score(), Is.EqualTo(24));}

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 OneStrikeTest.

[Test]public void OneStrikeTest(){ g.Roll(10); // strike g.Roll(3); g.Roll(4); RollMany(16, 0); Assert.That(g.Score(), Is.EqualTo(24));}

The Fourth test.[Test]public void OneStrikeTest(){ RollStrike(); g.Roll(3); g.Roll(4); RollMany(16, 0); Assert.That(g.Score(), Is.EqualTo(24));}

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.[Test]public void PerfectGameTest(){ RollMany(12, 10); Assert.That(g.Score(), Is.EqualTo(300));}

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

Discussion/Review

Adapted from:

Recommended