Tuesday, March 15, 2005

Scoring a game of bowling

So, I have mostly finished writing the class to model a game of bowling. I have tested the game code, and it was kind of painful. While writing the code to test the scoring functions, I noticed the code base getting uglier and uglier. After feeling like most of the class was sufficiently debugged, I took a moment to reflect and realized most of the ugliness was in the test code, which made me feel a whole lot better about myself. I think I have way too fragile of an ego to post the debugging code, but since I have nothing to blog about outside the development of this application, I thought I would go ahead and post the scoring code.

Here is the definition of the class modelling a game of bowling:

class CGame : public CObject
{
public:
CGame();
virtual ~CGame();
void ScoreFrame(int nFrame);
void ScoreGame();
void ScoreTenthFrame();
void SetRegularFrame(CFrame* pFrame);
void SetTenthFrame(CTenthFrame* pTenthFrame);

#ifdef _DEBUG
int CalculateScoreByFrame(int nFrame);
void DumpGame();
void MakeFrame(int nFrame, TCHAR chFirst, TCHAR chSecond);
void MakeTenthFrame(TCHAR chFirst, TCHAR chSecond, TCHAR chThird);
void StringToGame(CString sGame);
static bool TestGame();
#endif

static const int NUMREGULARFRAMES = 9;
protected:
int m_nCompletedFrames;
int m_nScoredFrames;
int m_nScoreByFrame[NUMFRAMES];
CFrame* m_pFrames[NUMREGULARFRAMES];
CTenthFrame* m_pTenthFrame;

private:
operator=(const CGame& Game) {};
CGame(const CGame& Game) {};
};

I left the debugging member function declarations in there for an exercise to the read to imagine their ugliness. Believe me, it is probably worse than you imagine. I don't think this class is done, but it most of the way, and it is sufficiently defined to test the major points of functionality. Oh yeah, and NUMFRAMES is defined in a separate header of global constants. I hope you aren't surprised to learn its value is 10. So lets take a look at the highest level function, ScoreGame.

void CGame::ScoreGame()
{
for (int i = 0; i < NUMREGULARFRAMES; ++i)
ScoreFrame(i+1);

ScoreTenthFrame();
}

Pretty clean so far. You can't ask for too much more. So how about the function to score a non-tenth frame of bowling with a zero-based index as a parameter.

void CGame::ScoreFrame(int nFrame)
{
ASSERT(nFrame >= 1 && nFrame <= NUMREGULARFRAMES);

if (m_pFrames[nFrame-1]->IsMark())
{
m_nScoreByFrame[nFrame-1] = 10;
}
else
{
m_nScoreByFrame[nFrame-1] = m_pFrames[nFrame-1]->GetCount(1)
+ m_pFrames[nFrame-1]->GetCount(2);
}

if (nFrame >= 2)
{
if (m_pFrames[nFrame-2]->IsSpare())
{
m_nScoreByFrame[nFrame-2] += m_pFrames[nFrame-1]->GetCount(1);
}
else if (m_pFrames[nFrame-2]->IsStrike())
{
if (m_pFrames[nFrame-1]->IsStrike())
{
m_nScoreByFrame[nFrame-2] += 10;
}
else
{
m_nScoreByFrame[nFrame-2] += m_pFrames[nFrame-1]->GetCount(1)
+ m_pFrames[nFrame-1]->GetCount(2);
}
}
}

if (nFrame >= 3)
{
if (m_pFrames[nFrame-3]->IsStrike() && m_pFrames[nFrame-2]->IsStrike())
m_nScoreByFrame[nFrame-3] += m_pFrames[nFrame-1]->GetCount(1);
}
}

Still pretty clean here. Some people might bitch about the nesting level, but that is a style holy war I am not wanting to get involved in. Besides, if you have never worked in statements conditionals nested three deep, you are either some functional programming language sicko or a freshmen undergrad. So how about scoring tenth frame?

void CGame::ScoreTenthFrame()
{
// Check for double in 8th and 9th
if (m_pFrames[7]->IsStrike() && m_pFrames[8]->IsStrike())
{
m_nScoreByFrame[7] += m_pTenthFrame->GetCount(1);
m_nScoreByFrame[8] += m_pTenthFrame->GetCount(1)
+ m_pTenthFrame->GetCount(2);
}
else if (m_pFrames[8]->IsSpare())
{
m_nScoreByFrame[8] += m_pTenthFrame->GetCount(1);
}

m_nScoreByFrame[9] = m_pTenthFrame->GetCount(1)
+ m_pTenthFrame->GetCount(2);

if (m_pTenthFrame->IsMark())
m_nScoreByFrame[9] += m_pTenthFrame->GetCount(3);
}

This code still fairly clean. I suppose some CS professor or software architect type might ding me about the magic numbers, and the CTenthFrame class as opposed to CFinalFrame class with a member indicating the number of the final frame. But in all honesty, I have no intentions of this software being able to handle 15 frame games of bowling a la Randy Quaid in Kinpin. I don't have any inclination to make this score games of candle pin or duck pin, either, so all you Canucks, lay off. :) I guess you could bitch about the magic numbers in the ScoreFrame function as well. I have to say, I won't lose sleep if my application doesn't score games of bowling in bizarro world that number frames -5 to 5. I just don't think it is necessary to define something like:

const int NINTHFRAME = 8;

when I have it so firmly in grained that array indices are zero-based. As tangent, here is one of my favorite computer sciences quotes that undoubtedly exists to poke fun at programming language style preference holy wars:

Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration. (Stan Kelly-Bootle)

Okay, so I have hinted at underlying ugliness this entire post. It involves the functions for counting pinfall. Let's take a look at the ugliness in increasing order of ugly.

int CShot::GetCount()
{
switch (m_wResult)
{
case MISS:
case FOUL:
return 0;

case STRIKE:
return 10;

case SPARE:
return 10;

default:
int nCount = 0;
for (int i = 0; i < 10; ++i)
{
if ((m_wResult & (1 << i)) != 0)
++nCount;
}
return nCount;
}
}

Not *too* bad, but I can do better: :)

int CFrame::GetCount(int nShot)
{
ASSERT(nShot == 1 || nShot == 2);

if (nShot == 2 &&
((m_pSecondShot->GetResult() & (CShot::MISS | CShot::FOUL)) != 0))
return 0;

return (nShot == 1) ? m_pFirstShot->GetCount()
: m_pSecondShot->GetCount() - m_pFirstShot->GetCount();
}

Not terrible on its own, but now we have some layered ugliness. It is still prettier than Edward James Olmos.

int CTenthFrame::GetCount(int nShot)
{
ASSERT(nShot >= 1 && nShot <= 3);

switch (nShot)
{
case 1:
return m_pFirstShot->GetCount();

case 2:
if ((m_pSecondShot->GetResult() & (CShot::MISS | CShot::FOUL)) != 0)
return 0;

if ((m_pFirstShot->GetResult() & CShot::STRIKE) != 0)
{
return m_pSecondShot->GetCount();
}
else
{
return (m_pSecondShot->GetCount() - m_pFirstShot->GetCount());
}
case 3:
if ((m_pThirdShot->GetResult() & (CShot::MISS | CShot::FOUL)) != 0)
return 0;
else if (((m_pFirstShot->GetResult() & CShot::STRIKE) != 0)
&& ((m_pSecondShot->GetResult() & CShot::STRIKE) == 0))
return (m_pThirdShot->GetCount()-m_pSecondShot->GetCount());

return m_pThirdShot->GetCount();
}

return -1; // Unreachable without corrupting nShot
}

Now I know I have seen/worked with/written uglier code, but what bothers me about it is that the ugliness is a symptom of earlier architecture decisions I had made and, arguably, forgotten about with code developed later. But on the other hand, I feel reasonably confident about the extensible requirements of this code. I also realize it is doubtful a significant number of people. Plus, the size of this code base will not be terribly large when the application is finished. I think a developer has to know when to compromise elegance for productivity. Well, now that I seem to be going somewhere with this post, namely, appropriately evaluating the need for code quality for a situation, I feel like I am okay posting some of the uglier test code:

void CGame::MakeFrame(int nFrame, TCHAR chFirst, TCHAR chSecond)
{
TRACE(_T("MakeFrame: chFirst = %c, chSecond = %c\n"), chFirst, (chSecond) ? chSecond : _T('0'));

CShot* p1stShot = new CShot;
CShot* p2ndShot = 0;
WORD wResult = 0;
int nCount = 0;
int nFirstShotCount = 0;

switch (chFirst)
{
case _T('X'):
p1stShot->SetResult(CShot::STRIKE);
break;

case _T('1'):
case _T('2'):
case _T('3'):
case _T('4'):
case _T('5'):
case _T('6'):
case _T('7'):
case _T('8'):
case _T('9'):
nCount = static_cast(chFirst - _T('0'));
for (int i = 0; i < nCount; ++i)
wResult |= (1 << i);
p1stShot->SetResult(wResult);
break;

case _T('-'):
p1stShot->SetResult(CShot::MISS);
break;

case _T('F'):
p1stShot->SetResult(CShot::FOUL);
break;

}

m_pFrames[nFrame]->SetFirstShot(p1stShot);

switch (chSecond)
{
case _T('1'):
case _T('2'):
case _T('3'):
case _T('4'):
case _T('5'):
case _T('6'):
case _T('7'):
case _T('8'):
case _T('9'):
p2ndShot = new CShot;
nCount = static_cast(chSecond - _T('0'));
nFirstShotCount = p1stShot->GetCount();
wResult = p1stShot->GetResult();
for (int i = 0; i < nCount; ++i)
wResult |= (1 << (i+nFirstShotCount));
p2ndShot->SetResult(wResult);
break;

case _T('-'):
p2ndShot = new CShot;
p2ndShot->SetResult(CShot::MISS);
break;

case _T('F'):
p2ndShot = new CShot;
p2ndShot->SetResult(CShot::FOUL);
break;

case _T('/'):
p2ndShot = new CShot;
p2ndShot->SetResult(CShot::SPARE);
break;

}
if (p2ndShot != 0) m_pFrames[nFrame]->SetSecondShot(p2ndShot);
}

void CGame::MakeTenthFrame(TCHAR chFirst, TCHAR chSecond, TCHAR chThird)
{
TRACE(_T("MakeTenthFrame: %c, %c, %c\n"), chFirst, chSecond, (chThird) ? chThird : _T('0'));

CShot* p1stShot = new CShot;
CShot* p2ndShot = new CShot;
CShot* p3rdShot = 0;
int nCount = 0;
int nFirstShotCount = 0;
bool fMark = false;
WORD wResult = 0;

switch (chFirst)
{
case _T('X'):
fMark = true;
p3rdShot = new CShot;
p1stShot->SetResult(CShot::STRIKE);
break;

case _T('1'):
case _T('2'):
case _T('3'):
case _T('4'):
case _T('5'):
case _T('6'):
case _T('7'):
case _T('8'):
case _T('9'):
nCount = static_cast(chFirst - _T('0'));
for (int i = 0; i < nCount; ++i)
wResult |= (1 << i);
p1stShot->SetResult(wResult);
break;

case _T('-'):
p1stShot->SetResult(CShot::MISS);
break;

case _T('F'):
p1stShot->SetResult(CShot::FOUL);
break;

}

m_pTenthFrame->SetFirstShot(p1stShot);

switch (chSecond)
{
case _T('1'):
case _T('2'):
case _T('3'):
case _T('4'):
case _T('5'):
case _T('6'):
case _T('7'):
case _T('8'):
case _T('9'):
nCount = static_cast(chSecond - _T('0'));
if ((p1stShot->GetResult() & CShot::STRIKE) != 0)
{
nFirstShotCount = 0;
wResult = 0;
}
else
{
wResult = p1stShot->GetResult();
wResult &= ~(CShot::FOUL|CShot::MISS);
nFirstShotCount = p1stShot->GetCount();
}

for (int i = 0; i < nCount; ++i)
wResult |= (1 << (i+nFirstShotCount));
p2ndShot->SetResult(wResult);
break;

case _T('-'):
p2ndShot->SetResult(CShot::MISS);
break;

case _T('F'):
p2ndShot->SetResult(CShot::FOUL);
break;

case _T('/'):
fMark = true;
p3rdShot = new CShot;
p2ndShot->SetResult(CShot::SPARE);
break;

case _T('X'):
p2ndShot->SetResult(CShot::STRIKE);
break;

}

m_pTenthFrame->SetSecondShot(p2ndShot);

if (fMark)
{
switch (chThird)
{
case _T('X'):
p3rdShot->SetResult(CShot::STRIKE);
break;

case _T('1'):
case _T('2'):
case _T('3'):
case _T('4'):
case _T('5'):
case _T('6'):
case _T('7'):
case _T('8'):
case _T('9'):
if (chSecond == _T('X') || chSecond == _T('/'))
{
nCount = chThird - _T('0');
for (int i = 0; i < nCount; ++i)
wResult |= (1 << i);
p3rdShot->SetResult(wResult);
}
else
{
// Handle strike on first ball, non-strike on second
// Reuse nFirstShotCount even though we want the second
// shot count
nCount = static_cast(chThird - _T('0'));
nFirstShotCount = p2ndShot->GetCount();
wResult = p2ndShot->GetResult();
for (int i = 0; i < nCount-nFirstShotCount; ++i)
wResult |= (1 << (i+nFirstShotCount));
p3rdShot->SetResult(wResult);
}
break;

case _T('-'):
p3rdShot->SetResult(CShot::MISS);
break;

case _T('F'):
p3rdShot->SetResult(CShot::FOUL);
break;

case _T('/'):
p3rdShot->SetResult(CShot::SPARE);
break;

}
m_pTenthFrame->SetThirdShot(p3rdShot);
}
}

void CGame::StringToGame(CString sGame)
{
bool fSecondShot = false;
int nLength = sGame.GetLength();
int nFrame = 0;
TCHAR chSecond = 0;
TCHAR chThird = 0;
int nStrikes = 0;

for (int i = 0; i < NUMREGULARFRAMES; ++i)
{
m_pFrames[i] = new CFrame;
}

m_pTenthFrame = new CTenthFrame;

for (int i = 0; i < nLength; ++i)
{
TCHAR chFirst = sGame.GetAt(i);

switch (chFirst)
{
case _T('X'):
switch (nFrame)
{
case 0: case 1: case 2: case 3: case 4:
case 5: case 6: case 7: case 8:
MakeFrame(nFrame, chFirst, 0);
++nStrikes;
++nFrame;
break;

case 9:
if ((sGame.GetAt(nLength-2) == _T('/'))
|| ((sGame.GetAt(nLength-3)) == _T('X')) && (nStrikes+i == 18))
{
chSecond = sGame.GetAt(nLength-2);
chThird = sGame.GetAt(nLength-1);
}
else
{
// Get last three shots and figure out if it were all in
// the 10th
chFirst = sGame.GetAt(nLength-3);
chSecond = sGame.GetAt(nLength-2);
chThird = sGame.GetAt(nLength-1);

// A bad game of bowling (no strikes) would consist of 18
// shots leading into the 10th

// check for X followed on shot two shots before end, then
// check length of string to figure out if that strike was
// in the ninth or tenth
if (chFirst == _T('X') && (i+nStrikes < 18))
{
chFirst = chSecond;
chSecond = chThird;
chThird = 0;
}
}
MakeTenthFrame(chFirst, chSecond, chThird);
++nFrame;
break;

}
break;

case _T('-'):
case _T('F'):
case _T('1'):
case _T('2'):
case _T('3'):
case _T('4'):
case _T('5'):
case _T('6'):
case _T('7'):
case _T('8'):
case _T('9'):
switch (nFrame)
{
case 0: case 1: case 2: case 3: case 4:
case 5: case 6: case 7: case 8:
++i;
chSecond = sGame.GetAt(i);
MakeFrame(nFrame, chFirst, chSecond);
++nFrame;
break;

case 9:
++i;
chSecond = sGame.GetAt(i);
chThird = (nLength - nStrikes == 2) ? 0 : sGame.GetAt(nLength-1);
MakeTenthFrame(chFirst, chSecond, chThird);
++nFrame;
break;

}
break;

}
}
}

void CGame::DumpGame()
{
CString sFmt;
CString sBuf;
CString sResult[10];
int nScoreByFrame = 0;
bool fMarkIn10th = false;

sFmt = _T("______________________________________________________________\n");
sFmt += _T("|1 |2 |3 |4 |5 |6 |7 |8 |9 |10 |\n");
sFmt += _T("| %2s | %2s | %2s | %2s | %2s | %2s | %2s | %2s | %2s | %3s |\n");
sFmt += _T("| %3d | %3d | %3d | %3d | %3d | %3d | %3d | %3d | %3d | %3d |\n");
sFmt += _T("|_____|_____|_____|_____|_____|_____|_____|_____|_____|______|\n");

for (int i = 0; i < NUMREGULARFRAMES; ++i)
{
if (m_pFrames[i]->IsStrike())
{
// First shot was a strike
sResult[i].AppendChar(_T('X'));
}
else if (m_pFrames[i]->IsSpare())
{
if ((m_pFrames[i]->GetFirstShot()->GetResult() & CShot::MISS) != 0)
{
// First shot was a miss
sResult[i].AppendChar(_T('-'));
}
else if ((m_pFrames[i]->GetFirstShot()->GetResult()
& CShot::FOUL) != 0)
{
// First shout was a foul
sResult[i].AppendChar(_T('F'));
}
else
{
// First shot knocked down pins, but wasn't a strike
sResult[i].AppendChar(static_cast(
m_pFrames[i]->GetCount(1)) + _T('0'));
}
sResult[i].AppendChar(_T('/'));
}
else
{
// If we are in this block, we opened in the given frame
if ((m_pFrames[i]->GetFirstShot()->GetResult() & CShot::MISS) != 0)
{
// First shot was a miss
sResult[i].AppendChar(_T('-'));
}
else if ((m_pFrames[i]->GetFirstShot()->GetResult() & CShot::FOUL)
!= 0)
{
// First shot was a foul
sResult[i].AppendChar(_T('F'));
}
else
{
// First shot knocked down pins, but wasn't a strike and we
// didn't spare
sResult[i].AppendChar(static_cast(
m_pFrames[i]->GetCount(1))+_T('0'));
}

if ((m_pFrames[i]->GetSecondShot()->GetResult() & CShot::MISS) != 0)
{
// Second shot was a miss
sResult[i].AppendChar(_T('-'));
}
else if ((m_pFrames[i]->GetSecondShot()->GetResult()
& CShot::FOUL) != 0)
{
// Second shot was a foul
sResult[i].AppendChar(_T('F'));
}
else
{
// We knocked down pins on the second ball, but didn't spare
sResult[i].AppendChar(static_cast(
m_pFrames[i]->GetCount(2))+_T('0'));
}
}
}

// Now process the 10th frame, first shot
if ((m_pTenthFrame->GetFirstShot()->GetResult() & CShot::STRIKE) != 0)
{
fMarkIn10th = true;
sResult[9].AppendChar(_T('X'));
}
else if ((m_pTenthFrame->GetFirstShot()->GetResult() & CShot::MISS) != 0)
{
sResult[9].AppendChar(_T('-'));
}
else if ((m_pTenthFrame->GetFirstShot()->GetResult() & CShot::FOUL) != 0)
{
sResult[9].AppendChar(_T('F'));
}
else
{
sResult[9].AppendChar(static_cast(m_pTenthFrame->GetCount(1))
+ _T('0'));
}

// Process second shot in the tenth
if ((m_pTenthFrame->GetSecondShot()->GetResult() & CShot::SPARE) != 0)
{
fMarkIn10th = true;
sResult[9].AppendChar(_T('/'));
}
else if ((m_pTenthFrame->GetSecondShot()->GetResult()
& CShot::STRIKE) != 0)
{
sResult[9].AppendChar(_T('X'));
}
else if ((m_pTenthFrame->GetSecondShot()->GetResult()
& CShot::MISS) != 0)
{
sResult[9].AppendChar(_T('-'));
}
else if ((m_pTenthFrame->GetSecondShot()->GetResult()
& CShot::FOUL) != 0)
{
sResult[9].AppendChar(_T('F'));
}
else
{
// Handle an open in the 10th that knocked down pins on the second
// shot, or a strike followed by the first shot of a non-strike
if ((m_pTenthFrame->GetFirstShot()->GetResult() & CShot::STRIKE) != 0)
{
sResult[9].AppendChar(m_pTenthFrame->GetSecondShot()->GetCount()
+ _T('0'));
}
else
{
sResult[9].AppendChar((m_pTenthFrame->GetSecondShot()->GetCount()
- m_pTenthFrame->GetFirstShot()->GetCount() + _T('0')));
}
}

// Handle third shot in the 10th
if (fMarkIn10th)
{
if ((m_pTenthFrame->GetThirdShot()->GetResult() & CShot::STRIKE) != 0)
{
// Spared and then struck in 10th, or struck out in the 10th
sResult[9].AppendChar(_T('X'));
}
else if ((m_pTenthFrame->GetThirdShot()->GetResult() & CShot::SPARE)
!= 0)
{
// Struck then spared in the 10th
sResult[9].AppendChar(_T('/'));
}
else if ((m_pTenthFrame->GetThirdShot()->GetResult()
& CShot::MISS) != 0)
{
// Either threw a double or a spare, then a miss
sResult[9].AppendChar(_T('-'));
}
else if ((m_pTenthFrame->GetThirdShot()->GetResult()
& CShot::FOUL) != 0)
{
// Either threw a double or a spare, then a foul
sResult[9].AppendChar(_T('F'));
}
else
{
if (((m_pTenthFrame->GetFirstShot()->GetResult() & CShot::STRIKE) != 0)
&& (m_pTenthFrame->GetSecondShot()->GetResult() & CShot::STRIKE) == 0)
sResult[9].AppendChar(m_pTenthFrame->GetThirdShot()->GetCount()
- m_pTenthFrame->GetSecondShot()->GetCount()+_T('0'));
else if ((m_pTenthFrame->GetSecondShot()->GetResult() & CShot::SPARE) != 0)
sResult[9].AppendChar(m_pTenthFrame->GetCount(3)+_T('0'));
}
}

int nScore[10];

for (int i = 0; i < 10; i++)
nScore[i] = CalculateScoreByFrame(i);

sBuf.Format(sFmt, sResult[0], sResult[1], sResult[2], sResult[3],
sResult[4], sResult[5], sResult[6], sResult[7], sResult[8], sResult[9],
nScore[0], nScore[1], nScore[2], nScore[3], nScore[4], nScore[5],
nScore[6], nScore[7], nScore[8], nScore[9]);

TRACE(sBuf);
}

And why not at this point? The mother of the ugliness:

bool CGame::TestGame()
{
CStringArray TestGames;
CArray Games;
TestGames.Add(_T("XXXXXXXXXF-"));
TestGames.Add(_T("8/8/8/8/8/8/8/8/8/F2"));
TestGames.Add(_T("9/9-9/9-9/9-9/9-9/9-"));
// 1 2 3 4 5 6 7 8 9 10
TestGames.Add(_T("9/9/9/9/9/9/9/9/9/-1"));
// 12345678910
TestGames.Add(_T("XXXXXXXXXX9/"));
// 1 2 345678910
TestGames.Add(_T("9/9/XXXXXXXXXX"));
// 1234 56 78910
TestGames.Add(_T("XXX9/X9/XXXXXX"));
TestGames.Add(_T("XXX9/XX9/XXXXX"));
TestGames.Add(_T("XXX9/9/XXXXXXX"));
TestGames.Add(_T("11111111111111111111"));
TestGames.Add(_T("22222222222222222222"));
TestGames.Add(_T("33333333333333333333"));
TestGames.Add(_T("81188118811881188118"));
// 12345678901234567890
TestGames.Add(_T("9/9/9/9/9/9/9/9/9/9/9"));
// 12 34 56 78 910
TestGames.Add(_T("XF/X-/XF/X-/XF/X"));
TestGames.Add(_T("44444444444444444444"));
TestGames.Add(_T("--------------------"));
TestGames.Add(_T("8/8/8/8/8/8/8/8/8/8/8"));
TestGames.Add(_T("7/7/7/7/7/7/7/7/7/7/7"));
TestGames.Add(_T("6/6/6/6/6/6/6/6/6/6/6"));
TestGames.Add(_T("5/5/5/5/5/5/5/5/5/5/5"));
TestGames.Add(_T("4/4/4/4/4/4/4/4/4/4/4"));
TestGames.Add(_T("3/3/3/3/3/3/3/3/3/3/3"));
TestGames.Add(_T("2/2/2/2/2/2/2/2/2/2/2"));
TestGames.Add(_T("1/1/1/1/1/1/1/1/1/1/1"));
TestGames.Add(_T("-/-/-/-/-/-/-/-/-/-/-"));
TestGames.Add(_T("9-XXXXXXXXXXX"));
TestGames.Add(_T("9/XXXXXXXXXXX"));
TestGames.Add(_T("9-9-9-9-9-9-9-9-9-9-"));
TestGames.Add(_T("XXXXXXXXXXXX"));
TestGames.Add(_T("F/X-/XF/X-/XF/X-/"));
TestGames.Add(_T("X9/XXXXXXXXXX"));

for (int i = 0; i < TestGames.GetCount(); ++i)
{
Games.Add(new CGame);
Games[i]->StringToGame(TestGames[i]);
TRACE(_T("Game %d is %s\n"), i+1, TestGames[i]);
Games[i]->ScoreGame();
Games[i]->DumpGame();
delete Games[i];
}

return true;
}

Yeah, I got bit in the ass several times because I had no function that attempted to validate a string as a game of bowling before I fed it into the code that mattered. The point is that in realistic software development environments, this could would be refactored if it were to become anything more than throw away test-to-give-the-developer-a-warm-fuzzy code.

Oh, and it is way to big of pain in the the ass to spell check a post when you have this much code. Just assume all typos were either me being tired or having a good enough grasp of the english language to take some liberties with the rules.

2 Comments:

At 10:03 PM, Blogger gus away from the metroplaza said...

Oh yeah, I meant to mention this before. I don't think you should have allowed fouls. I mean really, who fouls in bowling? I need to send me the source code for your program in order for me to look at it. I've been spoiled by syntax highlighting for too long to look at non-highlighted code.

 
At 10:08 AM, Blogger Jim B said...

Actually, Andy and some guy about got into it over a foul one week when I was gone. I think the guy fell or something.

What level of syntax highlight do you want? Just keywords and comments, or numbers and string literals, too? I have been thinking about writing my own utlity to HTMLify me some code.

 

Post a Comment

<< Home