Adventures in C#: The Bowling Game
When I demonstrate Test-Driven Development using the Bowling Game example, I begin by describing the problem and inviting the attendees to do a little up front design about what objects we may need. Then I take a very simple approach that produces a rather simple single-class solution, with none of the complexity we anticipated. I’ve been wondering how to drive the development to cause the creation of some of the classes that are anticipated, supposing that we might have some actual need for them. Here’s an example of doing TDD with a bit bigger “design” in mind.
##The Story
We are going to create an object, BowlingGame, which, given a valid sequence of rolls for one line of American Ten-Pin Bowling, produces the total score for the game. This story is picked because it's about the right size for a couple of hours of Test-Driven Development demonstration. Here are some things that the program will not do:
- We will not check for valid rolls.
- We will not check for correct number of rolls and frames.
- We will not provide scores for intermediate frames.
Depending on the application, this might or might not be a valid way to define a complete story, but we do it here for purposes of keeping the demonstration light. I think you'll see that improvements like those above would go in readily if they were needed.
I'll briefly summarize the scoring for this form of bowling:
- Each game, or "line" of bowling, includes ten turns, or "frames" for the bowler.
- In each frame, the bowler gets up to two tries to knock down all the pins.
- If in two tries, he fails to knock them all down, his score for that frame is the total number of pins knocked down in his two tries.
- If in two tries he knocks them all down, this is called a "spare" and his score for the frame is ten plus the number of pins knocked down on his next throw (in his next turn).
- If on his first try in the frame he knocks down all the pins, this is called a "strike". His turn is over, and his score for the frame is ten plus the simple total of the pins knocked down in his next two rolls.
- If he gets a spare or strike in the last (tenth) frame, the bowler gets to throw one or two more bonus balls, respectively. These bonus throws are taken as part of the same turn. If the bonus throws knock down all the pins, the process does not repeat: the bonus throws are only used to calculate the score of the final frame.
- The game score is the total of all frame scores.
What makes this game interesting to score is the lookahead in the scoring for strike and spare. At the time we throw a strike or spare, we cannot calculate the frame score: we have to wait one or two frames to find out what the bonus is.
That should be all you need to know about the game. If, after reading this article, you remain confused, please let me know via email so that I can improve the writeup.
##The Design Idea
It seems to me that there are three main cases for each bowling Frame: the Strike, where the bowler knocks down all pins in one try, and gets ten points plus the sum of the next two throws; the Spare, where the bowler knocks down all ten pins in two tries, and gets ten points plus the value of the next throw; and the Open, where in two tries the bowler knocks down less than ten pins.
It seems like these Frame objects would each have a very simple Score() method, and that there would be no conditionals in the scoring code. One issue, however, is that all but the Open Frame need access to pin counts that are properly in a subsequent Frame. I feel sure that I could /design/ how this might work, but I want to see if I can build it in a test-driven fashion.
##And We’re Off!
I'll start by writing tests that say what kind of Frame is needed. Here goes. I'll begin with my standard test. The Hookup() method is used to make sure that I've got NUnit referenced properly:
using System; using NUnit.Framework; namespace BowlingWithFrame { [TestFixture] public class BowlingTest: Assertion { public BowlingTest() { } [SetUp] public void SetUp() { } [Test] public void Hookup() { Assert(true); } } }
In practically no time at all, I have this first test running. I'll continue with my usual first bowling test, all gutter balls. This time I will use a notation that mentions what kind of Frame it is:
[Test] public void GutterBalls() { BowlingGame game = new BowlingGame(); for ( int frameNumber = 0; frameNumber < 10; frameNumber++) game.OpenFrame(0,0); AssertEquals(0, game.Score()); }
This will be enough to make us implement some code, in the "Fake It Until You Make It" style:
using System; namespace BowlingWithFrame { public class BowlingGame { public BowlingGame() { } public void OpenFrame(int firstThrow, int secondThrow) { } public int Score() { return 0; } } }
Sure enough, the tests are green. Now for something completely different, bowling all threes:
[Test] public void Threes() { BowlingGame game = new BowlingGame(); for ( int frameNumber = 0; frameNumber < 10; frameNumber++) game.OpenFrame(3,3); AssertEquals(60, game.Score()); }
Not too surprisingly, this doesn't work. I notice some duplication now that I'd like to get rid of, but we're on a red bar, so first we have to make this work. The simplest thing that could possibly work, I suppose, is to create a list of throws and have OpenFrame add to it. But I'm working toward these Frame objects, so I'm going to push in that direction. This is a violation of the rules -- the code isn't really calling very loudly, if at all, for this step. In fact, having realized this, I'll take a simpler step: I'll build the list of scores, and then after I'm green, refactor to create the Frame objects. Here's the simple solution::
public class BowlingGame { ArrayList throws; public BowlingGame() { throws = new ArrayList(); } public void OpenFrame(int firstThrow, int secondThrow) { throws.Add(firstThrow); throws.Add(secondThrow); } public int Score() { int total = 0; foreach (int pins in throws) total += pins; return total; } }
The tests are green. Now we can refactor. I'm thinking that I want an ArrayList of OpenFrames, each containing two throws. We refactor:
public class BowlingGame { ArrayList frames; public BowlingGame() { frames = new ArrayList(); } public void OpenFrame(int firstThrow, int secondThrow) { frames.Add(new OpenFrame(firstThrow, secondThrow)); } public int Score() { int total = 0; foreach (OpenFrame frame in frames) total += frame.Score(); return total; } }
And we drive quickly to this definition of OpenFrame:
public class OpenFrame { int score; public OpenFrame(int firstThrow, int secondThrow) { score = firstThrow + secondThrow; } public int Score() { return score; } }
This actually works. You might note that I pre-calculated the score rather than store the two pins. I might have done the latter but actually chose to precalculate because it is simpler, and because it throws away the individual pin values, which I expect to cause trouble right away, when I work on SpareFrame with my next test. First, though, I want to get rid of some duplication in the tests:
[TestFixture] public class BowlingTest: Assertion { BowlingGame game; public BowlingTest() { } [SetUp] public void SetUp() { game = new BowlingGame(); } [Test] public void Hookup() { Assert(true); } [Test] public void GutterBalls() { ManyOpenFrames(10, 0, 0); AssertEquals(0, game.Score()); } [Test] public void Threes() { ManyOpenFrames(10, 3, 3); AssertEquals(60, game.Score()); } private void ManyOpenFrames(int count, int firstThrow, int secondThrow) { for ( int frameNumber = 0; frameNumber < count; frameNumber++) game.OpenFrame(firstThrow, secondThrow); } }
Now for a test driving us to SpareFrame():
[Test] public void Spare() { game.Spare(4,6); game.OpenFrame(3,5); ManyOpenFrames(8,0,0); AssertEquals( 21, game.Score()); }
I can foresee that this will cause me to build a SpareFrame, and an interface to allow SpareFrame and OpenFrame to both be sent Score(). And it will face me with the problem of getting access to the throws in a subsequent Frame. That seems daunting, but I have an idea. Let's code a bit, since we have a red bar:
public class SpareFrame { int score; public SpareFrame(int firstBall, int secondBall) { score = 10 + NextBall(); } private int NextBall() { return 3; } public int Score() { return score; } }
That gives us our SpareFrame, which we access with the new Spare() method on Bowling Game:
public void Spare(int firstThrow, int secondThrow) { frames.Add(new SpareFrame(firstThrow, secondThrow)); }
You'll note that I used Fake It on the NextBall() function, just to keep going. The code doesn't run, though, because the cast to OpenFrame doesn't work for the SpareFrame. We need to give them a common interface.
public interface IFrame { int Score(); }
And reference the interface where we need to:
public class SpareFrame : IFrame { ... public class OpenFrame : IFrame { ... public int Score() { int total = 0; foreach (IFrame frame in frames) total += frame.Score(); return total; }
The tests run! Of course we have not solved the NextBall issue for the SpareFrame, so we'll write a test for that, just like Spare() but with different values:
[Test] public void Spare2() { game.Spare(4,6); game.OpenFrame(5,3); ManyOpenFrames(8,0,0); AssertEquals( 23, game.Score()); }
This fails, as expected. What shall we do??? Here is my cunning plan. We will create the ArrayList of throws again, and we will give each Frame the list, and the index of its first ball in the list. The SpareFrame (and later the StrikeFrame) will access this ArrayList for needed values. I foresee some duplication coming, but for now let's try it. I'm sort of refactoring here -- changing the algorithm a bit -- but since I'm not at a green bar, I'm going to call it implementing. I hope that my readers will give some useful feedback on how they would work in this situation.
public class BowlingGame { ArrayList throws; ArrayList frames; public BowlingGame() { throws = new ArrayList(); frames = new ArrayList(); } public void OpenFrame(int firstThrow, int secondThrow) { throws.Add(firstThrow); throws.Add(secondThrow); frames.Add(new OpenFrame(throws, throws.Count, firstThrow, secondThrow)); } public void Spare(int firstThrow, int secondThrow) { throws.Add(firstThrow); throws.Add(secondThrow); frames.Add(new SpareFrame(throws, throws.Count, firstThrow, secondThrow)); } public int Score() { int total = 0; foreach (IFrame frame in frames) total += frame.Score(); return total; } }
The above didn't work, even when I changed the Score() method in SpareFrame. The reason was that I added the throws, and then used the Count. Wrong, should have done that first. I decided to move the throws.Add() inside the Frame classes as well, resulting in this:
public class BowlingGame { ArrayList throws; ArrayList frames; public BowlingGame() { throws = new ArrayList(); frames = new ArrayList(); } public void OpenFrame(int firstThrow, int secondThrow) { frames.Add(new OpenFrame(throws, throws.Count, firstThrow, secondThrow)); } public void Spare(int firstThrow, int secondThrow) { frames.Add(new SpareFrame(throws, throws.Count, firstThrow, secondThrow)); } public int Score() { int total = 0; foreach (IFrame frame in frames) total += frame.Score(); return total; } } public class OpenFrame : IFrame { ArrayList throws; int startingThrow; int score; public OpenFrame(ArrayList throws, int startingThrow, int firstThrow, int secondThrow) { this.throws = throws; this.startingThrow = startingThrow; throws.Add(firstThrow); throws.Add(secondThrow); score = firstThrow + secondThrow; } public int Score() { return score; } } public class SpareFrame : IFrame { ArrayList throws; int startingThrow; public SpareFrame(ArrayList throws, int startingThrow, int firstThrow, int secondThrow) { this.throws = throws; this.startingThrow = startingThrow; throws.Add(firstThrow); throws.Add(secondThrow); } private int NextBall() { return (int) throws[startingThrow + 2]; } public int Score() { return 10 + NextBall();; } }
The oddest thing here is that BowlingGame holds on to the throws list but doesn't use it. I think he is asking to have a FrameFactory extracted or something like that. But the tests now run! Since the list is inside the Frames, I can remove the count parameter, resulting in this:
public OpenFrame(ArrayList throws, int firstThrow, int secondThrow) { this.throws = throws; this.startingThrow = throws.Count; throws.Add(firstThrow); throws.Add(secondThrow); score = firstThrow + secondThrow; } public SpareFrame(ArrayList throws, int firstThrow, int secondThrow) { this.throws = throws; this.startingThrow = throws.Count; throws.Add(firstThrow); throws.Add(secondThrow); }
Tests still run! This is starting to look nearly OK. I think I'll change the Score() calc on OpenFrame to be dynamic, thus:
public class OpenFrame : IFrame { ArrayList throws; int startingThrow; public OpenFrame(ArrayList throws, int firstThrow, int secondThrow) { this.throws = throws; this.startingThrow = throws.Count; throws.Add(firstThrow); throws.Add(secondThrow); } public int Score() { return (int) throws[startingThrow] + (int) throws[startingThrow+1]; } }
This gives me some duplication that I don't like, "(int) throws[...]", but I'll deal with that later. First I'll do the StrikeFrame, then remove duplication. And before that, I'm taking a nap. I flew in on the red-eye last night and boy are my arms tired.
OK, it's 24 hours later, and I'm ready for StrikeFrame. First a test:
[Test] public void Strike() { game.Strike(); game.OpenFrame(5,3); ManyOpenFrames(8,0,0); AssertEquals( 26, game.Score()); }
Which of course begs the rest of the implementation, in the style of Spare:
public void Strike() { frames.Add(new StrikeFrame(throws)); } using System; using System.Collections; namespace BowlingWithFrame { public class StrikeFrame : IFrame { ArrayList throws; int startingThrow; public StrikeFrame(ArrayList throws) { this.throws = throws; this.startingThrow = throws.Count; throws.Add(10); } private int FirstFollowingBall() { return (int) throws[startingThrow + 1]; } private int SecondFollowingBall() { return (int) throws[startingThrow + 2]; } public int Score() { return 10 + FirstFollowingBall() + SecondFollowingBall(); } } }
The tests work! Note the new name, FirstFollowingBall instead of NextBall. I'll make that change to SpareFrame, but notice that the definition will be different, as the StrikeFrame refers to the current ball plus one, while the SpareFrame will have plus two. We could address that, perhaps, by having a separate constant for frame size in the two classes. That seems a bit too speculative, and anyway we need to work to remove the duplication.
The obvious way to remove some of this duplication is to set up some superclass / subclass relationship between these classes. Taking OpenFrame as an example, they all share this duplicated code:
public class OpenFrame : IFrame { ArrayList throws; int startingThrow; public OpenFrame(ArrayList throws, int firstThrow, int secondThrow) { this.throws = throws; this.startingThrow = throws.Count; throws.Add(firstThrow); throws.Add(secondThrow); }
We have to ask ourselves whether this duplication is worth eliminating. We'll have to set up a Frame superclass with those variables in it, and the first two lines of the constructor duplicated. Then the subclasses will include some separate code in the constructor and of course their unique Score() method. The superclass subclass thing will let us replace the interface with the superclass if we want to, but that's pretty much a wash. I really don't think it's going to remove a lot of duplication, but as I have very limited experience with setting up superclass / subclass in C#, I guess I'll do it as a learning experiment.
There are two ways one could go. One could choose one of the concrete classes to be the base class, then override behavior in the subclasses, or we could make an abstract superclass. I'll go with the latter, on the grounds that it is safer. In the subclasses, if we forgot to override the Score() method from a concrete superclass, we would have a mistake. Our tests would find it, of course, but the abstract class is still the "right" thing to do, I believe. I'll manufacture the abstract class from IFrame and then add the subclasses one at a time, removing duplication as I go. I'll start by creating the Frame class:
using System; using System.Collections; namespace BowlingWithFrame { abstract public class Frame : IFrame { protected ArrayList throws; protected int startingThrow; public Frame(ArrayList throws) { this.throws = throws; this.startingThrow = throws.Count; } abstract public int Score(); } }
This compiles, but until I subclass it, I'm not sure if it will work. I decided to start with OpenFrame and make that work. In the course of that I discovered that I need to declare the variables protected so that the subclasses can see them. The new OpenFrame looks like this:
using System; using System.Collections; namespace BowlingWithFrame { public class OpenFrame : Frame { public OpenFrame(ArrayList throws, int firstThrow, int secondThrow) : base(throws) { throws.Add(firstThrow); throws.Add(secondThrow); } override public int Score() { return (int) throws[startingThrow] + (int) throws[startingThrow+1]; } } }
The reference to base() tells C# how to call the base constructor, and the override tells C# that we know we are overriding the "virtual" method. (The "virtual", by the way, is what ensures that the method belonging to our concrete class (OpenFrame in this case) will be called, even though we are just passing around the superclass. Right now we're still using the interface. I'm not sure whether that makes any difference, but the compiler insisted that I use override.)
The tests work. I'll do the other three classes one at a time, but I'll show the results together below, unless something interesting happens.
using System; using System.Collections; namespace BowlingWithFrame { public class SpareFrame : Frame { public SpareFrame(ArrayList throws, int firstThrow, int secondThrow) : base(throws) { throws.Add(firstThrow); throws.Add(secondThrow); } override public int Score() { return 10 + NextBall();; } private int NextBall() { return (int) throws[startingThrow + 2]; } } } using System; using System.Collections; namespace BowlingWithFrame { public class StrikeFrame : Frame { public StrikeFrame(ArrayList throws) : base(throws) { throws.Add(10); } override public int Score() { return 10 + FirstFollowingBall() + SecondFollowingBall(); } private int FirstFollowingBall() { return (int) throws[startingThrow + 1]; } private int SecondFollowingBall() { return (int) throws[startingThrow + 2]; } } }
Hmm. I said "other three classes" because I am aware that I'm missing a test and a class. The missing case is the bonus frame at the end of the game, when you get a strike or spare in the last frame. I haven't written that test or class yet, but I have thought about it enough to have forgotten that it isn't done yet. No harm done. I'll finish this refactoring, then do that test and class.
While I was at it, I removed the IFrame interface, and then changed the two locations that used it. The first was that Frame inherited from IFrame, and it now inherits from no one. The second was in the Score() method of the BowlingGame:
public int Score() { int total = 0; foreach (Frame frame in frames) total += frame.Score(); return total; }
The tests all run. There is a bit of duplication yet in the methods that deal with the following balls. Note that I didn't yet rename NextBall(), but that's on the list. It's time for a break now to watch Coyote Waits.
Well, I'm back. No more code tonight, but I do want to record one concern: the FinalFrame thing is going to be a bit odd, because it can be a strike or a spare or anything, all on its own. That makes me worry about two things. First, that frame won't be as expressive as the others, with Strike and Spare. (On the other hand, maybe that was too expressive anyway?) Second, that means that its Score() method will be pretty procedural, so much so that it might almost have enough functionality to do all three cases (Open, Spare, Strike). That would make this whole exercise a bit of a waste of time, except that it's an experiment to see whether the bigger design is actually better. There is another possibility that I can think of: the FinalFrame might include some kind of duplication that we can get rid of by refactoring.
Or maybe we should have a regular frame, and one or two BonusBall "frames" that provide a reason for the throws to be in the array, but answer zero to Score(). That might be cool. Or maybe the FinalFrame method would convert itself into Open or Strike or Spare. Hmm, this is going to get interesting.
First, though, some sleep. And then maybe I'll refactor out that duplication in the next ball logic.
Good morning. It turns out that I couldn't resist last night, and did the NextBall refactoring. I used the idea that I mentioned earlier, of having each subclass know the size of the frame, and having the bonus ball locations calculated using that value. I renamed the methods to FirstBonusBall and SecondBonusBall, by the way. Here's the Frame class and one of its children, the Strike:
abstract public class Frame { protected ArrayList throws; protected int startingThrow; public Frame(ArrayList throws) { this.throws = throws; this.startingThrow = throws.Count; } abstract public int Score(); abstract protected int FrameSize(); protected int FirstBonusBall() { return (int) throws[startingThrow + FrameSize()]; } protected int SecondBonusBall() { return (int) throws[startingThrow + FrameSize() + 1]; } } public class StrikeFrame : Frame { public StrikeFrame(ArrayList throws) : base(throws) { throws.Add(10); } override public int Score() { return 10 + FirstBonusBall() + SecondBonusBall(); } override protected int FrameSize() { return 1; } }
OK, that seems pretty nice. Now let's get to the tenth frame issue. The basic idea is that the player can get one or more bonus rolls in the tenth frame. I'm proposing that the frame be scored like this:
If the player rolls open in the last frame, just score with OpenFrame(). If he spares, score with Spare(), followed by one BonusRoll(), which is to be coded. If he strikes, score with Spare(), followed by either one BonusRoll listing two balls, or two BonusRoll calls, I'm not sure which.
As I think about it. It seems to me that the bonus roll really only needs to add the balls to the throws table -- it need not store any objects in the frames list. But it seems wrong to do that, because right now, the frame list correctly describes the structure of the game, and every throw is "owned" by a Frame. To preserve that symmetry, I'm inclined to build a new Frame subclass even though it is unnecessary. It seems a reasonable decision. Here goes. We'll begin with a test:
[Test] public void StrikeFinalFrame() { ManyOpenFrames(9,0,0); game.Strike(); game.BonusRoll(5); game.BonusRoll(3); AssertEquals( 18, game.Score()); // note that this is different from test Strike() }
This begs:
in BowlingGame ... public void BonusRoll(int roll){ frames.Add(new BonusRoll(throws, roll)); } and the new class: public class BonusRoll : Frame { public BonusRoll(ArrayList throws, int firstThrow) : base(throws) { throws.Add(firstThrow); } override public int Score() { return 0; } override protected int FrameSize() { return 0; } }
The test runs! One thing worth mentioning is the implementation of FrameSize(). I expect it never to be called, because our Score() method doesn't use any BonusBalls. An argument could be made that we should make the method produce an error, but then everyone would have to worry about that. Another possibility would be to have it return a large integer, so as to be sure we would get an exception if it does get called. But if that's what we want, we should just throw one ourselves. My preference is to have it do as little as possible, and to develop in such a way as to be sure that it won't be called. Your mileage may vary.
Now we should be able to test SpareFinalFrame, which I expect to work:
[Test] public void SpareFinalFrame() { ManyOpenFrames(9,0,0); game.Spare(4,6); game.BonusRoll(5); AssertEquals( 15, game.Score()); }
This test runs. I will close with the two tests I always write when I do this exercise. The first is a perfect game, which as most people know should lead to a score of 300. The second, less well-known, is that any game of alternating strikes and spares leads to a score of 200. I was told this by a bowling champion who happened to be in a session where I demonstrated TDD with this exercise, and I thought it was interesting. So I always include it as my last test. Here they are:
[Test] public void Perfect() { for ( int i = 0; i < 10; i++) game.Strike(); game.BonusRoll(10); game.BonusRoll(10); AssertEquals( 300, game.Score()); } [Test] public void Alternating() { for ( int i = 0; i < 5; i++ ) { game.Strike(); game.Spare(4,6); } game.BonusRoll(10); AssertEquals( 200, game.Score()); }
All the tests run. I believe that we are finished with this version of the BowlingGame. Take a look at all the code, then let's draw some conclusions.
BowlingTest.cs using System; using NUnit.Framework; namespace BowlingWithFrame { [TestFixture] public class BowlingTest: Assertion { BowlingGame game; public BowlingTest() { } [SetUp] public void SetUp() { game = new BowlingGame(); } [Test] public void Hookup() { Assert(true); } [Test] public void GutterBalls() { ManyOpenFrames(10, 0, 0); AssertEquals(0, game.Score()); } [Test] public void Threes() { ManyOpenFrames(10, 3, 3); AssertEquals(60, game.Score()); } [Test] public void Spare() { game.Spare(4,6); game.OpenFrame(3,5); ManyOpenFrames(8,0,0); AssertEquals( 21, game.Score()); } [Test] public void Spare2() { game.Spare(4,6); game.OpenFrame(5,3); ManyOpenFrames(8,0,0); AssertEquals( 23, game.Score()); } [Test] public void Strike() { game.Strike(); game.OpenFrame(5,3); ManyOpenFrames(8,0,0); AssertEquals( 26, game.Score()); } [Test] public void StrikeFinalFrame() { ManyOpenFrames(9,0,0); game.Strike(); game.BonusRoll(5); game.BonusRoll(3); AssertEquals( 18, game.Score()); // note that this is different from test Strike() } [Test] public void SpareFinalFrame() { ManyOpenFrames(9,0,0); game.Spare(4,6); game.BonusRoll(5); AssertEquals( 15, game.Score()); } [Test] public void Perfect() { for ( int i = 0; i < 10; i++) game.Strike(); game.BonusRoll(10); game.BonusRoll(10); AssertEquals( 300, game.Score()); } [Test] public void Alternating() { for ( int i = 0; i < 5; i++ ) { game.Strike(); game.Spare(4,6); } game.BonusRoll(10); AssertEquals( 200, game.Score()); } private void ManyOpenFrames(int count, int firstThrow, int secondThrow) { for ( int frameNumber = 0; frameNumber < count; frameNumber++) game.OpenFrame(firstThrow, secondThrow); } } } BowlingGame.cs using System; using System.Collections; namespace BowlingWithFrame { public class BowlingGame { ArrayList throws; ArrayList frames; public BowlingGame() { throws = new ArrayList(); frames = new ArrayList(); } public void OpenFrame(int firstThrow, int secondThrow) { frames.Add(new OpenFrame(throws, firstThrow, secondThrow)); } public void Spare(int firstThrow, int secondThrow) { frames.Add(new SpareFrame(throws, firstThrow, secondThrow)); } public void Strike() { frames.Add(new StrikeFrame(throws)); } public void BonusRoll(int roll){ frames.Add(new BonusRoll(throws, roll)); } public int Score() { int total = 0; foreach (Frame frame in frames) total += frame.Score(); return total; } } } Frame.cs using System; using System.Collections; namespace BowlingWithFrame { abstract public class Frame { protected ArrayList throws; protected int startingThrow; public Frame(ArrayList throws) { this.throws = throws; this.startingThrow = throws.Count; } abstract public int Score(); abstract protected int FrameSize(); protected int FirstBonusBall() { return (int) throws[startingThrow + FrameSize()]; } protected int SecondBonusBall() { return (int) throws[startingThrow + FrameSize() + 1]; } } } OpenFrame.cs using System; using System.Collections; namespace BowlingWithFrame { public class OpenFrame : Frame { public OpenFrame(ArrayList throws, int firstThrow, int secondThrow) : base(throws) { throws.Add(firstThrow); throws.Add(secondThrow); } override public int Score() { return (int) throws[startingThrow] + (int) throws[startingThrow+1]; } override protected int FrameSize() { return 2; } } } StrikeFrame.cs using System; using System.Collections; namespace BowlingWithFrame { public class StrikeFrame : Frame { public StrikeFrame(ArrayList throws) : base(throws) { throws.Add(10); } override public int Score() { return 10 + FirstBonusBall() + SecondBonusBall(); } override protected int FrameSize() { return 1; } } } SpareFrame.cs using System; using System.Collections; namespace BowlingWithFrame { public class SpareFrame : Frame { public SpareFrame(ArrayList throws, int firstThrow, int secondThrow) : base(throws) { throws.Add(firstThrow); throws.Add(secondThrow); } override public int Score() { return 10 + FirstBonusBall();; } override protected int FrameSize() { return 2; } } } BonusRoll.cs using System; using System.Collections; namespace BowlingWithFrame { public class BonusRoll : Frame { public BonusRoll(ArrayList throws, int firstThrow) : base(throws) { throws.Add(firstThrow); } override public int Score() { return 0; } override protected int FrameSize() { return 0; } } }
Observations and Conclusions
There are certainly things here to like, and things not to like. The real comparison will come when we look at another version of the program, coming soon to a web site near you.
I somewhat like the way of scoring the game in terms of saying Strike, Spare, and so on. One might argue, however, that just reporting the number of pins knocked down is simpler. Working this way requires the user of our BowlingGame class to understand the game. But I like the communication.
The scoring methods in BowlingGame seem good. They are short and sweet, just creating a class instance and letting it do the job. And the Score() method seems about as simple as can be, it just goes over the frame objects and adds up their scores.
The individual Frame subclasses are pretty simple too. Each one just calculates a simple expression as its Score(), and each one says how long the particular kind of frame is. There's not much duplication there, although two of the constructors look a bit alike, and some of the FrameSize() methods have the same return.
In some senses, I think this is pretty nice code. It looks like something we might have designed, although we built it in a very incremental fashion.
However, as we'll see in the sequel, there are implementations of this story that take a lot less code and that are, at least in some ways, more simple. When we have two solutions side by side, we'll see more about how to assess this one. But right now, I think it looks pretty good.