Ten Pin Bowling Calculator





4.00/5 (2 votes)
Simple ten-pin bowling calculator to calculate the scores of the game

Introduction
Recently, I was tasked to carry out this assignment, and have not come across many articles on explaining this, let alone how to compute the scores. I have come across two open-source projects (see the references below) that do it but my needs were more simpler than that, hence this article aims to fill the gap and also it is about how to calculate the scores used in Ten Pin Bowling, using an OOP approach that is intuitive and easy to use. The scoring system can be confusing but will be dispelled once you read this. I used the open-source projects to compare the results of the scores.
Background
Ten Pin Bowling is a competitive game with a simple objective - to knock down as many pins per throw on a narrow alley with the pins situated at the end of the alley. The game composes of frames, at each frame all ten pins are set up standing, the player gets two throws per frame to knock the pins down. There are ten frames per game. A throw is where the player throws a ball down the alley to attempt knocking down pin(s).
In each frame, there are two throws, with the exception of the last frame (will be discussed shortly).
- If all pins are knocked down on the first throw, that is called a Strike.
- If the player fails to knock down ALL pins on the first throw but succeeds on the second throw, that is called a Spare.
- x pins knocked on the first throw, and the remainder of y pins knocked on the second throw, such that, x + y is less than 10.
Forgive the usage of mathematical notations - everything tis clear as mud aye!
The scoring works like this:
- Strike - Add ten plus the number of pins knocked down by the next two balls to the score of the previous frame.
- Spare - Add ten plus the number of pins knocked down by the next ball to the score of the previous frame.
- Normal - Add the total of pins knocked down by the two throws to the previous frame. A gutter ball, is when the ball ends up in the gutter and does not knock any pins, that is worth zero points! :)
There, that's it, sounds tricky...wait...
On the last frame, if a Strike is thrown, the player has two throws to complete the frame. Likewise, if a Spare is thrown, the player has one throw to complete the frame. Hence the last frame will have three throws! Hmmm... how to compute THAT!
Note: Discussion of tactics/techniques are not discussed here. If you're looking to get a perfect score of maximum 300 points, you've come to the wrong place, but feel free to read it, and no, it will not help you anyway! :)
Using the Code
I will discuss the classes used and then the GUI, but first let's head on into classes here. Here's an overview of the classes in UML diagram (created with Dia).
UML Classes
Click on the image below to see the full UML in its glory!

A look at the classes and what they mean:
BallThrow
- This class is responsible for holding the number of pins knocked, and can tell if that throw is:- A Gutter ball
- A Strike
- A Spare
There are only two methods (both overloads) in this class called
MarkBall
.Frame
- This class holds an internal generic list of typeBallThrow
, and for whatever frame the state of play is at, it will fire an eventEndOfFrame
, likewise when the game is finished, i.e. all ten frames are completed, it fires an eventAllFramesDone
. The knocking of pins is handled here by invokingThrowBall
, which in turn invokes the method overloadsMarkBall
of theBallThrow
class.FrameHandler
- This is the main class which holds an internal generic list of typeFrame
, this also happens to be a consumer of the events fired from theFrame
class. Speaking of events, this class fires off three events to the external consumer, in this case of the application, the GUI, consumes it and acts accordingly.Score
- This is how the GUI updates the score for each frame.PerfectGame
- This will fire if the maximum points have reached, i.e. 300 (You'd have to be a good bowler or the 'King of Pins' to achieve this.)EndOfGame
- This will fire when the game ends and all ten frames are completed.
It is also used to handle the state of play and keep track of the scoring by referring to the above two classes. When playing a frame, the UI extracts the appropriate frame from this class (
Frame
) by simply indexing using the actual number that denotes the frame and sets it accordingly by invoking the method overloads forThrowBall
depending on whether it was a strike/spare or a number used (how the GUI marks it as a strike/spare will be discussed shortly).FrameScoreEventArgs
- A class inherited fromSystem.EventArgs
to hold the Frame number (FrameNumber
) and the score for that frame (FrameScore
). The events that use this class are found inFrame
.EndOfFrame
and inFrameHandler
.Score
.
Now that the overview of the classes are done, this should make it easier to understand the code as we'll go through below. All XML comments are stripped for brevity here.
BallThrow
public class BallThrow{
....
//
public BallThrow() {}
//
public BallThrow(int iPinsKnocked) {
this._iPinsKnocked = iPinsKnocked;
}
//
....
//
public void MarkBall(int iPinsKnocked){
this._iPinsKnocked = iPinsKnocked;
}
//
public void MarkBall(bool bStrike, bool bSpare) {
this._bThrowIsStrike = bStrike;
this._bThrowIsSpare = bSpare;
}
}
This class in Figure 1, above, only has a number of properties which uses privately declared variables to determine the state of this throw.
PinsKnocked
- The number of pins knocked down, this uses_iPinsKnocked
.IsAGutter
- Not really used here in this application but useful nonetheless! If no pins are knocked down, then it checks for comparison with_iPinsKnocked
to zero.SpareBall
- Was this throw a spare? It uses_bThrowIsSpare
to determine the status of throw.StrikeBall
- Was this throw a strike? This uses_bThrowIsStrike
to determine if this throw resulted in a strike.
Astute readers will notice that there are two constructors, the reason is, further on higher up in the class hierarchy, if a throw was a spare or strike, a need to instantiate this class with an empty constructor is required and the second overload of MarkBall
is invoked to mark this throw as spare or a strike. Otherwise, if this was a normal throw, the number of pins knocked down is passed into the constructor.
MarkBall(int iPinsKnocked)
- Sets up the number of pins knocked down by assigning_iPinsKnocked
to the desired quantity.MarkBall(bool bStrike, bool bSpare)
- Sets the appropriate flag to mark this throw as a strike or a spare.
Frame
public class Frame {
....
//
public Frame(int iFrameNo) {
this._iFrameNo = iFrameNo;
this._throwList = new List<BallThrow>();
}
//
public BallThrow this[int index] {
get {
if (this._throwList.Count == 0) return null;
if (!this.IsLastFrame) {
if (index >= BTHelpers.FIRST_THROW_INDEX &&
index <= BTHelpers.SECOND_THROW_INDEX) return this._throwList[index];
else return null; // Out of bounds....
} else {
// Last Frame
if (index >= BTHelpers.FIRST_THROW_INDEX &&
index <= BTHelpers.LAST_THROW_INDEX) return this._throwList[index];
else return null; // Out of bounds....
}
}
}
//
...
...
}
I deliberately left out the MarkBall
overloads as this will be discussed shortly. This class in Figure 2, above, only has a number of properties about the frame:
this
- This returns back the appropriateBallThrow
object, there's a validation to check on the index to theList<BallThrow>
collection.Number
- This simply tells what frame we're at.IsLastFrame
- Are we on the last frame? (Remember the last frame could have three throws)IsFirstFrame
- Are we on the first frame?IsStrike
- Was this frame a strike?IsSpare
- Was this frame a spare?TotalPinsKnocked
- The total number of pins knocked down in this frame. In the case of either a strike or a spare, this returns 10 either way.IsNormal
- Does this frame have no strike or spare, i.e. the sum of pins knocked down which is less than 10.CountOfThrows
- The number of throws in this frame.FirstBall
- Returns back the number of pins knocked down on the first throw, otherwise it will have 10 for a strike.SecondBall
- Returns back the number of pins knocked down on the second throw. If this frame was a spare, thenFirstBall
+ this shall equal to 10.LastBall
- This is for if we're the last frame and to return back the number of pins knocked down.
Note that the property TotalPinsKnocked
is taken care of in the method overloads MarkBall
and takes into account the last frame especially!
Frame.MarkBall(...)
public void ThrowBall(int iPins) {
if (!this.IsLastFrame) {
if (this._throwList.Count == 0) {
// First throw...
BallThrow throwBall = new BallThrow(iPins);
//
if (iPins == BTHelpers.MAX_PINS)
throwBall.MarkBall(true, false); // A Strike was used!
this._throwList.AddRange(new BallThrow[] { throwBall, new BallThrow() });
if (throwBall.StrikeBall) {
throwBall.MarkBall(BTHelpers.MAX_PINS);
int TotalPinsKnocked = BTHelpers.MAX_SCORE;
this._iFrameScore = TotalPinsKnocked;
this._iTotalPinsKnocked = BTHelpers.MAX_SCORE;
this.OnEndOfFrame(new FrameScoreEventArgs
(TotalPinsKnocked, this._iFrameNo));
return;
}
} else {
// Check for the first throw
BallThrow prevThrow = this._throwList[BTHelpers.FIRST_THROW_INDEX];
if (prevThrow.PinsKnocked + iPins == BTHelpers.MAX_PINS) {
// Spare!
this._throwList[BTHelpers.SECOND_THROW_INDEX].MarkBall(false, true);
}
this._throwList[BTHelpers.SECOND_THROW_INDEX].MarkBall(iPins);
int TotalPinsKnocked = this._throwList
[BTHelpers.FIRST_THROW_INDEX].PinsKnocked +
this._throwList
[BTHelpers.SECOND_THROW_INDEX].PinsKnocked;
//
this._iFrameScore = TotalPinsKnocked;
if (this._throwList.Count == BTHelpers.FRAME_STD_THROWS) {
this._iTotalPinsKnocked = TotalPinsKnocked;
this.OnEndOfFrame(new FrameScoreEventArgs
(TotalPinsKnocked, this._iFrameNo));
}
}
} else {
BallThrow prevThrow = null;
BallThrow throwLast = new BallThrow(iPins);
switch (this._throwList.Count) {
case 0:
// Simple enough case...we're on the first throw of the last frame.
// This could be either n pins knocked or a strike
BallThrow throwFirstBall = new BallThrow(iPins);
if (iPins == BTHelpers.MAX_PINS) throwFirstBall.MarkBall(true, false);
this._throwList.Add(throwFirstBall);
break;
case 1:
// Second throw of the last frame
// Check the previous
prevThrow = this._throwList[BTHelpers.FIRST_THROW_INDEX];
if (prevThrow.StrikeBall) {
// this... CANNOT BE A SPARE - IMPOSSIBLE!
// 1st Throw a Strike, IMPOSSIBLE TO HAVE A SPARE in the second
this._throwList.Add(throwLast);
return;
} else {
// Both ordinary no's...i.e. the first throw was n Pins,
// the second throw was (MAX_PINS - n) pins knocked down.
if (prevThrow.PinsKnocked + iPins < BTHelpers.MAX_PINS) {
this._throwList.Add(throwLast);
int TotalPinsKnocked = this._throwList
[BTHelpers.FIRST_THROW_INDEX].PinsKnocked +
this._throwList
[BTHelpers.SECOND_THROW_INDEX].PinsKnocked;
//
this._iFrameScore = TotalPinsKnocked;
this._iTotalPinsKnocked = TotalPinsKnocked;
// Signal end of frame...no more throws at this point
this.OnEndOfFrame(new FrameScoreEventArgs
(TotalPinsKnocked, this._iFrameNo));
// Signal game complete!
this.OnAllFramesDone();
return;
} else {
// Both ordinary no's...i.e. the first throw was n Pins,
// the second throw was spare!
// So another throw would be used i.e. 3rd throw.
if (prevThrow.PinsKnocked + iPins == BTHelpers.MAX_PINS) {
// This is a spare
throwLast.MarkBall(false, true);
this._throwList.Add(throwLast);
int TotalPinsKnocked = BTHelpers.MAX_PINS;
this._iFrameScore = TotalPinsKnocked;
}
}
}
break;
case 2:
default:
// We're on the last throw
prevThrow = this._throwList
[BTHelpers.SECOND_THROW_INDEX]; // Check the second throw
if (prevThrow.StrikeBall) {
// this... CANNOT BE A SPARE - IMPOSSIBLE!
this._throwList.Add(throwLast);
int TotalPinsKnocked =
this._throwList[BTHelpers.SECOND_THROW_INDEX].PinsKnocked +
this._throwList[BTHelpers.LAST_THROW_INDEX].PinsKnocked;
//
this._iFrameScore = TotalPinsKnocked;
this._iTotalPinsKnocked = TotalPinsKnocked;
// Signal end of frame...no more throws at this point
this.OnEndOfFrame(new FrameScoreEventArgs(TotalPinsKnocked,
this._iFrameNo));
this.OnAllFramesDone();
return;
}
if (prevThrow.SpareBall) {
// Oh! the 2nd throw was a spare...
if (prevThrow.PinsKnocked +
iPins < BTHelpers.MAX_PINS) { // Both ordinary no's...
this._throwList.Add(throwLast);
int TotalPinsKnocked = BTHelpers.MAX_PINS +
this._throwList[BTHelpers.LAST_THROW_INDEX].PinsKnocked;
this._iFrameScore = TotalPinsKnocked;
this._iTotalPinsKnocked = TotalPinsKnocked;
// Signal end of frame...no more throws at this point
this.OnEndOfFrame(new FrameScoreEventArgs
(TotalPinsKnocked, this._iFrameNo));
} else {
if (prevThrow.PinsKnocked + iPins ==
BTHelpers.MAX_PINS) { // this throw +
// prev = spare.. Will have another shot!...
throwLast.MarkBall(false, true);
this._throwList.Add(throwLast);
int TotalPinsKnocked = BTHelpers.MAX_PINS +
this._throwList[BTHelpers.LAST_THROW_INDEX].PinsKnocked;
//
this._iFrameScore = TotalPinsKnocked;
this._iTotalPinsKnocked = TotalPinsKnocked;
// Signal end of frame...no more throws at this point
this.OnEndOfFrame(new FrameScoreEventArgs
(TotalPinsKnocked, this._iFrameNo));
} else {
// 2nd throw was a spare
// 3rd throw was a normal one with n pins knocked.
throwLast.MarkBall(false, true);
this._throwList.Add(throwLast);
int TotalPinsKnocked = BTHelpers.MAX_PINS +
this._throwList[BTHelpers.LAST_THROW_INDEX].PinsKnocked;
this._iFrameScore = TotalPinsKnocked;
this._iTotalPinsKnocked = TotalPinsKnocked;
// Signal end of frame...no more throws at this point
this.OnEndOfFrame(new FrameScoreEventArgs
(TotalPinsKnocked, this._iFrameNo));
}
}
// Signal game complete!
this.OnAllFramesDone();
} else {
// 2nd Throw was just an ordinary pins knocked
// this last throw just happened
this._throwList.Add(throwLast);
// Normal digits...
int TotalPinsKnocked = this._throwList
[BTHelpers.FIRST_THROW_INDEX].PinsKnocked +
this._throwList
[BTHelpers.SECOND_THROW_INDEX].PinsKnocked +
this._throwList
[BTHelpers.LAST_THROW_INDEX].PinsKnocked;
//
this._iFrameScore = TotalPinsKnocked;
this._iTotalPinsKnocked = TotalPinsKnocked;
// Signal End of Frame
this.OnEndOfFrame(new FrameScoreEventArgs
(TotalPinsKnocked, this._iFrameNo));
// Signal game complete!
this.OnAllFramesDone();
}
// Allow it to fall through!
break;
}
}
}
Now, what a mouthful of code...fear not, for I shall lead you down the bowling alley and the light will shine! :) Ok, seriously, the logic is similar to the second overload of the above method, frames 1 to 9 are simple enough, it's the last frame that's where we need to watch out for, because there are a number of possibilities combining strike(s) and spare(s). It should be noted:
- For frames 1-9 inclusive, if a number of pins was thrown and is the first throw, then this is what happens:
- An empty
BallThrow
class is instantiated -throwBall
. throwBall.MarkBall
(2nd overload) is invoked to set the flags then... (This is optional unless a strike is thrown.)throwBall.MarkBall
(1st overload) is invoked to set the pins knocked down to the desired number of pins (This will be 10 if this was a strike, and hence, this frame gets marked as a strike).- The newly instantiated class is added to the collection (
List<BallThrow>
) using theAddRange
method of the generic list collection adding another emptyBallThrow
to the collection, to make this Frame consist of two throws. - The event
OnEndOfFrame
gets fired, in which theFrameHandler
class can then advance on to the next Frame.
Otherwise, if this was a second throw, then this is what happens:
- A check is made by obtaining the first throw in the
<BallThrow>
collection,prevThrow
(type ofBallThrow
class), and checking if the sum of pins knocked down on the previous throw equals 10, then this throw gets marked as a spare. - The event
OnEndOfFrame
gets fired, in which theFrameHandler
class can then advance on to the next Frame.
- An empty
- For the last frame, a check is made to see what throw we are on by checking the
List<BallThrow>
's propertyCount
and using theswitch
statement to determine the throw we're on..we also create a newBallThrow
object which gets added to the collection in one of the cases below...- If it is 0, then we're on the first throw of this last frame, again, we check if a strike was thrown by comparing to 10. Add it to the collection. No events are fired at this point.
- If it is 1, then we're on the second throw, again, we check the
_prevThrow
which is aBallThrow
object accessed from the generic collection...i.e. first throw...- We then determine if the previous throw was a strike.
- If the
_prevThrow
's pins plus this throw's pins is less than 10, then we add this throw to the generic collection, fire the eventEndOfFrame
and also fire another eventAllFramesDone
. - If the
_prevThrow
's pins plus this throw's pins is equal to 10, then we add this throw to the generic collection, mark this throw as a spare. No events are fired at this point since a third throw will be warranted.
- If it is 2, then we're on the third throw, again, we check the
_prevThrow
which is aBallThrow
object accessed from the generic collection...i.e. second throw...- We then determine if the previous throw was a strike, add it to the generic collection and fire two events
EndOfFrame
andAllFramesDone
. - If the
_prevThrow
was a spare, then we add to the generic collection and again, fire two eventsEndOfFrame
andAllFramesDone
. - If the
_prevThrow
's pins plus this throw's pins is less than 10, then we add this throw to the generic collection, fire the eventEndOfFrame
and also fire another eventAllFramesDone
. - If the
_prevThrow
's pins plus this throw's pins is equal to 10, then we add this throw to the generic collection, mark this throw as a spare, fire the eventEndOfFrame
and also fire another eventAllFramesDone
.
- We then determine if the previous throw was a strike, add it to the generic collection and fire two events
FrameHandler
Now we're almost done, this class is next up for discussion.
public class FrameHandler : IEnumerable<frame>, IEnumerator<frame> {
....
//
public FrameHandler() {
for (int nLoopCnt = 0; nLoopCnt < BTHelpers.MAX_FRAMES; nLoopCnt++) {
Frame f = new Frame(nLoopCnt + 1);
f.EndOfFrame += new EventHandler<FrameScoreEventArgs>(f_EndOfFrame);
this._listFrames.Add(f);
}
Frame fLastFrame = this[BTHelpers.LAST_FRAME_INDEX];
fLastFrame.AllFramesDone +=
new EventHandler<EventArgs>(fLastFrame_AllFramesDone);
}
//
....
//
void fLastFrame_AllFramesDone(object sender, EventArgs e) {
System.Diagnostics.Debug.WriteLine("**** THIS GAME IS COMPLETE ****");
Frame fPrev = this[BTHelpers.LAST_FRAME_INDEX - 1];
Frame fCurr = this[BTHelpers.LAST_FRAME_INDEX];
fCurr.Score = fCurr.TotalPinsKnocked + fPrev.Score;
this.OnScore(new FrameScoreEventArgs(fCurr.Score, fCurr.Number));
this.OnEndOfGame();
if (fCurr.Score == BTHelpers.MAX_FRAMES_SCORE) this.OnPerfectGame();
}
//
void f_EndOfFrame(object sender, FrameScoreEventArgs e) {
this.CalcScore(e.FrameNumber);
this.Reset();
foreach (Frame f in this) {
if (!f.IsLastFrame) this.OnScore
(new FrameScoreEventArgs(f.Score, f.Number));
}
}
// IEnumerable<frame> Members
....
//
....
//
private int CalcScore(int iFrameNo) {
int iScore = 0;
//System.Diagnostics.Debug.WriteLine(string.Format("[DEBUG] -
//CalcScore() - FRAME #{0} **********", iFrameNo));
for (int iCurrentFrame = BTHelpers.FIRST_FRAME_INDEX;
iCurrentFrame < iFrameNo; iCurrentFrame++) {
Frame f = this[iCurrentFrame];
Frame fNext = this[iCurrentFrame + 1];
Frame fNextNext = this[iCurrentFrame + 2];
int iNextTwoBalls = 0;
if (fNext != null){
iNextTwoBalls += fNext.FirstBall;
if (fNext.SecondBall == 0) {
if (fNextNext != null) {
iNextTwoBalls += fNextNext.FirstBall;
}
} else {
iNextTwoBalls += fNext.SecondBall;
}
}
if (f.IsStrike) iScore += BTHelpers.MAX_SCORE + iNextTwoBalls;
else if (f.IsSpare) iScore += BTHelpers.MAX_SCORE + fNext.FirstBall;
else iScore += f.TotalPinsKnocked;
f.Score = iScore;
//System.Diagnostics.Debug.WriteLine
//(string.Format("[DEBUG] - CalcScore() - Score: {0}", iScore));
}
return iScore;
}
}
This class is the meat of it all and handles the consuming of the Frame
's events EndOfFrame
and AllFramesDone
. When this class gets instantiated, it initializes the generic list collection, List<Frame>
. Notice how we looped from Frame 1 to 9 inclusive and assign the event handler for EndOfFrame
.
Then we specifically add a new Frame
that denotes the tenth frame and assign the event handler to AllFramesDone
.
It also implements the IEnumerable<Frame>
and IEnumerator<Frame>
to make it easy to iterate through the generic collection List<Frame>
. Notice how I did not allow the Frame
's events to get propagated to the outside, instead it gets handled here, and this class simply forwards it on to the outside, EndOfGame
and PerfectGame
.
From this so far, due to the way this was designed, perhaps a weakness maybe, that you cannot go back to a frame and stick in a value as it's already set up and would lead to incorrect results as the throws for a frame that you went back onto, was already done.
There is only one property for this class accessible to the outside:
this[index]
- This returns back the appropriate frame.
The exposable method to the outside is ClearAllThrows
which simply iterates through the collection and resets the Frame
's collection of BallThrow
.
- Upon receiving the
Frame
's eventEndOfFrame
, the class invokesCalcScore
and iterates through the collection, repeatedly firing offScore
event to update the score as it goes through each frame. - After the
Frame
's eventAllFramesDone
gets trapped, we then add on the sum of Frame 9 to Frame 10, to give the final score for Frame 10 and fire off the eventEndOfGame
. We also check to see if the score has reached maximum points of 300 and fire the eventPerfectGame
.
In referring to the private
method CalcScore
, the code to handle the scoring logic is this in pseudocode...
int score = 0;
for each frame
if frame is strike then score += 10 + next_two_balls();
else if frame is spare then score += 10 + next_ball();
else score += frame's score.
next_two_balls()
return firstball + secondball;
next_ball()
return firstball;
The trick lies in the usage of the next_two_balls in the above Figure 5, since when a frame has a strike, it has two BallThrow
's in that Frame
's generic collection, the first element of the zero-th index would be 10, the next element of the first index would be 0, this would obviously not make sense i.e. 10 + 0 = 10... (I fell for this when I discovered the code didn't calculate properly - duh!), so I used the next frame's first ball! That would explain why I had two variables fNext
and fNextNext
and check on them to compute iNextTwoBalls
as shown in Figure 4 above. The results computed then get assigned to that frame. Ok, everyone got that - aye tis clear as mud, wrong? right!
Now that's the meat of the bowling calculator out of the way, time to move on to the UI. By now, you can imagine, when the UI receives the events from the FrameHandler
's class, namely EndOfGame
, PerfectGame
and Score
, you can deduce it notifies the user of that - and updates the UI to reflect that. So far, so good...
The User Interface
The UI code is split up into two files Form1.cs and frmBowlClass.cs. Let's talk about the controls for frames used which makes up the interface...
- TextBoxes - For frames 1-9 inclusive, there's two textboxes (representing a throw each), for the last frame, there's three, inputs for each of the frame allowed are:
- Numeric - 0..9 only
- Spare - / only
- Strike - X only
It should be noted that the input length is 1 only, any other inputs are discarded (or assigned to /dev/null for that matter) and also the inputs advance on to the next frame automatically.
- Only the first throw can have an 'X' (a strike). The second input box becomes disabled.
- Only the second throw can have an '/' (a spare).
- The above two is for frames 1-9 inclusive!
- Numbers 0-9 on both throws are accepted.
- Labels - This is purely to show the score for each of the frames.
Each of the above are placed into the group box (or child controls if you prefer). The {FrameNo} and {ThrowNo} are substituted with the actual numbers indicating the frame and throw respectively. The names I gave to these controls are:
- Group boxes - grpFrame{FrameNo}, and in each group box, there's two text boxes and a label..
- Text boxes - txtBoxF{FrameNo}Throw{ThrowNo}...
- Labels - lblFrame{FrameNo}.
Right on, let's move on from here...
Form1.cs
private const string KEY_TAB = "{TAB}";
//
private const string LABELNAME = "lblFrame{0}";
private const string FIRSTTHROW_ANY_FRAME = "txtBoxF{0}Throw1";
private const string SECONDTHROW_ANY_FRAME = "txtBoxF{0}Throw2";
private const string THIRDTHROW_LAST_FRAME = "txtBoxF10Throw3";
//
private const string SPARE = "/";
private const string STRIKE = "X";
//
private const string REGEXP_FRAMENO = "frameNo";
private const string REGEXP_THROWNO = "throwNo";
//
private readonly string VALID_KEYS_THROW = "/0123456789X";
//
private System.Text.RegularExpressions.Regex _reTextInputName = new Regex(@
"^txtBoxF(?<frameno>\d+)Throw(? \d+)$",
System.Text.RegularExpressions.RegexOptions.Compiled |
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
//
private System.Text.RegularExpressions.Regex _reLblName = new Regex(@
"^lblFrame(?<frameno>\d+)",
System.Text.RegularExpressions.RegexOptions.Compiled |
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
//
private System.Text.RegularExpressions.Regex _reGrpName = new Regex(@"
^grpFrame(?<frameno>\d+)",
System.Text.RegularExpressions.RegexOptions.Compiled |
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
//
private BowlTracker.FrameHandler _frameHandler = null;
//
private System.Collections.Generic.Dictionary _hashCtls = new Dictionary ();
//
public frmBowlCalc() {
InitializeComponent();
this.HashGroups();
this._frameHandler = new FrameHandler();
this._frameHandler.Score +=
new EventHandler<framescoreeventargs>(_frameHandler_Score);
this._frameHandler.EndOfGame += new EventHandler (_frameHandler_EndOfGame);
this._frameHandler.PerfectGame += new EventHandler (_frameHandler_PerfectGame);
}
This explains why you're seeing the regex's and the constants...Looks crappy but...(I am my own worst enemy). Let's take a look at this constructor, it calls HashGroups
and initializes a new instance of FrameHandler
, sets up the event handlers for Score
, EndOfGame
and PerfectGame
. Now, but wtf is HashGroups
... hmmm... is there some hocus pocus going on here?.... Let's take a dive into it... HashGroups
is situated in frmBowlClass.cs so look in there.
HashGroups
private void HashGroups() {
foreach (Control c in this.Controls) {
if (c is GroupBox) {
GroupBox grp = (GroupBox)c as GroupBox;
if (grp != null) {
Match m = this._reGrpName.Match(grp.Name);
if (m.Success) {
int iFrameNo = int.Parse(m.Groups[REGEXP_FRAMENO].Value);
this._hashCtls.Add(iFrameNo, grp);
}
}
}
}
}
The above method, shown in the above Figure 7, is a generic collection Dictionary<int, GroupBox>
which simply stores the GroupBox
into the dictionary with the key being as the Frame Number. Yup, you've guessed it, each control in each group has a frame number from 1 to 10 inclusive! This makes it easier to pull in the label, find out the contents entered into the appropriate text box and perform some magic on it.
From looking at Figure 7, you can see how the controls on the UI are iterated and checked if the control is a group box, extract the frame number from the group box's Name
property by performing a regexp matching to extract the desired frame number and add it to the dictionary.
This makes things easier to "get at" the appropriate label to update the score...see Figure 8 below for the two methods used:
private TextBox LookUpTextBox(int iFrameNo, string sTxtBoxTargetName) {
GroupBox grpBox = this._hashCtls[iFrameNo];
if (grpBox != null) {
foreach (Control t in grpBox.Controls) {
if (t is TextBox) {
TextBox txtBox = (TextBox)t;
if (txtBox.Name.Equals(sTxtBoxTargetName)) return txtBox;
}
}
}
return null;
}
private Label LookUpLabel(int iFrameNo, string sLblBoxTargetName) {
GroupBox grpBox = this._hashCtls[iFrameNo];
if (grpBox != null) {
foreach (Control t in grpBox.Controls) {
if (t is Label) {
Label lblBox = (Label)t;
if (lblBox.Name.Equals(sLblBoxTargetName)) return lblBox;
}
}
}
return null;
}
Those two Figures, 7 and 8 respectively are in frmBowlClass.cs...Was that code just being smart and clever?.... Now it is obvious how the score gets shoved into the label on the UI, LookUpLabel
... likewise the similar method for LookUpTextBox
...
Let's take a look at how the score "gets at" the appropriate label for a frame, this is in Form1.cs by the way...
void _frameHandler_Score(object sender, FrameScoreEventArgs e) {
if (e.FrameScore > 0) {
Label lblFrame = this.LookUpLabel
(e.FrameNumber, string.Format(LABELNAME, e.FrameNumber));
lblFrame.Text = e.FrameScore.ToString();
}
}
There... wasn't so difficult, was it... notice how the LookUpLabel
was used to get the specific label for a specific frame number as shown in Figure 9.
The common code that is shared between all text box inputs to filter out fluff is situated in txtBoxThrow_KeyPress
, see Figure 10 below. The code to handle each throw is in txtBoxThrow
(One|Two|Three)_KeyUp
event handlers. ...let's take a look at the first throw input box...again, as shown below in Figure 10.
// in Form1.cs!!
private void txtBoxThrowOne_KeyUp(object sender, KeyEventArgs e) {
TextBox txtBox = (TextBox)sender as TextBox;
if (txtBox != null && txtBox.Text.Length > 0) {
if (this.HandleFirstThrow(txtBox)) {
e.Handled = true;
SendKeys.Send(KEY_TAB);
} else e.Handled = false;
}
}
private void txtBoxThrowOne_KeyUp(object sender, KeyEventArgs e) {
TextBox txtBox = (TextBox)sender as TextBox;
if (txtBox != null && txtBox.Text.Length > 0) {
if (this.HandleFirstThrow(txtBox)) {
e.Handled = true;
SendKeys.Send(KEY_TAB);
} else e.Handled = false;
}
}
// in frmBowlClass.cs
private bool HandleFirstThrow(TextBox txtBox) {
string sName = txtBox.Name;
Match m = this._reTextInputName.Match(sName);
if (m.Success) {
int iFrameNo = int.Parse(m.Groups[REGEXP_FRAMENO].Value);
int iThrowNo = int.Parse(m.Groups[REGEXP_THROWNO].Value);
if (iFrameNo < BowlTracker.BTHelpers.MAX_FRAMES) {
if (txtBox.Text.Equals(STRIKE)) {
this.DisableSecondThrow(iFrameNo);
this._frameHandler[iFrameNo].ThrowBall(true, false);
return true;
}
if (txtBox.Text.Equals(SPARE)) { // Cannot have a spare on the
// first throw! - illogical!
this.InvalidThrow(txtBox);
return false;
}
} else {
// Last Frame...
if (txtBox.Text.Equals(STRIKE)) {
this._frameHandler[iFrameNo].ThrowBall(true, false);
return true;
}
}
// Anything else here would be numeric...
int iPins = int.Parse(txtBox.Text);
this._frameHandler[iFrameNo].ThrowBall(iPins);
// Go back to previous frame and check if a spare was used
return true;
}
return false;
}
Looking at the txtBoxThrowXXX_KeyUp event handler, it is obvious how if the input is deemed valid then it advances on to the next frame using the Send
method of SendKeys
class.
In the function HandleFirstThrow
returns a boolean which will determine if it's ok to advance onwards to the next input.
The parameter TextBox
's Name
property gets passed through regexp to determine which throw and which frame is this textbox under, we now also can reference the appropriate frame by using the indexer to the FrameHandler
class to manipulate the frame. A check is made here to see which frame are we on.
- We then check if a strike was entered, if it was, the method
DisableSecondThrow
is called which prevents any input in the textbox for the second throw. And the methodThrowBall
gets called marking the selected frame as a strike. - Otherwise, we perform a simple check if the Spare was entered, if it was, then a method
InvalidThrow
gets called which simply clears the textbox thus forcing the user to enter a proper input. - Anything else entered, i.e. numeric input, gets passed into the method
ThrowBall
of the appropriate frame's class.
Let's look at the handler for handling second throws below in Figure 11.
private bool HandleSecondThrow(TextBox txtBox) {
string sName = txtBox.Name;
Match m = this._reTextInputName.Match(sName);
if (m.Success) {
int iFrameNo = int.Parse(m.Groups[REGEXP_FRAMENO].Value);
int iThrowNo = int.Parse(m.Groups[REGEXP_THROWNO].Value);
if (iFrameNo < BowlTracker.BTHelpers.MAX_FRAMES) {
// We're on the other frame(s)...
Throw2ndSpare:
if (txtBox.Text.Equals(SPARE)) {
this._frameHandler[iFrameNo].ThrowBall(false, true);
return true;
}
if (txtBox.Text.Equals(STRIKE)) {
this.InvalidThrow(txtBox);
return false;
}
TextBox prevThrow = this.LookUpTextBox
(iFrameNo, string.Format(FIRSTTHROW_ANY_FRAME, iFrameNo));
int iFirstThrow = 0;
if (prevThrow != null) iFirstThrow = int.Parse(prevThrow.Text);
int iSecondThrow = int.Parse(txtBox.Text);
if (iFirstThrow + iSecondThrow == BowlTracker.BTHelpers.MAX_PINS) {
// Set it to spare
txtBox.Text = SPARE;
goto Throw2ndSpare;
}
if (iFirstThrow + iSecondThrow < BowlTracker.BTHelpers.MAX_PINS) {
this._frameHandler[iFrameNo].ThrowBall(iSecondThrow);
return true;
}
// Any thing else here is an error!
this.InvalidThrow(txtBox);
return false;
} else {
// We're on the last Frame...
if (txtBox.Text.Equals(SPARE) || txtBox.Text.Equals(STRIKE)) {
if (txtBox.Text.Equals(STRIKE))
this._frameHandler[iFrameNo].ThrowBall(true, false);
if (txtBox.Text.Equals(SPARE))
this._frameHandler[iFrameNo].ThrowBall(false, true);
return true;
} else {
TextBox txtBoxPrevThrow = this.LookUpTextBox
(BowlTracker.BTHelpers.MAX_FRAMES,
string.Format(FIRSTTHROW_ANY_FRAME,
BowlTracker.BTHelpers.MAX_FRAMES));
//
if (!txtBoxPrevThrow.Text.Equals(STRIKE)) {
// Disable the third
TextBox txtBoxLastFrameLastThrow = this.LookUpTextBox
(BowlTracker.BTHelpers.MAX_FRAMES, THIRDTHROW_LAST_FRAME);
if (txtBoxLastFrameLastThrow != null)
txtBoxLastFrameLastThrow.Enabled = false;
}
}
int iSecondThrow = int.Parse(txtBox.Text);
this._frameHandler[iFrameNo].ThrowBall(iSecondThrow);
return true;
}
}
return false;
}
Again, this function in Figure 11, above, has a similar signature as shown in Figure 10....
In the function HandleSecondThrow
returns a boolean which will determine if it's ok to advance onwards to the next input.
The parameter TextBox
's Name
property gets passed through regexp to determine which throw and which frame is this textbox under, we now also can reference the appropriate frame by using the indexer to the FrameHandler
class to manipulate the frame. A check is made here to see which frame we are on.
- Frames 1-9 inclusive:
- If a spare was used, then it marks the frame's appropriate throw - ...Gasp! Oh dear! Did I just see a label
Throw2ndSpare
....OMG!...read on...relax... - Since this is the second throw, it is impossible to have a strike here and hence calls
InvalidThrow
. - We check the previous throw and determine if the pins knocked on this throw plus the previous throw is equal to ten then we... GASP! ARGC! ARGV! Did I just see the unspeakable code there
goto
...??? well, there we are, we need to jump back up to the labelThrow2ndSpare
to replace the text box with a / to indicate a spare. Hence the usage of the unspeakably sloppygoto
! :) Otherwise we just, merely set the number of pins for this throw.
- If a spare was used, then it marks the frame's appropriate throw - ...Gasp! Oh dear! Did I just see a label
- Last Frame - We check if a spare or a strike was entered and set the frame accordingly, otherwise, we check the previous throw, and if that is not a strike, then this frame will have two throws so we disable the third input box.
I can promise you that the last part of this before we wrap up will be shorter...
private bool HandleLastThrow(TextBox txtBox) {
string sName = txtBox.Name;
Match m = this._reTextInputName.Match(sName);
if (m.Success) {
int iFrameNo = int.Parse(m.Groups[REGEXP_FRAMENO].Value);
int iThrowNo = int.Parse(m.Groups[REGEXP_THROWNO].Value);
if (iThrowNo == 3) {
if (txtBox.Text.Equals(STRIKE)) {
this._frameHandler[iFrameNo].ThrowBall(true, false);
return true;
}
if (txtBox.Text.Equals(SPARE)) {
this._frameHandler[iFrameNo].ThrowBall(false, true);
return true;
}
// Anything else here is number by default
int iVal = int.Parse(txtBox.Text);
this._frameHandler[iFrameNo].ThrowBall(iVal);
return true;
}
}
return false;
}
HandleLastThrow
is far more simpler and again, similar signature to the previous two highlighted above. And the checking is simpler, since if we're executing in this function, then it must accept either a number or a spare or a strike and return back true
to the event handler txtBoxThrowThree_KeyUp
.
That wasn't so bad, was it...Now I hope you have a better understanding of the game of Ten pin Bowling and how the scoring works. The real scoring machines that you see in a real bowling alley are more complex than this. As, it would have, sensors and electronical-what-nots all over the place to be able to calculate as the game-play advances along and can tell instantly the score. Now, you know why you cannot go backwards i.e. playing on frame 6, then go back to a frame previously and shove in something into the input, but realistically speaking, that should be impossible since game play has advanced...but then again, this code can be easily modified to cater for that as I'm sure that there are sophisticated ways of achieving that in a real bowling alley to "resolve" scoring "conflicts"...
Points of Interest
Heh! Taking cue from this template, I never understood the scoring in bowling. And also, when I realized the scoring wasn't coming out correctly - I spent a day trying to figure out where or what or how it happened, as I pointed out above earlier on when talking about the pseudo-code in Figure 5 above.
All in all, it was good fun - Simple as that! :) Now, where did I leave my bowling ball & shoes...
Since I am very keen on Mono, I thought I'd include the screenshot of the calculator for your pleasure!

Happy bowling and may you get the "Perfect game" next time! ;)
References
Sources used...
- All about Ten Pin Bowling on Wikipedia
- Dia for Windows
- Open-Sourced Flash based bowling calculator
- Open-Sourced Javascript based bowling calculator
History
- 7th August, 2009 - First release