Optimizing Help Appreciated

Started by
11 comments, last by Zahlman 16 years, 10 months ago
Thank you reading this post. I've just made a game in a console application using C++. What I would like are some suggestions on how to make this program more optimized. It works (as far as I know), but I'm sure that you professionals out there could give me a few tips on making the code much more efficient (and critique me on what I've done!). Thanks in advance! And just some basic explanations about the game: The game's called Gomoku (also Omok in MapleStory). Two plays take turns putting down black and white pieces on the game grid. The first to connect 5 of them in a row horizontally, vertically, or diagonally wins. Here's the code:

// Gomoku (Omok) v1.0 - Username314 - June 12, 2007
// Two players take turns putting pieces on a 15 by 15 gameboard
// Connect 5 to win

#include <iostream>
using namespace std;

int x, y;
int Grid[23][23];
int Turn = 1;
int Win = 0;

class Gomoku {
  public:
    void InitializeGrid ();
	void ShowGrid();
	void Player1Prompt();
	void Player2Prompt();
	void ChangeTurn();
	void GetTurn();
	bool CheckPiece(int x, int y);
	void CheckWins(int x, int y);
	void CheckWin(int i);
	void HorizontalCheck(int x, int y);
	void VerticalCheck(int x, int y);
	void DiagonalCheck(int x, int y);
	void AfterWin();
};

void Gomoku::InitializeGrid() {
// assign 0 to all 225 positions of Grid[15][15]
	for (int x = 0; x <= 22; x++)
	{
		for (int y = 0; y <= 22; y++)
		{
			Grid[x][y] = 0;
		}
	}
}

void Gomoku::ShowGrid() {
// displays the current Gomuku grid
	for (int x = 4; x <= 18; x++)
	{
		for (int y = 4; y <= 18; y++)
		{
			if (Grid[x][y] == 0)
				cout << "-";
			if (Grid [x][y] == 1)
				cout << "X";
			if (Grid [x][y] == 2)
				cout << "O";
		}
		cout << endl;
	}
}

void Gomoku::GetTurn() {
// depending on the turn, initize prompts for players
	if (Turn == 1)
		Player1Prompt();
	else if (Turn == 2)
		Player2Prompt();
}

void Gomoku::Player1Prompt() {
	int x;
	int y;
	do {
		cout << "-Player 1 (X)- Enter x coordinate: ";
		cin >> x;
		cout << "-Player 1 (X)- Enter y coordinate: ";
		cin >> y;
		if ( (x < 0) || (x > 14)  || (y < 0) || (y > 14) )
			cout << "Invalid coordinates. Please enter again." << endl;
		if (CheckPiece (x+4, y+4) == false)
			cout << "Position already taken. Please enter again." << endl;
	} while ( (CheckPiece (x+4, y+4) == false) || (x < 0) || (x > 14)  || (y < 0) || (y > 14));
	Grid[x+4][y+4] = 1;
	ShowGrid();
	CheckWins(x+4, y+4);
}

void Gomoku::Player2Prompt() { 
	int x;
	int y;
	do {
		cout << "-Player 2 (O)- Enter x coordinate: ";
		cin >> x;
		cout << "-Player 2 (O)- Enter y coordinate: ";
		cin >> y;
		if ((x < 0) || (x > 14)  || (y < 0) || (y > 14))
			cout << "Invalid coordinates. Please enter again." << endl;
		if (CheckPiece(x+4, y+4) == false)
			cout << "Position already taken. Please enter again." << endl;
	} while ((CheckPiece(x+4, y+4) == false) || (x < 0) || (x > 14)  || (y < 0) || (y > 14));
	Grid[x+4][y+4] = 2;
	ShowGrid();
	CheckWins(x+4, y+4);
}

void Gomoku::ChangeTurn() {
// change the turns of players
	if (Turn == 1)
		Turn = 2;
	else
		Turn = 1;
}

bool Gomoku::CheckPiece(int x, int y) {
// check to see if position x,y has been occupied by a piece or not
// post: returns true if not occupied, returns false if occupied 
	if (Grid[x][y] == 0)
	{
		return(true);
	}
	else
	{
		return(false);
	}
}

void Gomoku::CheckWins(int x, int y) {
// checks for win
	HorizontalCheck(x, y);
	VerticalCheck(x, y);
	DiagonalCheck(x, y);
}

void Gomoku::HorizontalCheck(int x, int y) {
	int i = 0;
	for (int j = -4; j <= 0; j++) {
		for (int k = 0; k <= 4; k++) {
			i += Grid[x+j+k][y];
		}
		CheckWin(i);
		i = 0;
	}
}

void Gomoku::VerticalCheck(int x, int y) {
	int i = 0;
	for (int j = -4; j <= 0; j++) {
		for (int k = 0; k <= 4; k++) {
			i += Grid[x][y+j+k];
		}
		CheckWin(i);
		i = 0;
	}
}

void Gomoku::DiagonalCheck(int x, int y) {
	int i = 0;
	for (int j = -4; j <= 0; j++) {
		for (int k = 0; k <= 4; k++) {
			i += Grid[x+j+k][y+j+k];
		}
		CheckWin(i);
		i = 0;
		for (int k = 0; k <= 4; k++) {
			i += Grid[x-j-k][y+j+k];
		}
		CheckWin(i);
		i = 0;
	}
}

void Gomoku::CheckWin(int i) {
// check for winning totals
	if (i == 5)
		Win = 1;
	else if (i == 10)
		Win = 2;
}

void Gomoku::AfterWin() {
	if (Win == 1) {cout << "Player 1 wins!" << endl;}
	else if (Win == 2) {cout << "Player 2 wins!" << endl;}
}

int main ()
{	
	class Gomoku Omok;
	do {
		Omok.InitializeGrid();
		Omok.ShowGrid();
		Omok.GetTurn();
		while (Win == 0)
		{
			Omok.ChangeTurn();
			Omok.GetTurn();
		}
		Omok.AfterWin();
		char Prompt;
		cout << "Would you like to play again? (Y/N)";
		cin >> Prompt;
	} while ((Prompt == 'Y') || (Prompt == 'y'));
	system("PAUSE");
	return(0);
}

EDIT: Put the code in the correct source box for easy reading. [Edited by - Username314 on June 13, 2007 9:21:24 PM]
Advertisement
Just to add a few words: I've been reading a lot about the usefulness of pointers which use less memory. So perhaps someone can show me how they can be used in my program? (I did read about it on the "official C++ tutorial site", but how to put it into real application still puzzles me.) Also, you probably noticed my n00b class. I've just kind of realized how to use one so the structures could definitely be better. I think I need constructors?

Sorry for being a such a n00b... have fun critiquing ;)
Alright, this is just a cursory overview. First, put your code in source tags. [source][/source] This will make it more readable.

OK, some quick notes from glancing at this.

Many of your functions don't need to be public. A good rule of thumb in OO is don't make public what doesn't need to be made public.

Omok.InitializeGrid() and Omok.ShowGrid() could probably go in the constructor and have a clear grid instead.

To clean up code, you could probably memset your array to all 0's instead of running through a couple for loops.
X, Y, Grid, Turn, and Win variables should probably belong to the class, and not be global. At very least, a namespace.

Rather than having if 1, if 2, if 3..., use a switch statement.

cin is going to get you into trouble if you enter invalid values here. What if I enter "Hello World" in your cin >> int x?

PlayerPrompt1 & PlayerPrompt2 could be condensed into 1 function. PlayerPrompt(PlayerNum)

You're missing a diagonal check. There are two ways you can go diagonal.

From a quick look, your check wins can access outside of the array.

Once again, when displaying the winner, you can eliminate the if check by simply passing a variable of which person wins.

Get rid of system("pause");
Plenty of things that can be changed to make it better, but first it's time to take a step back and examine something about the tools you're using. What compiler are you using?

do {	...	char Prompt;	...} while ((Prompt == 'Y') || (Prompt == 'y'));
This should not compile. Anything you're using that doesn't put up a major red flag on that is ignoring scoping rules. [Somebody please correct me if I'm wrong about this, but this seems to be a pretty obvious case of a variable being referenced after it falls out of scope.]

Both 'player prompt' functions could be squished into a single function with a player identification variable passed to it. It'll save you some typing, keep it smaller, and reduce the redundancy, which means it is easier to change if you ever have to work on it again.

You also don't need 'class' previous to 'Gomoku' when declaring the variable Omok.
Quote:Original post by Username314
Thank you reading this post.

I've just made a game in a console application using C++. What I would like are some suggestions on how to make this program more optimized. It works (as far as I know), but I'm sure that you professionals out there could give me a few tips on making the code much more efficient (and critique me on what I've done!). Thanks in advance!


For now, the best thing is to forget you ever heard the term "optimized". Optimization is about making the program run faster or use less memory. Right now we're using hardly any memory: the grid is only 23 * 23 * 4 = 2116 bytes, and there are only a few more ints filling space beyond that. So we're talking about less than 3 kilobytes for our own variables, which is much, much, much tinier than your computer's available memory - and in fact, is much tinier than the space that will automatically be used up by the standard library for all the 'iostream' functionality. It may even be smaller than the space taken up to hold your code itself.

As for speed, the calculations you have to do are very, very, very insignificant. Think about all the cool games you have (I hope ;) ) on your computer, and how much work is involved in making all those fancy graphics. Almost all of your code's calculation consists of (a) figuring out which pixels on the screen to light up in order to draw the letters (this part is handled for you by 'iostream', in conjunction with the operating system), and (b) (which is actually probably much, much less work than (a)) checking whether a play results in a win. In fact, you already "optimized" (b) by not re-checking over the whole board for 5-in-a-row patterns with each move. Anyway, these things are tiny. They take much, much, much less time than the player will take to decide where he wants to move. :)

Instead, what we're going to do is clean up the code. Right now, it's disorganized. It repeats itself in places; it puts the important data in illogical locations; it lacks meaningful abstractions; it gives bad names to things and then relies on comments to explain what is actually meant. All of these things make it harder to maintain code, i.e. to (figure out how to) fix a bug when it's found, or to add features, or generally speaking to change any aspect of the behaviour. And that's a Bad Thing(TM). Because in the real world, things change, and you want to make your life as easy for yourself as possible. Plus, when you get to a point in your programming career where optimization *does* make a difference, you will often find that doing proper cleanup nets you some optimizations "for free". Plus, cleaning things up sometimes fixes bugs "for free" (this typically happens where you have "repeated" code that actually is slightly different in the wrong way; once you're reusing a common chunk of code, there's no way to create the "wrong difference", because there's no second chunk of code to differ from the first ;) ).

Quote:
Just to add a few words: I've been reading a lot about the usefulness of pointers which use less memory. So perhaps someone can show me how they can be used in my program? (I did read about it on the "official C++ tutorial site", but how to put it into real application still puzzles me.)


Forget about pointers for now. When you need them, you'll know.

Saying "pointers, which use less memory" is meaningless anyway; less memory than what? Certainly, a pointer uses more memory than nothing at all, because the pointer uses some memory, and nothing-at-all obviously uses none. The way that we can save memory with pointers is to use them to share data. But right now, there is no data to share.

Analogy: If there are a dozen people who want to go to a party at my house, then giving each of them a scrap of paper with my address on it (a pointer) certainly takes up less (physical) space than building a complete copy of my house for each person and putting one inside each. (Not to mention that everyone would be lonely and have a lame party, despite having the full run of "my" house.) But if
I move out before the scheduled date for the party, or if I cancel the party, all those scraps of paper are useless.

Quote:Also, you probably noticed my n00b class. I've just kind of realized how to use one so the structures could definitely be better. I think I need constructors?


This will be one of the first things I'll be touching on, yes. But just as a teaser - notice that word, structure. C++ classes evolved from C structs, i.e., structures. Right now, your class doesn't contain any data; it just has functions, that operate on some separate chunk of global data. This defeats the purpose of having the class, and is the primary example of the "putting important data in illogical places" I mentioned above.

So, a mini-assignment before we start. Consider each of your global variables, and answer:

- What is the purpose of this variable?
- Does it logically belong to (is it logically a part of) a Gomoku object?

Also, be sure that you can explain the difference between the Gomoku class and a Gomoku object (AKA an instance of the class).
OK, here's the first pass through the code. I'm fixing very little here, and just leaving comments at the places where things are changed to explain why they're done. Basically this is just to cover the basics of good style; from here on we're going to work primarily on good design.

I intend to do this interactively, i.e. at some points I will be asking you to take the next step.

// Global comment: I fixed up the spacing in a few places for consistency. Also,// it looks like you *usually* put braces on the same line as the if/while/etc.,// so I changed things so that this is *consistently* done.#include <iostream>using namespace std;// We're going to make some constants, to explain what's so special in our// program about certain numbers like 4, 15 and 23.const int WinningLineLength = 5;const int BoardSize = 15;const int WinningLineDistance = WinningLineLength - 1;const int BorderedBoardSize = BoardSize + 2 * WinningLineDistance;// Notice how we define some constants in terms of others. That way, if we// change the "board size" that we want to play on (I've heard of this game,// but on a 19x19 board), the other numbers will automatically be what they// should be.// Anyway, we then substitute these constants in for numbers in the places// where they're appropriate, and then, again, if the numbers should change// for some reason, we don't have to fix any of the code - the code// automatically uses the new values from the updated constants.int x, y;int Grid[BorderedBoardSize][BorderedBoardSize];int Turn = 1;int Win = 0;class Gomoku {	public:	void InitializeGrid ();	void ShowGrid();	void Player1Prompt();	void Player2Prompt();	void ChangeTurn();	void GetTurn();	bool CheckPiece(int x, int y);	void CheckWins(int x, int y);	void CheckWin(int i);	void HorizontalCheck(int x, int y);	void VerticalCheck(int x, int y);	void DiagonalCheck(int x, int y);	void AfterWin();};// I took out comments describing what each function does. We're going to// be either rewriting these comments later, or deciding that they aren't// needed. The existing comments had some problems anyway: spelling errors// make it harder for the person reading the code to make any use of the// comment, and in some cases the comment simply said something that wasn't// accurate.void Gomoku::InitializeGrid() {	// A note about ranges: I see that you correctly understand that an	// array[N] has indices from 0 up to N-1 inclusive. However, -1's in	// our code are ugly. It makes more sense to talk about this range as	// 0 to N, *excluding* N. This way, the "upper bound" of our range matches	// the size, and it's easier to figure out what numbers we need.	// Therefore, when we write a loop to iterate over an array, we use a	// strict less-than and compare directly to the array size, thus:	for (int x = 0; x < BorderedBoardSize; ++x) {		for (int y = 0; y < BorderedBoardSize; ++y) {			Grid[x][y] = 0;		}	}	// That shows how we use the constants, BTW.	// Oh, and just quickly note the pre-increment instead of post-increment for	// the for loops: without going into detail, this is considered idiomatic in	// C++. It's not a big deal but you should be aware of it.}void Gomoku::ShowGrid() {	// Again notice how, with strict inequality, we avoid needing weird -1's.	for (int x = WinningLineDistance; x < WinningLineDistance + BoardSize; ++x) {		for (int y = WinningLineDistance; y < WinningLineDistance + BoardSize; ++y) {			// Notice I changed the C-strings to single characters here. It's not			// a big deal, though. More importantly, I strongly recommend bracing			// your if/else blocks even when there's only a single statement - it			// avoids problems if/when you need to add more later.			// But anyway, in this case a switch statement is more appropriate:			switch (Grid[x][y]) {				case 0: cout << '-'; break;				case 1: cout << 'X'; break;				case 2: cout << 'O'; break;			} // Although later we will replace this with something even smarter :)		}		// Don't use 'endl' as a substitute for plain old newlines. It means		// something slightly different. Just use a C-string with a newline in it,		// or (as I usually prefer) a newline character:		cout << '\n';	}}void Gomoku::GetTurn() {	if (Turn == 1) {		Player1Prompt();	} else {		// Doing the else-if here is a bit silly: Turn should only ever be one		// of those two values, so it's strange to check that we have the second		// value if we don't have the first - of course we do.		// (Also notice that you did it that way in ChangeTurn(), so let's be		// consistent :) )		Player2Prompt();	}}void Gomoku::Player1Prompt() {	int x;	int y;	// I recommend not putting a space between a function name and the arguments	// when you call a function - the idea is to make function calls look	// different from if/while/etc. statements.	do {		cout << "-Player 1 (X)- Enter x coordinate: ";		cin >> x;		cout << "-Player 1 (X)- Enter y coordinate: ";		cin >> y;		// For the same reason that we use strict less-than when iterating, we		// use non-strict greater-than for range checks:		if ((x < 0) || (x >= BoardSize) || (y < 0) || (y >= BoardSize)) {			cout << "Invalid coordinates. Please enter again." << endl;		}		// It's usually a bad idea to compare to 'true' or 'false' directly;		// those keywords exist mostly so that you can *assign to* bools.		// When you make such a comparison, you're being more verbose than		// necessary - "if it is raining" reads more clearly than		// "if it is true that it is raining", and "if it is not raining"		// reads more clearly than "if it is false that it is raining" - and		// adds an opportunity to mess up (by writing = instead of ==).		if (!CheckPiece(x + WinningLineDistance, y + WinningLineDistance)) {			cout << "Position already taken. Please enter again." << endl;		}	} while ((!CheckPiece(x + WinningLineDistance, y + WinningLineDistance)) || (x < 0) || (x >= BoardSize) || (y < 0) || (y >= BoardSize));	Grid[x + WinningLineDistance][y + WinningLineDistance] = 1;	ShowGrid();	CheckWins(x + WinningLineDistance, y + WinningLineDistance);}void Gomoku::Player2Prompt() {	int x;	int y;	do {		cout << "-Player 2 (O)- Enter x coordinate: ";		cin >> x;		cout << "-Player 2 (O)- Enter y coordinate: ";		cin >> y;		if ((x < 0) || (x >= BoardSize) || (y < 0) || (y >= BoardSize)) {			cout << "Invalid coordinates. Please enter again." << endl;		}		if (!CheckPiece(x + WinningLineDistance, y + WinningLineDistance)) {			cout << "Position already taken. Please enter again." << endl;		}	} while ((!CheckPiece(x + WinningLineDistance, y + WinningLineDistance)) || (x < 0) || (x >= BoardSize) || (y < 0) || (y >= BoardSize));	Grid[x + WinningLineDistance][y + WinningLineDistance] = 2;	ShowGrid();	CheckWins(x + WinningLineDistance, y + WinningLineDistance);}void Gomoku::ChangeTurn() {	if (Turn == 1) {		Turn = 2;	} else {		Turn = 1;	}}bool Gomoku::CheckPiece(int x, int y) {	// First, a minor note: 'return' is a keyword, not a function, so there	// is no reason to put the thing-to-return in parentheses.	// But more importantly: comparing two things *already* gives you a	// boolean value - 'if' uses bools (technically, things which are	// implicitly convertible to bools). So the original logic was doing a	// whole bunch of extra work for nothing: "if x is true, then y is true;	// otherwise y is false". Why not just "y is the same as x"?	return Grid[x][y] == 0;}void Gomoku::CheckWins(int x, int y) {	HorizontalCheck(x, y);	VerticalCheck(x, y);	DiagonalCheck(x, y);}void Gomoku::HorizontalCheck(int x, int y) {	// Scope your variables as tightly as possible. Here, 'i' is only used inside	// the 'j' loop, so it makes sense to declare the variable inside the 'j'	// loop. This gives us the extra benefit that it automatically gets reset to 	// 0 every time through, so we don't have to do that separately.	// Notice that I still use non-strict less-than here, still to avoid -1's.	for (int j = -WinningLineDistance; j <= 0; ++j) {		int i = 0;		// ... but here, we're clearly checking length-many pieces, so:		for (int k = 0; k < WinningLineLength; ++k) {			i += Grid[x+j+k][y];		}		CheckWin(i);	}}void Gomoku::VerticalCheck(int x, int y) {	for (int j = -WinningLineDistance; j <= 0; ++j) {		int i = 0;		for (int k = 0; k < WinningLineLength; ++k) {			i += Grid[x][y+j+k];		}		CheckWin(i);	}}void Gomoku::DiagonalCheck(int x, int y) {	// To make things look a little more consistent, I cut this up into	// two separate loops: one to check forward-slanting diagonals and	// one for backward-slanting diagonals. My reasons for this will become	// clear later.	for (int j = -WinningLineDistance; j <= 0; ++j) {		int i = 0;		for (int k = 0; k < WinningLineLength; ++k) {			i += Grid[x+j+k][y+j+k];		}		CheckWin(i);	}		// Notice how, because of C++'s scoping rules, and the fact that we	// scoped things properly, there is no problem with calling things 'i',	// 'j' and 'k' the second time around. (If your compiler complains, it	// might be deliberately offering the wrong behaviour, in order to support	// ancient code from before the major standardization of C++ in 1998. In this	// case you should be able to consult the compiler documentation and find a	// way to get the correct behaviour, but you should probably just get a	// more recent compiler.)	for (int j = -WinningLineDistance; j <= 0; ++j) {		int i = 0;		for (int k = 0; k < WinningLineLength; ++k) {			i += Grid[x-j-k][y+j+k];		}		CheckWin(i);	}}void Gomoku::CheckWin(int i) {	// I made an educated guess about what the 5 and 10 meant here... ;)	if (i == WinningLineLength) {		Win = 1;	} else if (i == WinningLineLength * 2) {		Win = 2;	}}// You might consider using this spacing style in some other places... ;)void Gomoku::AfterWin() {	if (Win == 1) { cout << "Player 1 wins!" << endl; }	else { cout << "Player 2 wins!" << endl; }	// Again, *at the time AfterWin is called*, Win can't be 0, so I removed the	// else-if. There are things you can add to the code to make sure of this	// ('assertions': these deliberately crash your program, in a very specific	// way, if something turns out not to be true that you are "asserting" to be	// true - this is actually a very good thing, because it means that you can	// pinpoint where things went wrong, and not have the program continue on with	// nonsensical data), but for now we'll just assume we know what we're doing.}int main() {	// As someone else noted, there's no need to put 'class' here, and it's	// normal to omit it.	The idea is that a class *defines a data type* - just	// like 'int' or 'float' or 'char' - so you should be able to use the class	// name in the same way that you use those names.	Gomoku Omok;	// As someone else noted, a variable declared inside a do-while loop is	// *not* properly in scope for the while-condition, so we have to declare	// Prompt outside:	char Prompt;	do {		Omok.InitializeGrid();		Omok.ShowGrid();		Omok.GetTurn();		while (Win == 0) {			Omok.ChangeTurn();			Omok.GetTurn();		}		Omok.AfterWin();		cout << "Would you like to play again? (Y/N)";		cin >> Prompt;	} while ((Prompt == 'Y') || (Prompt == 'y'));	// Don't artificially pause your programs at the end. Learn to run it	// properly instead: from the command line, or perhaps using a batch file.}


EXERCISES:

1) The questions from the previous post.

2) How would you minimally change the code to play tic-tac-toe instead? Hint: read the comments about constants. After doing this exercise, you should have a much greater appreciation of their value ;)

3) After finishing all the assignments, make the change from exercise 2) and verify that it works. (There's actually a bug in your win-checking logic, which will probably become more apparent if you try out this exercise ;) But we will fix that later, after we have done a little work to make bugs easier to fix. ;) ) You don't need to show the changed code; in fact, throw it away when you're done the experiment.

ASSIGNMENTS:

1) Based on your answers to exercise 1), eliminate the global variables and put the appropriate data into the Gomoku class. Does the rest of the code need to change? Why or why not?

2) Remove my comments and just leave short notes for anything you feel is especially tricky or not yet fully understood.

3) Submit the results in a source box, along with any questions you have at this point (outside the source box ;) ).
The following are style changes, the optimizations aren't really needed.

bool Gomoku::CheckPiece(int x, int y) {// check to see if position x,y has been occupied by a piece or not// post: returns true if not occupied, returns false if occupied 	if (Grid[x][y] == 0)	{		return(true);	}	else	{		return(false);	}}


Should be:

bool Gomoku::CheckPiece(int x, int y) {// check to see if position x,y has been occupied by a piece or not// post: returns true if not occupied, returns false if occupied 	return (Grid[x][y] == 0);}


Drop the 'class' keyword here:
class Gomoku Omok;  -> Gomoku Omok;




Free variables go into your class under private, most of other methods go under private as well, a new method needs to be added to provide access to winning condition.
class Gomoku{public:  Gomoku()    : Turn(1)    , Win(1)  {    InitializeGrid();    ShowGrid();  }  void Play( void )  {    GetTurn();    while (Win == 0)    {	Omok.ChangeTurn();	Omok.GetTurn();    }    Omok.AfterWin();  }private:  void ChangeTurn();  void GetTurn();  void AfterWin();  void ShowGrid();  void Player1Prompt();  void Player2Prompt();  bool CheckPiece(int x, int y);  void CheckWins(int x, int y);  void CheckWin(int i);  void HorizontalCheck(int x, int y);  void VerticalCheck(int x, int y);  void DiagonalCheck(int x, int y);  int x, y;  int Grid[23][23];  int Turn = 1;  int Win = 0;}// modified main int main (){	  char Prompt;  do {    Gomoku Omok;        Omok.Play();    cout << "Would you like to play again? (Y/N)";    cin >> Prompt;  } while ((Prompt == 'Y') || (Prompt == 'y'));  system("PAUSE");  return(0);}


Rationale about the changes:
- The details of how the game is played are irrelevant. We have a class Comoku, which allows us to play a game. This is reflected in the design

- A new copy of Gomoku is created for each game. This may seem "unoptimized", but it goes better in line with OO. Gomoku is complex class, which represents "a game". As such, it becomes obsolete after the game is over. Reseting the state may prove problematic, so we simply disalow it. (Note, the class isn't really re-allocated, so there is no true cost to this. In addition, it's closer to RAII (resource acquisition is initialization), where object is fully initialized when allocated

- Additional benefit of limiting the life-cycle of Gomoku is that the constructor now initializes the board and makes everything ready.

- None of the variables used need to be global in scope. They don't even need to be visible outside of class.

- None of the methods, with exception of Play need to be visible outside either. So all of those are nicely hidden in private section of the class.

Thank you very much Zahlman and others who have replied to the thread. Zahlman, I've looked through your revised code and appreciated what you've done.

Now, before we go forward, I'd like to ask you about this code that you wrote for the class:

class Gomoku{public:  Gomoku()    : Turn(1)    , Win(1)  {    InitializeGrid();    ShowGrid();  }  void Play( void )  {    GetTurn();    while (Win == 0)    {	Omok.ChangeTurn();	Omok.GetTurn();    }    Omok.AfterWin();  }private:  void ChangeTurn();  void GetTurn();  void AfterWin();  void ShowGrid();  void Player1Prompt();  void Player2Prompt();  bool CheckPiece(int x, int y);  void CheckWins(int x, int y);  void CheckWin(int i);  void HorizontalCheck(int x, int y);  void VerticalCheck(int x, int y);  void DiagonalCheck(int x, int y);  int x, y;  int Grid[23][23];  int Turn = 1;  int Win = 0;}


This code gave me a couple of errors. I'd love to correct them but I cannot understand what
: Turn(1)
and
, Win(1)
do. If you can explain them to me I can go ahead and get rid of the errors and continue with the more "optimized" classes declaration.

Thank you!
EDIT: Oops, sorry about the double post.
Quote:Original post by Username314
Thank you very much Zahlman and others who have replied to the thread. Zahlman, I've looked through your revised code and appreciated what you've done.

Now, before we go forward, I'd like to ask you about this code that you wrote for the class:

*** Source Snippet Removed ***

This code gave me a couple of errors. I'd love to correct them but I cannot understand what
: Turn(1)
and
, Win(1)
do. If you can explain them to me I can go ahead and get rid of the errors and continue with the more "optimized" classes declaration.

Thank you!


They're a part of an initialization list. That is the part of a constructor that is used to initialize members. For built-in types like int, float and char it works pretty much the same as using a regular assignment in the constructor. Initialization lists should be preferred though since what you're conceptually doing is to initialize elements to some initial value. Also for more complex objects you may observe differences. The reason for this is that an initialization list directly calls the appropriate constructor, but with a regular assignment the default constructor is first called before the constructor body and then the operator= is used to assign the element.

So it basically replaces default construction and assignment with proper initialization and should be used whenever possible. Remember that they only works for constructors.

EDIT: Looking in the actual code I see someone added:
int Turn = 1;int Win = 0;

In the class which isn't valid C++ (I believe C# and other languages allows it though). It is exactly the same thing that initialization lists are supposed to offer; an initial value for the members. Also in case you couldn't figure it out Turn(1) means initialize Turn with the value 1.

This topic is closed to new replies.

Advertisement