Hi,

I have to write game Minesweeper in OOP way. I have classes:
Square which inherits to mineSquare, emptySquare, digitSquare and class Board which is responsible for initialize and control the game.

I have such code:

#include <iostream>
#include <typeinfo>
#include "time.h"

using namespace std;


class Square{
public:
	Square(){
		this->isShown = false;
		this->isFlagged = false;
	}
	bool getIsShown(){
		return this->isShown;
	}
	bool getIsFlagged(){
		return this->isFlagged;
	}
	void makeItShown(){
		this->isShown = true;
	}
	void makeItFlagged(){
		this->isFlagged = true;
	}
	void makeItUnflagged(){
		this->isFlagged = false;
	}
private:
	bool isShown;
	bool isFlagged;
};


class digitSquare : Square{
public:
	digitSquare(){
		value = 0;
	}
	int getValue(){
		return value;
	}
	void setValue(int value){
		this->value = value;
	}
private:
	int value;
};



class emptySquare : Square{

};

class mineSquare : public Square{
};

mineSquare type_mineSquare;
emptySquare type_emptySquare;
digitSquare type_digitSquare;

class Board{
public:
	Board(int xMax=10, int yMax=10, int amountOfMines=10){
		gameOver = false;
		this->xMax = xMax;
		this->yMax = yMax;
		this->amountOfMines = amountOfMines;
		Square*** tbl = new Square**[xMax];
		for(int i=0; i<xMax; i++){
			tbl[i] = new Square*[yMax];
		}
	}
	void init();
	void placeMines();
	bool isGameOver(){
		if(gameOver == true){
			unhideTable();
		}
		else{

		}
		return gameOver;
	}
private:
	void unhideNeighboringEmptySquares(int x, int y){
	} 
	void unhideTable(){
	}
	Square ***tbl;
	int xMax;
	int yMax;
	int amountOfMines;
	bool isMine(int x, int y);
	int countNeighboringMines(int x, int y);
	void showSquare(int x, int y);
	void flagSquare(int x, int y);
	bool gameOver;
	
};

void Board::init(){
	for(int i=0; i<xMax; i++){
		for(int j=0; j<yMax; j++){
			tbl[i][j]->makeItFlagged();
		}
	}
}

void Board::placeMines(){
	int amountOfMines = this->amountOfMines;
	for(amountOfMines; amountOfMines>0; amountOfMines--){
		srand((unsigned int)time(NULL));
		int xRandom = rand()%xMax;
		int yRandom = rand()%yMax;
		if(!isMine(xRandom,yRandom)){ 
			delete tbl[xRandom][yRandom];
			tbl[xRandom][yRandom] = new mineSquare();
		}
		else amountOfMines++;
	}
}

bool Board::isMine(int x, int y){
	if(typeid(*tbl[x][y]) == typeid(type_mineSquare)) return true;
	else false;
}
int Board::countNeighboringMines(int x, int y){

	int amountOfNeighboringMines = 0;

	if(x > 0){
		if(isMine(x-1, y)) amountOfNeighboringMines++;
		if(y > 0 && isMine(x-1, y-1)) amountOfNeighboringMines++;
		if((y < this->yMax-1) && isMine(x-1, y+1)) amountOfNeighboringMines++;
	}

	if(x < this->xMax-1){
		if(isMine(x+1, y)) amountOfNeighboringMines++;
		if(y > 0 && isMine(x+1, y-1)) amountOfNeighboringMines++;
		if((y < this->yMax-1) && isMine(x+1, y+1)) amountOfNeighboringMines++;
	}

	if(y > 0 && isMine(x, y-1)) amountOfNeighboringMines++;
	if((y < this->yMax-1) && isMine(x, y+1)) amountOfNeighboringMines++;
	return amountOfNeighboringMines;
}

void Board::showSquare(int x, int y){
	if(tbl[x][y]->getIsShown() == false) tbl[x][y]->makeItShown();
	if(isMine(x,y)) this->gameOver = true;
}

void Board::flagSquare(int x, int y){
	if(tbl[x][y]->getIsShown() == false){
		if(tbl[x][y]->getIsFlagged() == false){
			tbl[x][y]->makeItFlagged();
		}
		else{
			tbl[x][y]->makeItUnflagged();
		}
	}
}



int main(){
	Board* Game = new Board();
	Game->init(); // error
	Game->placeMines(); // i suppose i would get error here also

	system("PAUSE");
	return 0;
}

It's compiling without any problems. I get error (http://img218.imageshack.us/gal.php?g=pppml.jpg) when program try run method init().


Can anyone help me?

I use VC++ 2008 ;)

I can see a couple things here:
1. Have another look at Line 70:

Square*** tbl = new Square**[xMax];

This declares a new tbl variable that is local to your board constructor and is destroyed when your constructor terminates. Because it has the same name as your private variable Board::tbl, it masks it from view. Once you correct that, there will be other errors.

2. You are creating the 2-d array with 3 levels of indirection. A 2-D array only has 2 levels of indirection and your constructor only initializes to 2 levels of indirection. Then, when you call Board::Init(), you are trying to access the still un-initialized third level of indirection. As a result, you are dereferencing dangling pointers. To initialize these pointers, you either need to add another set of initializations to your constructor or change Board::Init to something like this:

void Board::init(){
  for(int i=0; i<xMax; i++){
    for(int j=0; j<yMax; j++){
      tbl[i][j] = new Square;     //notice the initialization of Level-3 indirection
      tbl[i][j]->makeItFlagged();
    }
  }
}

Sorry 'bout double, ran out of edit time...

I would not recommend changing Board::Init() as demonstrated though. It really should be handled in your constructor, that's what it's there for. Are you planning on resetting and reusing the board at the end of the game? In not, given the limited functionality of the method, I might recommend getting rid of it completely. It's performing actions that really should be performed in a constructor.

Edited 6 Years Ago by Fbody: n/a

1. Do you suggest to "move" init() and placeMines() methods to constructor or only init()?

2. I've improved my code.

Line 70:

tbl = new Square**[xMax];

Init function - the previous version was sent by my stupid mistake.

void Board::init(){
	for(int i=0; i<xMax; i++){
		for(int j=0; j<yMax; j++){
			tbl[i][j] = new Square();
		}
	}
}

Now the program doesn't hang out, but function placeMines() doesn't change type of random cells from Square to mineSquare.

void Board::placeMines(){
	int amountOfMines = this->amountOfMines;
	for(amountOfMines; amountOfMines>0; amountOfMines--){
		srand((unsigned int)time(NULL));
		int xRandom = rand()%xMax;
		int yRandom = rand()%yMax;
		if(!isMine(xRandom,yRandom)){  // it comes into loop
			delete tbl[xRandom][yRandom];
			tbl[xRandom][yRandom] = new mineSquare(); // but doesn't change the type
		}
		else amountOfMines++;
	}
}

Hmmmm..... I hadn't quite gotten that far. I had only investigated your initial query when I posted. In light of other issues that I now see, I think your issue is more complicated than you realize.

For starters, you only need to call srand() one time. Take it out of PlaceMines() and make it the first line of main(). If you don't, your program could theoretically run fast enough that all of the mines end up on the same Square because you keep resetting the RNG to the same starting value. You did account for this with your if statement, but I suspect you'll find it works better (and faster) if you move the seed statement to a more appropriate location.

I think combining placeMines() and Init() somehow would be advisable. Whether you combine them under one function or call one from the other doesn't really matter to me. From a pure speed/efficiency standpoint it would be better because it eliminates a function call, but it hurts your code's readability (which is actually pretty good). Combining these operations into one allows you to calculate and place all the Squares at the same time; which saves computations and improves speed.

The problem with the way you are doing it now is that it causes a memory leak by creating X*Y Squares, then re-assigning mineCount pointers to Square objects to mineSquare objects without destroying the existing Square objects. When that happens, you lose access to the old object(s) without destroying it/them, so that memory never gets de-allocated until the program ends. Make sure you are mindful of this while calculating and placing your digitSquares as well.

I think a combination of changes to your constructor and Init are in order:

Board(int xMax=10, int yMax=10, int amountOfMines=10) {
  gameOver = false;
  this->xMax = xMax;
  this->yMax = yMax;
  this->amountOfMines = amountOfMines;
  Square*** tbl = new Square**[xMax];
  for(int i=0; i<xMax; i++) {
    tbl[i] = new Square*[yMax];

    //add another nested for loop to initialize all the pointers to NULL/0

  }
}

void Board::Init(){

  //add a call to PlaceMines()

  for(int i=0; i<xMax; i++) {
    for(int j=0; j<yMax; j++) {
      tbl[i][j]->makeItFlagged();  //<---remove this

      // v--- ADD THIS (pseudocode)---v
      if space is NULL
        if counter function returns non-zero
          place digitSquare with return value as digit value
        else
          place emptySquare
    }
  }
}

You may need to make some changes to placeMines() as well.

Edited 6 Years Ago by Fbody: n/a

So, i've done what you said.

Board(int xMax=10, int yMax=10, int amountOfMines=10){
		this->gameOver = false;
		this->xMax = xMax;
		this->yMax = yMax;
		this->amountOfMines = amountOfMines;
		tbl = new Square**[xMax];
		for(int i=0; i<xMax; i++){
			tbl[i] = new Square*[yMax];
		}

		for(int i=0; i<xMax; i++){ //marked all pointers as NULL
			for(int j=0; j<yMax; j++){
				tbl[i][j] = NULL;
			}
		}
	}

void Board::init(){
	placeMines(); // called function
	int numberOfNeigboringMines;
	for(int i=0; i<xMax; i++){
		for(int j=0; j<yMax; j++){
			if(tbl[i][j] == NULL){ // if cell isnt mineSquare
				numberOfNeigboringMines = countNeighboringMines(i,j);
				if(numberOfNeigboringMines>0){
					tbl[i][j] = new digitSquare(); // make it digitsquare
					tbl[i][j]->setValue(numberOfNeigboringMines); // and setvalue
				}
				else{
					tbl[i][j] = new emptySquare();
				}
			}
		}
	}
}

void Board::placeMines(){
	int amountOfMines = this->amountOfMines;
	for(amountOfMines; amountOfMines>0; amountOfMines--){
		int xRandom = rand()%xMax;
		int yRandom = rand()%yMax;
		if(tbl[xRandom][yRandom]==NULL){ 
			tbl[xRandom][yRandom] = new mineSquare();
		}
		else amountOfMines++;
	}
}

I've created also a function that check type of all cells:

void Board::getStatus(){
	int counter=0;
	for(int i=0; i<xMax; i++){
		for(int j=0; j<yMax; j++){
			cout << counter << ". " << typeid(*tbl[i][j]).name() << endl;
			counter++;
		}
	}
}

Unfortunately result of this function is:

1. class Square
2. class Square
3. class Square
...............
98. class Square
99. class Square

I must admin that pointers are my "achilles heel" and I don't feel comfortable with them. I have to read more about them and try to understand them on 100%.

Edited 6 Years Ago by bejfake: n/a

They are all returning Square because you didn't dereference your pointer correctly. When dereferencing pointers stored in arrays, you must first get to the index of the pointer before you can dereference it.

This:

*anArray[i][j]

dereferences the pointer anArray, then attempts to index into the array.

This:

*(anArray[i][j])

indexes into the array, then dereferences the pointer at that array index.

Edited 6 Years Ago by Fbody: n/a

It's ashamed to admit, but I don't know how to dereference properly.

When i try something like this:

*(tbl[i][j]) = NULL;

I got error

1>minesweeper.cpp(85) : error C2679: binary '=' : no operator found which takes a right-hand operand of type 'int' (or there is no acceptable conversion)
1> minesweeper(34): could be 'Square &Square::operator =(const Square &)'
1> while trying to match the argument list '(Square, int)'

It's ashamed to admit, but I don't know how to dereference properly.

When i try something like this:

*(tbl[i][j]) = NULL;

I got error

It is extremely important that you understand the distinction between an object and a pointer to an object. I suggest you read this tutorial.

You only dereference a pointer if you need to access the information at the memory address that it points to. If you are attempting to work with or modify the pointer itself, you do not dereference it. The specific way you dereference it depends on the situation. You are already making heavy use of the "this" pointer (yes, "this" is a pointer, it's a pointer to the specific object that the statement is executing inside of) and the "arrow" dereference operator ('->') so you must have some understanding, you just don't realize it.

Where exactly is the line in question? Is it in the initialization section of your constructor? If so, you should not be dereferencing it. Please post the containing function.

Edited 6 Years Ago by Fbody: n/a

Line number 85. That one which make error.

Did you mean to reference pointer here:

cout << counter << ". " << typeid(*(tbl[i][j])).name() << endl;

?

As you see I've done it and it didn't help.

Edited 6 Years Ago by bejfake: n/a

I will try understand pointers 4 sure. Previously I had to deal with PHP and Python, where I haven't to use pointers. Here almost everything is based on them.

Where exactly is the line in question? Is it in the initialization section of your constructor? If so, you should not be dereferencing it. Please post the containing function.

Maybe let's try in another way. Tell me where am I supposed to use dereference?

I sent whole code to external server : http://paste.ideaslabs.com/show/LgmqhqHxTX
With this code I don't get any errors in compiling but result is:

1. class Square
2. class Square
......
99. class Square

Moreover I've just deleted code from line 156 to 177.

Edited 6 Years Ago by bejfake: n/a

Line number 85. That one which make error.

Did you mean to reference pointer here:

cout << counter << ". " << typeid(*(tbl[i][j])).name() << endl;

?

As you see I've done it and it didn't help.

Where are you calling getStatus? I've been digging through your code, and there are things in it that still aren't compiling properly. The errors mostly seem to be related to access levels and typing errors.

Also, I'm finding that you're not implementing your polymorphism correctly. You need to add either some type casts or some virtual functions.

Edited 6 Years Ago by Fbody: n/a

I will try understand pointers 4 sure. Previously I had to deal with PHP and Python, where I haven't to use pointers. Here almost everything is based on them.

Maybe let's try in another way. Tell me where am I supposed to use dereference?

I sent whole code to external server : http://paste.ideaslabs.com/show/LgmqhqHxTX
With this code I don't get any errors in compiling but result is:

1. class Square
2. class Square
......
99. class Square

Moreover I've just deleted code from line 156 to 177.

Did you read the link I put in the post you responded to? That should tell you alot about that. Also:

You only dereference a pointer if you need to access the information at the memory address that it points to. If you are attempting to work with or modify the pointer itself, you do not dereference it. The specific way you dereference it depends on the situation. You are already making heavy use of the "this" pointer (yes, "this" is a pointer, it's a pointer to the specific object that the statement is executing inside of) and the "arrow" dereference operator ('->') so you must have some understanding, you just don't realize it.

I'm calling getStatus in main(). Almost last line.

Okay, that's where I have it too. What compiler are you using? I'm surprised you can get this to run, it won't even compile for me... You must be making more changes than you are reporting, which is fine.

Edited 6 Years Ago by Fbody: n/a

I'm using Visual C++ 2008.

Yeah, I see that I should use virtual functions.

I think i should forget about this project until i get not only basic informations about pointers.

Thank you very much for help.

Edited 6 Years Ago by bejfake: n/a

I'm using Visual C++ 2008.

Yeah, I see that I should use virtual functions.

I think i should forget about this project until i get not only basic informations about pointers.

Thank you very much for help.

I'm using VC++ 2008 also. Yes, as currently designed you are going to need some virtual functions for this to work.

I wouldn't give up on this project, I think this is actually a really good project to get pointer practice on. However, I think you need to take the polymorphic elements out of it until you understand pointers better.

Maybe temporarily re-write your Square class so that you can simply change a few of the settings within the Square object(s) to make them different types of Squares. It's not really a good class design, because each Square would have a lot of "fluff" in it. It should work pretty well for educational purposes though.

I know that it isn't good class design, but I have to make project to school which include all basic properties of OOP such as encapsulation, inheritance, polymorphism, so I had to invent some distribution of classes :)

Maybe you're right, I will focus on it again, but not today. In few days I will response here for sure. See ya and thank you again for help :)

This article has been dead for over six months. Start a new discussion instead.