Skip to main content
GameDev.net gamedev.net
🔒 Locked

class / function question

Started by benen Sep 4, 2006 at 4:58 AM 11 replies 2.1k views
Original Post
benen
benen
hey. I havent programmed for quite a bit of time. Just getting back into it. Ive decided to try to replicate the minesweeper flags game that you can play in msn using openGL. ive started off making a class for the squares on the board: class square{ public: int type; // 0 = BLANK; 1-8 = NUMBERED; 9 = FLAG; int visible; square() { assign(0, 0); } void assign(char squareType, int squareVisible); }; void square::assign(char squareType, int squareVisible){ type = squareType; visible = squareVisible; } I then created a function for randomly picking a set amount of squares across the board to be flags. With the following header. void generateFlags(square gameFlag[BOARD_WIDTH][BOARD_HEIGHT]){ // blah blah blah } Finally there is a function to set the values for all of the other squares. If you are familiar with the game you will know that each square that is not a flag contains a number. The number being the amount of flags that directly surround that square. void setOtherSquares(square gameFlag[BOARD_WIDTH][BOARD_HEIGHT]){ // blah blah blah } Is this the best way to go about this problem? Would it be better to create another class for the board? Also I want to have something like the following: square gameFlag[BOARD_WIDTH][BOARD_HEIGHT]; // the squares on the board generateFlags(gameFlag); setOtherSquares(gameFlag); The here problem is that i want to pass the gameFlag object into the functions as a reference. So that the functions directly alter the contents of the object but when i try to do this the compiler complains about not being able to reference arrays. Can anyone help me with a solution? Cheers Benen
ToohrVyk
ToohrVyk
Store the internal board state in a class, this way you don't have to pass the board at all (since it's a member of the object).
benen
benen
so something like the following?

class board{
public:
square gameSquares[BOARD_WIDTH][BOARD_HEIGHT];

board();
void generateFlags();
void setOtherSquares();
};

board::board(){
generateFlags();
setOtherSquares();
}

If this is what you mean. What do i do about the gameSquares array. Classes will only allow arrays of predefined sizes correct? What if i want the game board size to be able to be changed in the game?

Thanks
Benen
kkmarkowski
kkmarkowski
hello,

declaring references to arrays, e.g. int*& array[256], is illegal because the compiler is instead declaring an array of references.

they (references to arrays) are uneccessary because arrays are (in a simplified view) pointers. you dont need to pass a reference to an array to modify the data referenced by the pointer (call by reference can be done with pointers or references).

hope that this is understandable.

kind regards,
Matthias Markowski
benen
benen
hey, thanks for the reply. I'm not 100% sure that i get you. How do you suggest I write the class to allow for different sized arrays? Feel free to use the class that i posted earlier if you like.

Thanks
Benen
kkmarkowski
kkmarkowski
check out the attached example. its a rough and working class design - dont take it as the one and only right solution, its just an proposal. (the annotations do suck)

the square class uses an unsigned as internal storage for the flags and the number of surrounding bombs, as continuation of your idea. right now you could use an char for that class too, because only 7 bits are used right now (to save memory).

the board class stores its squares in an dynamic sized array and saves its bounds apart as member variables. initializing dynamic sized arrays is a little tricky, you can use a simple array too if you dereference your values with [x + y * width].

i dont know how far you are with programming, if you have problems understanding some part(s), dont hesitate to ask, ill try to explain it in a simple way.

the files should be splitted in cpp and h.

class square{public:	enum square_flag	{		// bit 1-4 store the number of adjacencies		mask		= 0x0F, // 1st - 4th bits are a bit mask to access the count		bomb		= 0x10, // 5th bit is the bomb switch		mark		= 0x20, // 6th bit is used for the mark		visible		= 0x40, // 7th bit is the visiblity		reserved	= 0x80  // 8th bit is unused	};public:	square()	{		m_uiFlags = NULL;	}public:	// set a bit to the flags	void SetFlag(square_flag flag)	{		m_uiFlags |= flag;	}public:	// clear a bit from the flags	void ClearFlag(square_flag flag)	{		m_uiFlags &= ~flag;	}public:	// check whether a flag is set	bool IsFlag(square_flag flag) const	{		return (m_uiFlags & flag) == flag;	}public:	void SetCount(unsigned uiCount)	{		// store the count in 4 reserved bits		m_uiFlags &= ~0x0F;		m_uiFlags |= uiCount & mask;	}public:	unsigned GetCount() const	{		// get the count from the 4 reserved bits		return m_uiFlags & mask;	}protected:	// saves the number of surrounding bombs 	// and flags in one unsigned using bitmasks	unsigned m_uiFlags;};

class board{public:	board(unsigned uiWidth, unsigned uiHeight)	{		// save the width and height		m_uiWidth = uiWidth;		m_uiHeight = uiHeight;		// allocate an array of square arrays		m_ppField = new square*[m_uiWidth];		// for each line of squares		for(unsigned x = 0; x < m_uiWidth; x++)			// allocate an array of squares			m_ppField[x] = new square[m_uiHeight];	}public:	~board()	{		// for each line of squares		for(unsigned x = 0; x < m_uiWidth; x++)			// delete the array of squares			delete[] m_ppField[x];		// delete the array of square arrays		delete[] m_ppField;	}public:	bool FillBySeed(unsigned uiSeed)	{		// set the random seed		srand(uiSeed);		// initialize each square		for(unsigned x = 0; x < m_uiWidth; x++)		for(unsigned y = 0; y < m_uiHeight; y++)		{			// fill the field with bombs			if(rand()%8 == 0)				// random occurance, set the bomb				m_ppField[x][y].SetFlag(square::bomb);		}		// update each square		for(unsigned x = 0; x < m_uiWidth; x++)		for(unsigned y = 0; y < m_uiHeight; y++)		{			// start with a clean count			unsigned uiCount = 0;			// check for bounds			if(x > 0 && y > 0)				// check if it is a bomb				if(m_ppField[x - 1][y - 1].IsFlag(square::bomb))					uiCount++;							// check for bounds			if(y > 0)				// check if it is a bomb				if(m_ppField[x][y - 1].IsFlag(square::bomb))					uiCount++;			// insert the other checks before the update			m_ppField[x][y].SetCount(uiCount);		}		return true;	}public:	bool RenderExample()	{		// render each square		for(unsigned x = 0; x < m_uiWidth; x++)		for(unsigned y = 0; y < m_uiHeight; y++)		{			// save a pointer for simple access			square* s = &m_ppField[x][y];			if(s->IsFlag(square::visible))			{				if(s->IsFlag(square::bomb))					// it is a bomb					DrawBomb();				else					// it is a number					DrawNumber(s->GetCount());			} else			{				if(s->IsFlag(square::mark))					// it is a mark					DrawMark();				else					// it is blank					DrawBlank();			}		}		return true;	}protected:	square** m_ppField;protected:	unsigned m_uiWidth;	unsigned m_uiHeight;};
benen
benen
wow. Thankyou for so much effort!

Ok first of all i really dont understand this:

enum square_flag
{
// bit 1-4 store the number of adjacencies

mask = 0x0F, // 1st - 4th bits are a bit mask to access the count
bomb = 0x10, // 5th bit is the bomb switch
mark = 0x20, // 6th bit is used for the mark
visible = 0x40, // 7th bit is the visiblity
reserved = 0x80 // 8th bit is unused

};

I've never worked with bits before. I understand what you are achieving by it but really dont understand what is happening. Also, what is the mask, bomb and mark for?

From the way i see it 0F = 15, 10 = 16, 20 = 32 etc. etc. so its all confusing to me sorry :( lol

The only times ive used enumeration is for something like the following:
enum weekday } Sunday, Monday, Tuesday, Wednesday, Thursday, Friday Saturday }; etc. so its going a bit over my head :)

I have a lot of questions if you dont mind answering them so i thougth i'd go bit at a time.

Thanks again
Benen

[Edited by - benen on September 6, 2006 11:23:43 PM]
kkmarkowski
kkmarkowski
the enums are really simple to understand, we just have to take a look at their values in binary.

first of all the explanation - mark is the flag which you can set on an invisible field by right clicking (at least in windows minesweeper) which doesnt affect the game, just puts a mark on a field so that you can remember that you had a suspicion that there is a bomb under the field you marked (it looks like a real world flag, but i didnt want to confuse you with that name for a flag). bomb, visible and reserved are self-explanatory..

the ~ operator is negation, & is bitwise-and, | is bitwise-or. you can google it easily, but i expect that you know that already.

mask = 0x0F = 00001111b
bomb = 0x10 = 00010000b
mark = 0x20 = 00100000b
visible = 0x40 = 01000000b
reserved = 0x80 = 10000000b

mask can be used with an binary-and to extract the adjacent bomb count from m_uiFlags with the equalation... m_uiFlags & mask == (unsigned) number of bombs around.

m_uiFlags &= ~0x0F;m_uiFlags |= uiCount & mask;


for example, this code first takes the mask and flips all bits of mask, which leads to 11110000b. using and on m_uiFlags, we clear all bits which indicated the bomb number, but left alone all flags. after that, we can use mask & uiCount to avoid an overrun of our 4 bits of the bomb number - then we add it, using the bitwise-or operator, to the m_uiFlags.
benen
benen
ok thanks for the explanation. I have been reading a fair bit about bit manipulation. I'm starting to grasp it. I havent noticed the mark function in the online game but ive never right clicked. It is a good idea anyway and I'd like to use it. Just incase you werent sure. There are no bombs in the online game. There are 51 flags and you have to find them not miss them. First to 26 wins.

Looking at the board class. I completely understand the initialisation of the dynamic array. After seeing it I realised i have used them before. Only single dimension arrays but same concept.

What I dont understand is the fillbyseed function. I need to specify 51 flags. If i'm understanding your class correctly it is giving each square a 1 in 8 chance of being a flag? Either way that is something i can easily change. The part i dont understand is the method for counting the surrounding flags.

if(x > 0 && y > 0)
// check if it is a bomb
if(m_ppField[x - 1][y - 1].IsFlag(square::bomb))
uiCount++;

// check for bounds
if(y > 0)
// check if it is a bomb
if(m_ppField[x][y - 1].IsFlag(square::bomb))
uiCount++;

this appears to only count flags if they are to the top left of the square or to the top of the square.

In the function I wrote previously I used this:

// TOP LEFT
if(x>1 && y>1)
if(m_ppField[x-1][y-1].IsFlag(square::bomb))
uiCount++;

// TOP
if(y>1)
if(m_ppField[x][y-1].IsFlag(square::bomb))
uiCount++;

// TOP RIGHT
if(x1)
if(m_ppField[x+1][y-1].IsFlag(square::bomb))
uiCount++;

// LEFT
if(x>1)
if(m_ppField[x-1][y].IsFlag(square::bomb))
uiCount++;

// RIGHT ---- STUFFING UP HERE???
if(x if(m_ppField[x+1][y].IsFlag(square::bomb))
uiCount++;

// BOTTOM LEFT
if(x>1 && y if(m_ppField[x-1][y+1].IsFlag(square::bomb))
uiCount++;

// BOTTOM
if(y if(m_ppField[x][y+1].IsFlag(square::bomb))
uiCount++;

// BOTTOM RIGHT
if(x if(m_ppField[x+1][y+1].IsFlag(square::bomb))
uiCount++;

m_ppField[x][y].SetCount(uiCount);

It worked fine previously but after putting it into your class as above it was dying where i have commented it. Program gets an error when it gets to m_ppField[15][2] for some reason. I'll keep fiddling around.

Also I used the following function to set the flags.

for(unsigned i=0; i unsigned randX, randY;

// pick random number for x position from 0 to boardWidth
randX = rand()%m_uiWidth;
randX++;

// pick random number for y position from 0 to boardHeight
randY = rand()%m_uiHeight;
randY++;

// assign position to the flag
m_ppField[randX][randY].SetFlag(square::bomb);
}

As you can see I incremented randX and randY by one. The way I work it out in my head i shouldnt have to do this but for some reason I end up with no flags on the right column and bottom row of the board if i dont. Man im so rusty after my break!!!

Thanks
Benen
benen
benen
dah, never mind. Just my bad logic lol. All fixed :)
Fruny
Fruny
I think that obsessing over the packing of the square information is very misguided considering that you aren't likely to have millions of squares. Even if you were using 4 bytes (e.g. an int) for each piece of information (count, bomb flag, mark flag, visible flag) and had a 100x100 grid, that would "only" be 160kB. Not something your computer would sweat over.

Now, if you were making a game for a cell phone, then packing would probably be beneficial but still... have you guys ever heard of bitsets?

struct square_flag{   unsigned char mask : 4;   bool bomb          : 1;   bool mark          : 1;   bool visible       : 1;};

"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Zahlman
Zahlman
0) To make a multiple-dimensioned (2 in your case), dynamically sized (i.e. not known ahead of time), rectangular (i.e. each "row" is the same length) array-like thing that's a real object (so that you can pass it around, return it from a function etc.), use [boost]::multi_array. Or if you really must do it yourself because you like pain, read this: practice safe codo-masochism.

1) Don't make a member function called 'assign()', without very special circumstances. Just do the work in the constructor (preferably with an initializer list) or (as appropriate) a copy constructor or operator= overload.

2) The number on a Minesweeper square is not a part of its state, and therefore should not be represented by a data member (except possibly as a cached value for optimization; but YAGNI applies here). In the same way that my salary is part of a description of me, but my neighbour's salary is not, what is relevant to each Square is whether it is *itself* a bombed square.

The possible states are:

- Not visible or marked; no bomb.
- Not visible or marked; bomb.
- Not visible, but marked; no bomb.
- Not visible, but marked; bomb.
- Visible; no bomb. (The square might be *rendered* with a number; but that depends on the state of *other* nearby squares.)
- Visible; bomb (i.e. exploded).

Clearly the has-bomb-ness is orthogonal, but we cannot have a visible, marked square. We could make an enumeration for the rest of the state, and do it as follows:

struct Square {  bool hasBomb;  typedef enum { HIDDEN, FLAGGED, VISIBLE } displayState;  displayState state;  Square(bool hasBomb = false, displayState state = HIDDEN) :     hasBomb(hasBomb), state(state) {}};


Then for example, we give the World a function to determine the number of bombs on neighbours, and a function to determine the graphical appearance of a square:

// This is of course a very rough sketch with lots of missing thingsclass World {  typedef boost::multi_array<square, 2> grid_t;  grid_t grid;  World(int x, int y): grid(boost::extents[x][y]);  int neighbouringBombs(int x, int y) {    int count = 0;    for (int i = x - 1; i < x + 2; ++i) {      for (int j = y - 1; j < y + 2; ++j) {        // make sure we're in bounds        if (i >= 0 && i < grid.shape()[0] && j >= 0 && j < grid.shape()[1]) {          if (grid[j].hasBomb) {            count++;          }        }      }    }    return count;  }  int appearanceOf(int x, int y) {    switch (grid[x][y].state) {      case HIDDEN:      return HIDDEN_APPEARANCE;      case FLAGGED:      return FLAGGED_APPEARANCE;      case VISIBLE:      if (grid[x][y].hasBomb) {        return EXPLODED_APPEARANCE;      } else {        return VISIBLE_APPEARANCE + neighbouringBombs(x, y);        // we use VISIBLE_APPEARANCE to represent an open square,        // VISIBLE_APPEARANCE + 1 corresponds to the image of a square with a        // '1' on it, etc.      }    }  }  void draw() {    for (int i = 0; i < grid.shape()[0]; ++i) {      for (int j = 0; j < grid.shape()[1]; ++j) {        drawSquareAt(appearanceOf(i, j),                      gridXOffset + i * TILE_SIZE,                      gridYOffset + j * TILE_SIZE);      }    }  }};
kkmarkowski
kkmarkowski
Quote:
Original post by Fruny
I think that obsessing over the packing of the square information is very misguided considering that you aren't likely to have millions of squares.


wasting memory is in nearly all cases inelegant, even though not a pain here. you have to balance... dont know if bitsets cause worser performance penalties than the logical operatations (shouldnt), but they seem to be a better solution here.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.