• FEATURED

View more

View more

View more

### Image of the Day Submit

IOTD | Top Screenshots

### The latest, straight to your Inbox.

Subscribe to GameDev.net Direct to receive the latest updates and exclusive content.

# Simple Console based Minesweeper game

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

5 replies to this topic

Posted 17 June 2013 - 02:36 PM

Recently I've gotten to thinking about what it would take to implement a simple console based minesweeper game, what data structures and algorithms would be needed for a bare-minimum playable game.  No variations on game size, difficult, etc.  No scoring or timing.  No visual aids for the player beyond revealing appropriate portions of the map.

This weekend, I had a few spare hours, and took a crack at such an implementation.  It actually ended up being easier than I had expected (took about an hour and a half total).  Wound up being a bit over a hundred lines of code.  The sources below are actually over 200 lines, but I spent half an hour liberally commenting the code for posting.  I also set myself an additional task of utilizing the STL as much as possible in this mini-project as well, which I think went a long way in keeping the line count down.  I was not intentionally trying to keep line count down though.

Hope this is an appropriate place to post this.  I don't have any questions to ask, any problems I need assistance with.  Just wanted to toss this out there for others to check out, if they wish.  I'm certainly open to any comments, criticisms, or questions though.

So, without further ado:

#include <iostream>
#include <vector>
#include <queue>
#include <utility>
#include <random>
#include <ctime>

using namespace std;

//matrix is a 2d vector of chars, used for both our map and our mask
typedef vector<vector<char> > matrix;

//below are our minesweeper map params
const int x_dim = 10, y_dim = 10, numMines = 10;

/********************************************
Add a mine to our map
returns true if mine added
false if mine wasn't added (attempted to add a mine to a location that already had one)
also updates proximity values in the map as well
*********************************************/
{
//Generate a random location for this mine
int x = rand() % x_dim, y = rand() % y_dim;

//Add the mine, if one isn't already there
if(map[x][y] != '*')
{
map[x][y] = '*';
//walk through all neighboring locations and increment their proximity counts
for(int dx = x-1; dx <= x+1; dx++)
for(int dy = y-1; dy <= y+1; dy++)
if(dx >= 0 && dx < x_dim && dy >= 0 && dy < y_dim)
if(map[dx][dy] != '*') //Don't update proximity count for mine locations
if(map[dx][dy] == '.')
map[dx][dy] = '1'; //initial proximity, set to 1
else
map[dx][dy]++; //otherwise, increment current count (works even with chars so long as count never exceeds 9)
return true;
}

//random location already contains a mine
return false;
}

/*****************************************************************
reveals the specified location of the map/mask
expands areas that are not in proximity to a mine as well
returns true if a mine was revealed (ie game over)
******************************************************************/
bool revealLocation(matrix& map, matrix& mask, int x, int y)
{
//reveal the location

//if location is a mine, game over, just return true
if(map[x][y] == '*')
return true;

//If our location isn't in proximity to a mine
//we reveal all neighboring locations
//dots indicate no neighboring mine
if(map[x][y] == '.')
{
//openLocations holds neighboring locations that also
//are not in proximity to a mine
queue<pair<int, int> > openLocations;
openLocations.push(make_pair(x, y));

//Walk through all dot locations and reveal their neighboars
while(!openLocations.empty())
{
//Get the next location from our queue
pair<int, int> next = openLocations.front();

//The two for loops iterate over a 3x3 block within our map
//surrounding the point next.  It will check the point itself
//as well, which is redundant, but we hardly need highly
//optimized code here
for(int dx = next.first-1; dx <= next.first+1; dx++)
{
for(int dy = next.second-1; dy <= next.second+1; dy++)
{
//Let's make sure the current location is within the
//bounds of our map.  If next is an edge location, then
//we'll be iterating over some points outside the map
//So just ignore those points
if(dx >= 0 && dx < x_dim && dy >= 0 && dy < y_dim)
{
//if this neighbor is a dot location and hasn't
//previously been revealed, add it to our list
if(map[dx][dy] == '.' && mask[dx][dy] == '#')
openLocations.push(make_pair(dx, dy));

//reveal this neighboring location

}
}
}
//We're done with the current location in our queue, so we can remove it
openLocations.pop();
}
}

return false;
}

/******************************************************
returns a count of the remaining masked locations
used to determine if only mines are masked (ie our win condition)
*******************************************************/
{
int count = 0;
for(int x = 0; x < x_dim; x++)
for(int y = 0; y < y_dim; y++)
if(mask[x][y] == '#') count++;

return count;
}

int main()
{
srand((unsigned int)time(NULL));

//map contains our mines and proximity values
//Only unmasked locations of map will be visible though
//as tracked by our following mask matrix
matrix map;

//mask tracks revealed locations
//# char indicates unrevealed locations
//. char indicated revealed locations

//Generate our map and mask
for(int i = 0; i < x_dim; i++)
{
map.push_back(vector<char>(y_dim, '.'));
}
int mineCount = 0;

//loop to add mines until we have a full minefield
do
{
mineCount++;
}while(mineCount != numMines);

int x_in, y_in;
do
{
//Display the masked minefield
//output column indices first
cout << "  0123456789" << endl;
cout << "  ----------" << endl;

//loop through our rows
for(int x = 0; x < x_dim; x++)
{
//output each row index before the row itself
cout << x << "|";

//loop through all the columns for this row
for(int y = 0; y < y_dim; y++)
{
//if location is still masked, display mask char
//otherwise display the underlying map value
cout << '#';
else
cout << map[x][y];
}
cout << endl;
}

//Wait for user input to reveal a location
cin >> x_in >> y_in;

//pass the specified location to our revealLocation function
//revealLocation returns true if a mine was unmasked
if(revealLocation(map, mask, x_in, y_in))
{
cout << "You set off a mine.  Game over!" << endl;
break;
}

//Count our masked location, if == numMines, then only mines are left
//and player has won the game
{
cout << "You have found all the mines.  Congratulations!" << endl;
break;
}

}while(1);

map.clear();

cout << endl;

system("pause");
return 0;
}



### #2Paradigm Shifter  Members

Posted 17 June 2013 - 02:44 PM

I wrote a unix based minesweeper when I was learning C.

I see you are calculating the score of neighbouring mines every time you add one. Don't do that, just do it when a tile without a mine is revealed by the player. Another reason to do it that way is that the game cheats if you pick a mine on the first turn and moves it somewhere else ;)

I used recursion rather than a queue for opening multiple zero squares. Your way is fine though.

EDIT: Everyone on my course moaned at me until I put in a "mark square" ability as well, I used *a4 syntax for that. It was popular in the class everyone used to play it ;) I had variable size and number of bombs too, either as arguments to main (so you could do minesweeper 10 10 20 for a 10 x 10 board with 20 mines), or asked you for the values if you didn't use the command line arguments. If I ran out of letters I went aa, bb, cc, dd, etc. EDIT2: I see you use numbers for both rows and columns though. I allowed a4 or 4a to be valid input, the letters were the rows and the numerals specified columns.

I notice you don't do any error checking on the bounds of the input either. You'll want to do that!

Edited by Paradigm Shifter, 17 June 2013 - 03:02 PM.

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

Posted 17 June 2013 - 03:11 PM

I was more interested in just getting the core gameplay implemented as a short exercise.  If I progress with it any further at all, it will be a shift to a full graphical UI.  I've never really been a fan of cin based input error checking.  I find it rather cumbersome, but then I guess that can probably be said about many/most forms of error checking.

EDIT:  Now that I think about it more though, I like your idea of using an alphanumeric combination as opposed to a double numeric for rows columns, and I'm thinking I'll get input in a string and play around with regex's for parsing. Regex's may be a bit overkill, but I don't get a lot of real opportunities to use them, and I'm not all that proficient with them as a result.  Plus they're certainly more interesting than checking the state of cin, clearing, and ignoring.

Edited by jHaskell, 17 June 2013 - 03:17 PM.

### #4Paradigm Shifter  Members

Posted 17 June 2013 - 03:16 PM

Yeah, I would have gone on and done a GUI version if we weren't using a command line based unix system. It's pretty easy to do if you use fixed size button controls for the cells.

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

### #5luveti  Members

Posted 17 June 2013 - 03:22 PM

I just copy and pasted your code into MSVC++ to check it out and noticed that you don't clear the screen before updating the screen, an easy way to do this on windows is to call system("cls"); this provides a much more user friendly experience

This is where I added it in to your code:

...
//Wait for user input to reveal a location
cin >> x_in >> y_in;
system("cls");
...

Cool little project though!

Edited by xDarkShadowKnightx, 17 June 2013 - 03:23 PM.

### #6Paradigm Shifter  Members

Posted 17 June 2013 - 03:31 PM

EDIT:  Now that I think about it more though, I like your idea of using an alphanumeric combination as opposed to a double numeric for rows columns, and I'm thinking I'll get input in a string and play around with regex's for parsing. Regex's may be a bit overkill, but I don't get a lot of real opportunities to use them, and I'm not all that proficient with them as a result.  Plus they're certainly more interesting than checking the state of cin, clearing, and ignoring.

I used the power of sscanf I think ;) It was C rather than C++ though.

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.