Upload
mike-clement
View
3.776
Download
3
Tags:
Embed Size (px)
DESCRIPTION
Citation preview
Bowling Game KataAdapted for C# and nUnitBy Dan Stewart (with modifications by Mike
Clement)[email protected]
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: