connect4 project

We just got our new project for CS121, and I have a couple of questions. Our assignment is to write a connect4 game. I've figured out the logic, which isn't that bad, and written most of the game in a single .cpp, using functions. I first initialize a two dimensional char array with an 'empty board', like this:
1
2
3
4
5
6
7
8
9
const int ROWS=7;
const int COLUMNS=16;
char board[ROWS][COLUMNS]={{'6','|','_','|','_','|','_','|','_','|','_','|','_','|','_','|'},
{'5','|','_','|','_','|','_','|','_','|','_','|','_','|','_','|'},
{'4','|','_','|','_','|','_','|','_','|','_','|','_','|','_','|'},
{'3','|','_','|','_','|','_','|','_','|','_','|','_','|','_','|'},
{'2','|','_','|','_','|','_','|','_','|','_','|','_','|','_','|'},
{'1','|','_','|','_','|','_','|','_','|','_','|','_','|','_','|'},
{' ','|','A','|','B','|','C','|','D','|','E','|','F','|','G','|'}};


Then call a function to display it, and get this
6|_|_|_|_|_|_|_|
5|_|_|_|_|_|_|_|
4|_|_|_|_|_|_|_|
3|_|_|_|_|_|_|_|
2|_|_|_|_|_|_|_|
1|_|_|_|_|_|_|_|
|A|B|C|D|E|F|G| //these are lined up properly in the game
During the game, the array gets 'populated' with pieces, and after each move, a function checks for a winner.
What I really would like to do, to make this work better, and to prepare for our next exam, is to make this work as a class. That way, when I create a new instance of the class,
Connect4 newgame;
it will contain the array, and then I can call member functions to newgame.playpiece(), newgame.checkForWinner(), etc.
What I'm not sure about is how to initialize the array...do I initialize it as a private member variable, or initialize it in the constructor?
When I cut and paste the array definition into a private variable, I get errors.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

class Connect4
{
	private:
		
		static const int ROWS=7;
		static const int COLUMNS=16;
		char board[7][16]={{'6','|','_','|','_','|','_','|','_','|','_','|','_','|','_','|'},
{'5','|','_','|','_','|','_','|','_','|','_','|','_','|','_','|'},
{'4','|','_','|','_','|','_','|','_','|','_','|','_','|','_','|'},
{'3','|','_','|','_','|','_','|','_','|','_','|','_','|','_','|'},
{'2','|','_','|','_','|','_','|','_','|','_','|','_','|','_','|'},
{'1','|','_','|','_','|','_','|','_','|','_','|','_','|','_','|'},
{' ','|','A','|','B','|','C','|','D','|','E','|','F','|','G','|'}
};
	public:
		
	void Connect4::displayBoard()
	{
	for (unsigned int rowCount=0;rowCount<ROWS;rowCount++)
	    {
	     for (unsigned int columnCount=0;columnCount<COLUMNS;columnCount++)
		cout << board[rowCount][columnCount];
		
	     cout << endl;
	    }//for
	}//displayBoard
};//class Connect4  




here are the errors
1>f:\cmpsci121\proj-03\connect4.h(18) : error C2059: syntax error : '{'
1>f:\cmpsci121\proj-03\connect4.h(18) : error C2334: unexpected token(s) preceding '{'; skipping apparent function body
1>f:\cmpsci121\proj-03\connect4.h(36) : error C2065: 'board' : undeclared identifier
1>f:\cmpsci121\proj-03\connect4test.cpp(14) : error C2065: 'connect4' : undeclared identifier

The array definition is a direct cut-and-paste from the original .cpp file, which works fine, so I don't understand why it doesn't work as a variable in the class. We just started talking about classes Monday, so I'm probably doing something silly that I just don't see. Any suggestions?
|
Here's a simplified example that demonstrates your compile error and might make it more obvious:

1
2
3
4
struct Foo {
   int x = 4;
   int y;
};


Why can't x be initialized there?

The keyword static, when used to declare a variable within a class, means that there is only one copy of that variable in the entire application, no matter how many times the class is instantiated, and each instantiation shares that copy. As an example:

1
2
3
4
5
6
7
8
9
10
11
struct Foo {
   static int x = 4;
   int y;
};

Foo f1;
Foo f2;

f1.y = 10;   // f1.y == 10, f2.y is not affected
f2.y = 12;   // f2.y == 12, f1.y is not affected (still 10)
f1::x = 5;     // f2::x == f1::x == 5 now. 


Note the different syntax also -- the "." is the member operator. It says that
y is a member of f1 and is a member of f2. The "::" is the scope operator. It
says that x is scoped with f1 and is scoped with f2. It does not say that
x is a member of either.

On a programming note, I have a couple of comments.
First, defining your board that way makes manipulating the board hard, since
the offset of column A within the board array is at index 3, and B at 5, etc.
It would be simpler if you defined an array that just held the game pieces. Then
the offset of column A is at index 0, B at 1, etc. Your way means that you either have to write functions or mathematical formulas to express the board coordinate (x,y) otherwise any changes whatsoever to the board layout will cause you to change tons of code. Basically what I'm saying is to separate the internal representation of the gameboard from the display of it.

Second, consider making an even more object-oriented design. Create a GameBoard class that just encapsulates an NxM array of pieces and has functions like IsGameOver(), WhoWon(), PutPieceInColumn(), GetPieceAtXY(), NumColumns(), NumRows(), NumPiecesInColumn(), etc. Then create another class -- Connect4 that contains as a member a GameBoard instance and actually plays the game.

If you are ambitious, write two more classes -- one to encapsulate computer AI to generate moves and another to encapsulate a human player. These two classes should have a GetNextMove() function that returns the column in which the player wishes to play a piece. Connect4 would then contain one ComputerAI instance and one HumanPlayer instance to play the game.

If you are even more ambitious, make an abstract base class called Player that ComputerAI and HumanPlayer derive from. GetNextMove() would be pure virtual in the base class. Then have Connect4 ask you if player 1 should be human or computer and if player 2 should be human or computer, and then instantiate the appropriate object.

So as to not make this project a project in AI, just have the computer player play randomly.
Last edited on
yeah, the prof told us to just have the computer play randomly, AI is just a little over our heads, I've been programming for a whole 10 weeks now.
As far as having an array just hold the pieces, that is what I really wanted to do in the first place. The problem is output. All we have been taught is cout..I have no idea how I would display just the empty board, and then add the pieces. Having the board setup as part of the array allows me to use a simple cout to display both the board and the pieces in the same stream.

Right now I'm trying to wrap my mind around the difference between the member operator and the scope operator, and why I can't get the array to initialize.. Everything tells me that creating a GameBoard class with an NxM array should be pretty easy.
The reason I used static for the constants is because of this error:

cmpsci121\proj-03\connect4.h(16) : error C2864: 'Connect4::ROWS' : only static const integral data members can be initialized within a class
1>f:\cmpsci121\proj-03\connect4.h(17) : error C2864: 'Connect4::COLUMNS' : only static const integral data members can be initialized within a class

I should really try to understand why the error occurs instead of just throwing in a 'static' to make it go away.
If I have the constants for ROW and COLUMN defined at the beginning of the .cpp, before main(), will the class still recognize them?

On a side note, I really love this sh**. I have a degree in Psych from several years ago, and at 40 never thought I would have the chance to go back to school and solve really cool problems. It's just the implementation of the solutions that are holding me back right now. Everyone else in my class is like 19, so I don't really have much help there. What's really cool is that our prof assigns projects that are doable with stuff we've learned already, but hint that if you do a little more work you can come up with a much more elegant solution.
So the answer to the question lies in the difference between declaration and instantiation. The following declares a type:

1
2
3
4
struct Foo {
   int x;
   int y;
};


It does not instantiate a variable. Hence, how can you assign x a value when no x has been instantiated?

static as you've used it says there is only one ROWS and only one COLUMNS variable no matter how many instances of Connect4 you make. Because of this, the compiler allows you to not only declare the variable but also instantiate it in the same line of code.

As for output, your output function just needs to be a little more complicated. Something like this:

1
2
3
4
5
6
7
for( int row = 0; row < ROWS; ++row )
{
   cout << row << '|';
   for( int col = 0; col < COLUMNS; ++col )
       cout << board[row][col] << '|';
   cout << endl;
}


You'll need to do a bit of modification of the function to match your code, but the general gist should give you some ideas.

If I have the constants for ROW and COLUMN defined at the beginning of the .cpp, before main(), will the class still recognize them?


ROW and COLUMN must be defined in the source file before they are first referenced. So if they are first referenced on line 100, then they have to be defined before line 100.
aha..., I think I see.
In the classes we have looked at, the variable is declared in the structure or class, but is instantiated in the constructor.
The problem I am having is that a two dimensional array has to be initialized when it is declared...
Its easy to do
1
2
3
struct Foo {
   int x;
};


and then send a value when you instantiate, like this:
Foo hello(10);
which assigns 10 to x in the constructor.
Arrays cannot be initialized that way... they have to be initialized when they are declared(I think), so I cannot declare:
struct Foo {
char gameBoard[7][16]
}
and then instantiate the array when I create a new object.

Foo newGame({...........}
etc, etc, etc.

I am SO confused....

You've got it, except that you can declare the array as above without initializing it.

This works:

1
2
3
4
5
struct Foo {
  int array[5][4];
};

Foo f;


OK, I rewrote the header file so that the array only includes the empty gameboard, and not the format of the board itself. Here is the class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

class Connect4
{	private:
		char board[6][7];
				                   
	public:
	Connect4()                         // Default constructor 
		{
			for(int rowCount=0;rowCount<ROWS;rowCount++)
				for (int columnCount=0;columnCount<COLUMNS;columnCount++)
					board[rowCount][columnCount]='_';
		}
		
	void Connect4::displayBoard()
		{  
		for(int rowCount=0;rowCount<ROWS;rowCount++)
				{
				for (int columnCount=0;columnCount<COLUMNS;columnCount++)
					cout << '|' << board[rowCount][columnCount];
				cout << '|' << endl;
				}
		cout << '|' << 'A' << '|' <<'B' << '|' << 'C' <<
			'|'<<'D'<<'|'<<'E'<<'|'<< 'F' <<'|'<<'G'<<'|' << endl;
		}
		
		
}; // End connect4 class declaration 

When I create a new instance
Connect4 newgame;
and then call
newgame.displayBoard();
I get this:

|_|_|_|_|_|_|_|
|_|_|_|_|_|_|_|
|_|_|_|_|_|_|_|
|_|_|_|_|_|_|_|
|_|_|_|_|_|_|_|
|_|_|_|_|_|_|_|
|A|B|C|D|E|F|G|
Exactly what I started with. Now I have to rewrite newgame.playPiece() to work with the new array(now it doesn't have to take into consideration all the '|' characters, just the column the player chooses to play.
Thanks for the help J, I'll let you know how it turns out.
I'm going to try this afternoon to make it work like this, and then try to implement some of the suggestions you made in your first response post, although we haven't talked about inheritance at all in class, I'm pretty fuzzy about that.


hi, we had the same assignment in AI, Ive a working solution in Java (Solution Key and my own attempted solution) which your welcome to look at maybe it will help as we had to implement many of the methods you metion.
email me at patricknevin212@gmail.com if interested...
I've written most of the game, player-vs-player,computer-vs-player, and computer-computer, as far as playing all the pieces.
What I'm working on now is the Connect4::checkForWinner() function. What I do, for example, is take the chars in the column of the last piece played, and convert all those chars to a string, so all I have to do now is find out if there are 4 consecutive chars in that string.
I'm not getting the right results with what I have. I have to check for "_" because all cells in the empty array contain those.
For example, my string might be:
"_$_%%%%"
The pieces played vary from game to game, so I can't just check for '%', I just have to figure out if any 4 consecutive chars (excluding '_') are the same within the string. Here is what I have.
1
2
3
4
5
6
7
8
9
10
11
int consecutive=1;
for (int count=0;count<checkWinnerColumn.length()-1;count++)
	{
		if (checkWinnerColumn[count]=="_")
			consecutive=0;
		else
		{
		if checkWinnerColumn[count]==checkWinnerColumn[count+1]
			consecutive++;
		}
	}

Any suggestions?
I'm a little confused for a couple of reasons.

One, there are more ways to win than just vertical -- you have to check horizontal and diagonal as well.

Two, there cannot be empty spaces in the middle of a column according to the rules.

Three, why do the pieces vary from game to game?

I would store player 1's pieces as 0x01 and player 2's pieces as 0x02 with empty spaces being 0x00. You can still convert that to a string, in which case all you would need to do is search for string( 4, 0x01 ) or string( 4, 0x02 ) (which you can do with the find method).

But translating the matrix to a string for the purposes of determining game over is probably not easier than just using the matrix in the first place.


Just to point out as well in your if/else code above you can write it like
1
2
3
4
5
6
7
8
9
int consecutive=1;
for (int count=0;count<checkWinnerColumn.length()-1;count++)
	{
		if (checkWinnerColumn[count]=="_")
			consecutive=0;
		else if (checkWinnerColumn[count]==checkWinnerColumn[count+1])
			consecutive++;
		}
	}
JS,
the pieces vary because as part of the assignment, we have to ask each player what piece he would like to play.(any character)
I will be checking horizontal and diagonal as well(i haven't quite figured out the diagonals yet, I have until tuesday), I just used the vertical as an example.
In all the cases, vert ,horiz, or diag, I want to convert the pieces to a string, so that i can use the same logic in every case to determine whether or not there are 4 consecutive pieces. That way, when I do figure out how to work with the diagonals, I can use the same code.
And yes, there wont be any spaces(or '_') in a column, but there will be in a row. I would rather not use different code to determine a winner in a column vs a winner in a row.
I think I made a cut and paste error, Mythios, you are right about that, but there is a problem with my code...

lets say that the first char is a '_'....consecutive will be 0;

then the next two are '%'....now consecutive will be two. ok so far.
now, what if the next char is a '#'? This will be the piece from the other player, but my code adds one to consecutive because it is not '_'
...big problem there.

The bottom line is, to make this work with my logic, I have to be able to check for any consecutive 4 characters in a string(with the exception of '_')


If i was you i would make a background array to store the background board with your "_" and such. Then make another one for the players objects to be ontop of that. That way you wont have to scan and remove other bits. Might change your code a bit but when i make things like this using console - i have a map array and a charater object overlay array. So when you display the overlay it will just remove the old piece off the screen. If that makes any sence lol
Ok, I see.

I would still store in my internal data structure the value 0x01 for player 1's piece and 0x02 for player 2's piece and when I go to output the board, translate 0x01 to the piece player 1 chose and 0x02 to the piece player 2 chose. You could just store each player's chosen piece in a 2-character array.

Converting to a string does allow you to implement the same code to check all directions, though you still have to write separate code to generate the strings, which means you probably don't save much. I think there are other ways to do it, but your way will work fine. What I said in my previous post still applies. If you can know what character each player is using (see 1st paragraph) then you just need to find the first occurrence of string( 4, 0x01 ) or string( 4, 0x02 ) in the string.

Does that make sense?
I understand what you said but it could be a little tricky heh - guess we'll see how he goes
I played with what I had a bit this morning before class, added a couple of things, and it now declares a winner, but not until there are 5 consecutive.

1
2
3
4
5
6
7
8
9
10
11
12
13
for (int count=0;count<6;count++)
		{
			if (checkWinnerColumn[count]=='_')
				consecutive=0;
			else if (checkWinnerColumn[count]== checkWinnerColumn[count+1])
					consecutive++;
			else consecutive=1;
			if (consecutive==4)
			{
				cout << "We have a winner!" << endl;
				system("pause");
			}
		}

The problem is that if the 4 consecutive are the 4 last positions of the string, the line:
 
else if (checkWinnerColumn[count]== checkWinnerColumn[count+1])

compares the last char in the string, 'count' to 'count+1 .I have no idea what its comparing since count+1 is past the end of the string. In any case, that condition will return false, and consecutive won't be incremented. That's why it takes 5 consecutive to win instead of 4.
I am completely certain that there are many better ways to accomplish this, and hopefully with experience I will learn, but for now I am working with a limited playbook.
The reason I would like to use the same code for all directions is that our prof hammers home on a daily basis to use reusable code, and put it in a function. Once I figure out how the logic works, I can just have a bool function that takes any string, and the number of consecutive to search for. It would return true if that number of consecutive chars is found in the string.
JS, I'm with you on storing the pieces as 0x01 and 0x02, that shouldn't be that hard, and will make it much easier to keep track of what pieces are where, but you lost me when you said that I just need to find the first occurence of string(4,0x01). I'm not sure what that means. It shouldn't be that hard to store the pieces that way, and translate to the actual characters during displayboard(), but doesn't that put me in the same boat, figuring out how to check for 4 consecutive characters?

string has the following constructor (from memory):

string( size_t count, char c )

which constructs a string containing "count" copies of c. So string( 4, 'Z' ) makes a string containing the text "ZZZZ".

string also has a member function that searches for another string within itself. Again, from memory, the function is something like:

size_type find( const string& needle, size_type start = 0 )

Which says to search for "needle" within itself, starting at position 0. The function returns the index where it found needle or npos if it didn't find it.
So

1
2
3
4
5
string haystack = "Hello world!";
string needle = "el";

cout << haystack.find( needle ) << endl;
cout << haystack.find( needle, 5 ) << endl;


outputs 1 and npos, respectively. In the first case, the string "el" is found beginning at index 1 in the string. In the second case, because the search starts at index 5, the string "el" is not found.

Topic archived. No new replies allowed.