Simple Minesweeper Game, Many Problems

Started by
9 comments, last by fredrikhcs 15 years, 9 months ago
[See newest posts for current problems] Hello. I'm trying to make a little minesweeper game. It's my first C++ program and game, so it's taken quite a bit of time to create, even though it is far from complete. I've run into a lot of obstacles the last few hours or so, and maybe it's just that I haven't slept for 24 hours, but I just can't seem to solve them! If you can help out that would be great. The biggest problem is that the promixity_pass function doesn't generate the correct numbers. It seems to be caused by bool mine(board&, int, int) function, which also doesn't seem to work correctly when called from main.cpp. main.cpp

#include <iostream>
#include <string>
#include <vector>

using std::cin;				using std::cout;				
using std::endl;			using std::ostream;			
using std::string;			using std::vector;		

#include "board.h"

int main() {	
	bool user_exited = false;
	int board_size_y = 8; 	
	int board_size_x = 8; 	
	int number_of_mines = 10; 
	
	while(!user_exited) {
		board minefield = create_board(board_size_y, board_size_x, number_of_mines);
		board overlay = create_board(board_size_y, board_size_x);
		update_overlay(overlay);

		bool game_over = false;
		bool made_move = false;
		
		while(!game_over) {
			// print current board
			print_board(minefield, overlay, cout);
			// print stats
			
			int move_x;
			int move_y;
			
			// get user input 
			while(!made_move) {
				
				cout << "X: ";
				cin >> move_x;
				cout << "Y: ";
				cin >> move_y;
				
				// FIXME check if input is an integer
				
				if (in_range(minefield, move_x, move_y))
					made_move = true;
				else
					made_move = false;
			}
			
			made_move = false;
			
			// stepped on a mine?
			if(!mine(minefield, move_x, move_y)) {
				update_overlay(overlay, move_x, move_y);
			} else {
				update_overlay(overlay);
				print_board(minefield, overlay, cout);
				cout << "You stepped on a mine, and were completely and utterly dismembered!" << endl; // add random msgs
				game_over = true;
			}		
		}
		// FIXME add a menu here
		user_exited = true;
	}
	
	return 0;
}
board.cpp

#include <stdexcept>
#include <iostream>
#include <cstdlib>
#include <vector>
#include <ctime>
#include "board.h"

using std::domain_error;	using std::endl;			
using std::ostream;			using std::vector;			
using std::rand;			using std::srand;

/*
	Generates and returns a squares_y * squares_x board, mines number of mines 
	on it.
*/
board create_board(const int& spots_y, const int& spots_x, int mines) {
	board field;
	row current_row;
	
	int mines_left = mines;
	int spots = spots_y * spots_x;
	int spots_left_in_row = spots_x;
	int spots_left = spots_y * spots_x;
	
	if(mines >= spots_left) 
		throw domain_error("int mines is out of range.");

	for(int y = 0; y <= spots_y; ++y) {
		current_row = create_row(spots_y, spots_x, spots_left_in_row, spots_left, mines_left);
		field.push_back(current_row);
		spots_left_in_row = spots_x;
	}
	
	proximity_pass(field);
	
	return field;
}

/* 
	Overloaded function that generates a board with no mines.
*/
board create_board(const int& spots_y, const int& spots_x) { 
	//FIXME make new create_row for this overload
	board field;
	row current_row;
	
	int no_mines = 0;
	int spots = spots_y * spots_x;
	int spots_left_in_row = spots_x;
	int spots_left = spots_y * spots_x;

	for(int y = 0; y <= spots_y; ++y) {
		current_row = create_row(spots_y, spots_x, spots_left_in_row, spots_left, no_mines);
		field.push_back(current_row);
		spots_left_in_row = spots_x;
	}
	
	return field;
}

/*
	Generates and returns a row of spots_x elements.
*/
// FIXME make it check first if there are mines left to improve performance
row create_row(const int& spots_y, const int& spots_x, int& spots_left_in_row, int& spots_left, int& mines_left) {
	row new_row;
	static int ratio = calculate_ratio(3, spots_left, mines_left);
	static int empty = 0;
	static int mine = 9;
	
	// can we afford to skip this row?
	if(row_skippable(spots_left_in_row, spots_left, mines_left)) {
		if(my_random() % 100 <= ratio) {
			while(spots_left_in_row >= 0) {
				new_row.push_back(empty);
				--spots_left;
				--spots_left_in_row;
			}
			return new_row;
		}
	}
	
	while(spots_left_in_row >= 0) {
		// can we afford to skip this spot?
		if (spot_skippable(spots_left, mines_left)) {
			// 50% chance
			if(mines_left >= 0 && my_random() % 100 <= ratio) {
				// mine
				new_row.push_back(mine);
				--spots_left;
				--spots_left_in_row;
				--mines_left;
			} else {
				// not mine
				new_row.push_back(empty);
				--spots_left;
				--spots_left_in_row;
			}
		// plant mines!
		} else {
		// function to fill remaining row?
			new_row.push_back(mine);
			--spots_left;
			--spots_left_in_row;
			--mines_left;
		}
	}
		
	return new_row;
}

/*
	Writes the number of nearby mines to each
	spot on the board.
*/
void proximity_pass(board& board) {
	int nearby_mines = 0;
	
	// iterate through all spots
	for(int y = (board.size() - board.size()) + 1; y < board.size() - 1; ++y) {
		for(int x = 1; x != (board.at(x)).size() - 1; ++x) {
			nearby_mines = 0;
						
			if(!mine(board, y, x)) {
				// left upper corner
				if(in_range(board, y - 1, x - 1)) {
					if(mine(board, y - 1, x - 1))
						++nearby_mines;
				}
			
				// one above
				if(in_range(board, y - 1, x)) {
					if(mine(board, y - 1, x))
						++nearby_mines;
				}
			
				// right upper corner
				if(in_range(board, y - 1, x + 1)) {
					if(mine(board, y - 1 , x + 1))
						++nearby_mines;
				}
			
				// one to the right
				if(in_range(board, y, x + 1)) {
					if(mine(board, y, x + 1))
						++nearby_mines;
				}
			
				// right lower corner
				if(in_range(board, y + 1, x + 1)) {
					if(mine(board, y + 1, x + 1))
						++nearby_mines;
				}
			
				// one below
				if(in_range(board, y + 1, x)) {
					if(mine(board, y + 1, x))
						++nearby_mines;
				}
			
				// left lower corner
				if(in_range(board, y + 1, x - 1)) {
					if(mine(board, y + 1, x - 1))
						++nearby_mines;
				}
			
				// one to the left
				if(in_range(board, y, x - 1)) {
					if(mine(board, y, x - 1))
						++nearby_mines;
				}

				// insert number into board
				(board.at(y)).at(x) = nearby_mines;
				std::cout << nearby_mines << std::endl;
			}
		}
	}		
}

bool row_skippable(const int& spots_left_in_row, const int& spots_left, const int& mines_left) {
	return (spots_left - spots_left_in_row > mines_left);
}

// FIXME merge these two?

bool spot_skippable(const int& spots_left, const int& mines_left) {
	return (spots_left > mines_left);
}

// FIXME faster, more accurate, less memory consuming randomization = thx
int my_random() {
	static int last = 0;
	srand(time(NULL) + last);
	last = rand();
	return last;
}

int calculate_ratio(int multiplier, int& spots_left, int& mines_left) {
	return (spots_left / mines_left) * multiplier;
}

bool mine(board& board, int x, int y) {
	return ((board.at(y)).at(x) == 9);
}

bool in_range(const board& board, int x, int y) {
	return (board.size() >= y && (board.at(0)).size() >= x);
}

/*
	Prints the board to ostream out.	
*/
// FIXME can we "update" the board here instead?
void print_board(const board& board, const board& overlay, ostream& out) {
	for(int b = board.size() - board.size(); b < board.size() - 1; ++b) {
		for(int r = 0; r != (board.at(b)).size() - 1; ++r) {
			if((overlay.at(b)).at(r) == 0)
				out << "? ";
			else {
				if((board.at(b)).at(r) == 0)
					out << "-" << " ";
				else 
					out << (board.at(b)).at(r) << " ";
			}
		}
		out << endl;
	}	
}

/*
	Unhides the spot given spot on the board.
*/
void update_overlay(board& overlay, const int& move_x, const int& move_y) {
	if(in_range)
		(overlay.at(move_y)).at(move_x) = 1;
}

// Overloaded version to show everything.
void update_overlay(board& overlay) {
	for(board::iterator b_it = overlay.begin(); b_it < overlay.end(); ++b_it) {
		for(row::iterator r_it = b_it->begin(); r_it < b_it->end(); ++r_it) {
			*r_it = 1;
		}
	}
}	
board.h

#ifndef GUARD_board_h
#define GUARD_board_h

#include <iostream>
#include <string>
#include <vector>
#include <map>

typedef std::vector<int> row; 
typedef std::vector<row> board;

bool 	row_skippable	(const int&, const int&, const int&);
bool 	spot_skippable	(const int&, const int&);
bool 	in_range	(const board&, int, int);
bool 	mine		(board&, int, int);

int 	calculate_ratio	(int, int&, int&);
int 	my_random	();

void	print_board	(const board&, const board&, std::ostream&);
void 	update_overlay	(board&, const int&, const int&);
void 	update_overlay	(board&);
void 	proximity_pass	(board&);

row 	create_row	(const int&, const int&, int&, int&, int&);
board 	create_board	(const int&, const int&, int);
board 	create_board	(const int&, const int&);
#endif
[Edited by - fredrikhcs on July 23, 2008 10:06:13 PM]
Advertisement
-1) For that much code, use source tags rather than code tags.

0) Consider using an object-oriented style to implement 'board' functionality. Instead of providing functions which take a board by reference, let the board be a class which provides member functions. That way, you can initialize it with a constructor, and get much more idiomatic usage (since you don't have to call the "setup" function"

1) Use boost::multi_array to represent a 2-dimensional grid. This lets you index in with [x][y] subscripts, but it also lets you treat the whole array as a single chunk of memory (which it is internally), which is useful for...

2) The standard library can reduce the "pick squares to plant mines" operation to a single step. It will also make sure the distribution is even (this is hard to prove about your algorithm, even if it is true, which I doubt). We simply use the standard library algorithm std::random_sample().

3)
/*	Generates and returns a squares_y * squares_x board, mines number of mines 	on it.*/board create_board(const int& spots_y, const int& spots_x, int mines) {/* 	Overloaded function that generates a board with no mines.*/board create_board(const int& spots_y, const int& spots_x) { 


This is redundant. All you need to do is use a default parameter:
board create_board(const int& spots_y, const int& spots_x, int mines = 0) {// We can just check if mines == 0 before calling the proximity_pass.}


Of course, following the previous advice, that would turn into a constructor :)

4)
	for(int y = (board.size() - board.size()) + 1; y < board.size() - 1; ++y) {		for(int x = 1; x != (board.at(x)).size() - 1; ++x) {


What are you expecting (board.size() - board.size()) to equal? :) And why are you using < the first time and != the second time? Also, just use [] for access; you can be quite sure that the numbers are not out-of-bounds, so there's no need to check for it.

5)
int my_random() {	static int last = 0;	srand(time(NULL) + last);	last = rand();	return last;}


This isn't how random number generation is supposed to work. Just call srand() at the top of main(), and use rand() thereafter. What you're doing here is duplicating the work ("re-seed based on previous value") that rand() already does, and probably doing a worse job of it. Keep in mind that time() has very low resolution (seconds), so the value it returns is not likely to change at all while you are setting up the board.

6)
bool mine(board& board, int x, int y) {	return ((board.at(y)).at(x) == 9);}


The reason this isn't working is that you pass the coordinates in (y, x) order, but re-label them as (x, y) in the function parameters, which flips them. Thus you're checking the wrong spots in the array. And again, no need for .at() here - except that if you test your game with a non-square board, you would at least get an exception here which might help alert you to the problem. ;)

7)
		if(!mine(board, y, x)) {				// left upper corner				if(in_range(board, y - 1, x - 1)) {					if(mine(board, y - 1, x - 1))						++nearby_mines;				}// etc. 7 more times


See if you can think of a way to get rid of this repetition by using more nested loops. Hint: loop over the values that are added to x and y in indexing, and skip the (0, 0) iteration using a 'continue' statement.

8)
bool in_range(const board& board, int x, int y) {	return (board.size() >= y && (board.at(0)).size() >= x);}


This is simultaneously too careful and not careful enough.

It's too careful because your code is already careful with loop indices to make sure that these situations will never happen. And because you're already being "too careful" with .at().

It's not careful enough because you don't check for negative indices as well. ;) But really, you should just get rid of it. Good code is simple, clean code that can be proven not to have these problems by reading the code.

9)
Quote:// FIXME can we "update" the board here instead?


No! Don't do that. Keep updating and rendering separate. In the long run you'll be glad you did. Maybe not on this project (since 'updating' ought to be idempotent), but you should get experience with this technique.
Hello Frederik

Zahlman makes a lot of good points (your mine placement is indeed not random). There are a lot of problems with your code.

I fixed a few of the problems so the game is now minimally functional, in that the map is displaying correctly and you can type in the coordinates of a mine and die. You might want to use it as a starting off point for implementing Zahlmans suggestions.

#include <iostream>#include <string>#include <vector>using std::cin;using std::cout;				using std::endl;using std::ostream;			using std::string;using std::vector;		#include "board.h"int main() {	  bool user_exited = false;  int board_size_y = 8; 	  int board_size_x = 8; 	  int number_of_mines = 10;   while(!user_exited) {    board minefield = create_board(board_size_y, board_size_x, number_of_mines);    board overlay = create_board(board_size_y, board_size_x);    update_overlay(overlay);    bool game_over = false;    bool made_move = false;    while(!game_over) {      // print current board      print_board(minefield, overlay, cout);      // print stats      int move_x;      int move_y;      // get user input       while(!made_move) {        cout << "X: ";        cin >> move_x;        cout << "Y: ";        cin >> move_y;        // FIXME check if input is an integer        if (in_range(minefield, move_x, move_y))          made_move = true;        else          made_move = false;      }      made_move = false;      // stepped on a mine?      if(!mine(minefield, move_x, move_y)) {        update_overlay(overlay, move_x, move_y);      } else {        update_overlay(overlay);        print_board(minefield, overlay, cout);        cout << "You stepped on a mine, and were completely and utterly dismembered!" << endl; // add random msgs        game_over = true;      }    }    // FIXME add a menu here    user_exited = true;  }  return 0;}


#include <stdexcept>#include <iostream>#include <cstdlib>#include <vector>#include <ctime>#include "board.h"using std::domain_error;using std::endl;			using std::ostream;using std::vector;			using std::rand;using std::srand;/*Generates and returns a squares_y * squares_x board, mines number of mines on it.*/board create_board(const int& spots_y, const int& spots_x, int mines) {  board field;  board overlay;  row current_row;  int mines_left = mines;  int spots = spots_y * spots_x;  int spots_left_in_row = spots_x;  int spots_left = spots_y * spots_x;  if(mines >= spots_left)     throw domain_error("int mines is out of range.");  for(int y = 0; y <= spots_y; ++y) {    current_row = create_row(spots_y, spots_x, spots_left_in_row, spots_left, mines_left);    field.push_back(current_row);    spots_left_in_row = spots_x;  }  proximity_pass(field);  return field;}/* Overloaded function that generates a board with no mines.*/board create_board(const int& spots_y, const int& spots_x) {   //FIXME make new create_row for this overload  board field;  row current_row;  int no_mines = 0;  int spots = spots_y * spots_x;  int spots_left_in_row = spots_x;  int spots_left = spots_y * spots_x;  for(int y = 0; y <= spots_y; ++y) {    current_row = create_row(spots_y, spots_x, spots_left_in_row, spots_left, no_mines);    field.push_back(current_row);    spots_left_in_row = spots_x;  }  return field;}/*Generates and returns a row of spots_x elements.*/// FIXME make it check first if there are mines left to improve performancerow create_row(const int& spots_y, const int& spots_x, int& spots_left_in_row, int& spots_left, int& mines_left) {  row new_row;  static int ratio = calculate_ratio(3, spots_left, mines_left);  static int empty = 0;  static int mine = 9;  // can we afford to skip this row?  if(row_skippable(spots_left_in_row, spots_left, mines_left)) {    if(my_random() % 100 <= ratio) {      while(spots_left_in_row >= 0) {        new_row.push_back(empty);        --spots_left;        --spots_left_in_row;      }      return new_row;    }  }  while(spots_left_in_row >= 0) {    // can we afford to skip this spot?    if (spot_skippable(spots_left, mines_left)) {      // 50% chance      if(mines_left >= 0 && my_random() % 100 <= ratio) {        // mine        new_row.push_back(mine);        --spots_left;        --spots_left_in_row;        --mines_left;      } else {        // not mine        new_row.push_back(empty);        --spots_left;        --spots_left_in_row;      }      // plant mines!    } else {      // function to fill remaining row?      new_row.push_back(mine);      --spots_left;      --spots_left_in_row;      --mines_left;    }  }  return new_row;}/*Writes the number of nearby mines to eachspot on the board.*/void proximity_pass(board& board) {  int nearby_mines;  // iterate through all spots  for (int y = 1; y < board.size() - 1; ++y) {    for (int x = 1; x < (board.at(y)).size() - 1; ++x) {      nearby_mines = 0;      if (!mine(board, x, y)) {        for (int dx = -1; dx <= 1; ++dx) {          for (int dy = -1; dy <= 1; ++dy) {            if (in_range(board, x + dx, y + dy)) {              if (mine(board, x + dx, y + dy)) ++nearby_mines;            }          }        }        // insert number into board        (board.at(y)).at(x) = nearby_mines;        //std::cout << nearby_mines << std::endl;      }    }  }}bool row_skippable(const int& spots_left_in_row, const int& spots_left, const int& mines_left) {  return (spots_left - spots_left_in_row > mines_left);}// FIXME merge these two?bool spot_skippable(const int& spots_left, const int& mines_left) {  return (spots_left > mines_left);}// FIXME faster, more accurate, less memory consuming randomization = thxint my_random() {  static int last = 0;  srand(time(NULL) + last);  last = rand();  return last;}int calculate_ratio(int multiplier, int& spots_left, int& mines_left) {  return (spots_left / mines_left) * multiplier;}bool mine(board& board, int x, int y) {  return ((board.at(y)).at(x) == 9);}bool in_range(const board& board, int x, int y) {  return (board.size() - 1 > y && (board.at(0)).size() - 1 > x && y >= 1 && x >= 1);}/*Prints the board to ostream out.	*/// FIXME can we "update" the board here instead?void print_board(board& minefield, board& overlay, ostream& out) {  for(int b = 1; b < minefield.size() - 1; ++b) {    for(int r = 1; r != (minefield.at(b)).size() - 1; ++r) {         if((overlay.at(b)).at(r) == 0)        out << "? ";      else {        if (mine(minefield, r, b)) out << "X ";        else if((minefield.at(b)).at(r) == 0) out << "- ";        else out << (minefield.at(b)).at(r) << " ";      }        }    out << endl;  }	}/*Unhides the spot given spot on the board.*/void update_overlay(board& overlay, int& move_x, int& move_y) {  if(in_range(overlay, move_x, move_y))    (overlay.at(move_y)).at(move_x) = 1;}// Overloaded version to show everything.void update_overlay(board& overlay) {  for(board::iterator b_it = overlay.begin(); b_it < overlay.end(); ++b_it) {    for(row::iterator r_it = b_it->begin(); r_it < b_it->end(); ++r_it) {      *r_it = 1;    }  }}	


#ifndef GUARD_board_h#define GUARD_board_h#include <iostream>#include <string>#include <vector>#include <map>typedef std::vector<int> row; typedef std::vector<row> board;bool 	row_skippable	(const int&, const int&, const int&);bool 	spot_skippable	(const int&, const int&);bool 	in_range	(const board&, int, int);bool 	mine		(board&, int, int);int 	calculate_ratio	(int, int&, int&);int 	my_random	();void	print_board(board& minefield, board& overlay, std::ostream& out);void 	update_overlay(board& overlay, int& move_x, int& move_y);void 	update_overlay(board& overlay);void 	proximity_pass	(board&);row 	create_row	(const int&, const int&, int&, int&, int&);board 	create_board	(const int&, const int&, int);board 	create_board	(const int&, const int&);#endif
Thanks for the advice, Zalhman, which I have some questions for.

Quote:-1) For that much code, use source tags rather than code tags.

What are source tags/code tags?

Quote:
4)

for(int y = (board.size() - board.size()) + 1; y < board.size() - 1; ++y) {
for(int x = 1; x != (board.at(x)).size() - 1; ++x) {



What are you expecting (board.size() - board.size()) to equal? :) And why are you using < the first time and != the second time? Also, just use [] for access; you can be quite sure that the numbers are not out-of-bounds, so there's no need to check for it.

I wanted a value equaling zero. :D This is what I get for not sleeping, I guess.

On the [] versus .at. I looked into other ways of accessing vectors because I was passing only a const reference (only to remove the const of a few paremeters for some reason). Is there some midground between using [] and using const references, or is no const the only way?

Quote:
/*	Generates and returns a squares_y * squares_x board, mines number of mines 	on it.*/board create_board(const int& spots_y, const int& spots_x, int mines) {/* 	Overloaded function that generates a board with no mines.*/board create_board(const int& spots_y, const int& spots_x) { 


This is redundant. All you need to do is use a default parameter:
board create_board(const int& spots_y, const int& spots_x, int mines = 0) {// We can just check if mines == 0 before calling the proximity_pass.}

Edit: Just tried that. It seems like I have to pass an argument for that parameter even when I have a default parameter for it. And for instance, if a function has a default value for a parameter, and that parameter is followed by a parameter of the same type, how could you then not call the function with an argument for that parameter? Or do you always send an argument to a default parameter, and what argument would that be -- a type specifier (like "int", I mean)?

Edit: Also, for auxiliary functions, it might be a good idea in general not to require an lval when just asking for numbers like value X and Y, or is that too wasteful?

Wiggin: I will check that code out soon. Edit: Thanks for the code. Very helpful!

[Edited by - fredrikhcs on July 22, 2008 7:21:24 AM]
Quote:Original post by fredrikhcs
Thanks for the advice, Zalhman, which I have some questions for.

Quote:-1) For that much code, use source tags rather than code tags.

What are source tags/code tags?


Please see the FAQ, which is linked at the top of every forum page (including the post form).

Quote:
On the [] versus .at. I looked into other ways of accessing vectors because I was passing only a const reference (only to remove the const of a few paremeters for some reason). Is there some midground between using [] and using const references, or is no const the only way?


[] works on const containers as well. In general, though, const is something you need to think about. It's a promise that something won't change. If the compiler complains, it's because you appear to be trying to change that something in some way. So either you are wrong, or you were wrong. :)


Quote:
Edit: Just tried that. It seems like I have to pass an argument for that parameter even when I have a default parameter for it.


Shouldn't be.

Quote:And for instance, if a function has a default value for a parameter, and that parameter is followed by a parameter of the same type, how could you then not call the function with an argument for that parameter? Or do you always send an argument to a default parameter, and what argument would that be -- a type specifier (like "int", I mean)?


You can't have any non-default parameters after the first default one in the list. Parameters from the function call are assigned left to right, and if there is a default value for everything remaining, those defaults are used.

Quote:Edit: Also, for auxiliary functions, it might be a good idea in general not to require an lval when just asking for numbers like value X and Y, or is that too wasteful?


I have no idea what you're trying to ask here.

Look, it works quite simply:

#include <iostream>void test(int x = 0, int y = 0) {  std::cout << "x: " << x << " y: " << y << std::endl;}int main() {  test(1, 2);  test(3); // this must be the x value since the arguments are filled in left to right  test();}
I'm trying to use random_sample as Zahlman suggested, but I can't get it working.

Here's the related code:
void fill_pass(board& board, int spots_x, int spots_y, int fill_symbol) {	row row;	for(int y = 0; y < spots_y; ++y) {		for(int x = 0; x < spots_x; ++x) {			if(fill_symbol == 9 && x == y)				row.push_back(fill_symbol);				row.push_back(0);			}		board.push_back(row);		row.clear();	}}board create_board(const int& spots_x, const int& spots_y, int mines) {	board new_board;	if(mines >= spots_y * spots_x) throw domain_error("int mines is out of range.");	fill_pass(new_board, spots_x, spots_y, 0);	if(mines > 0) {		board minefield;		fill_pass(minefield, 8, 8, 9);		random_sample(minefield.begin(), minefield.end(), new_board.begin(), new_board.end());		proximity_pass(new_board);		return new_board;	}	return new_board;}

These are the containers:
typedef std::vector<int> row; typedef std::vector<row> board;
Quote:Original post by Zahlman
[] works on const containers as well. In general, though, const is something you need to think about. It's a promise that something won't change. If the compiler complains, it's because you appear to be trying to change that something in some way. So either you are wrong, or you were wrong. :)

Gotcha. I'll replace the .ats with [] then.

On default arguments: It turns out I had to remove the = 0 from the function definition and put it only in the declaration.

Quote:Original post by fredrikhcs
I'm trying to use random_sample as Zahlman suggested, but I can't get it working.

Here's the related code:
*** Source Snippet Removed ***
These are the containers:
typedef std::vector<int> row; typedef std::vector<row> board;


Er, sorry, wasn't really thinking about how that was going to work. random_sample copies from a random subset of elements, whereas we want to put data into a random subset of elements. (Your existing code does nothing sensible, unfortunately, but I suppose that's only to be expected)

Fortunately, the standard library still has our back: we can shuffle the container to get the desired result. We put all the mines at the beginning, and then use std::random_shuffle to distribute them evenly and randomly.

But in any case, you really, really want 1-dimensional containers (or rather, viewable-as-1-dimensional) to work with the standard library. The boost::multi_array is exactly suited to your purposes.

#include "boost/multi_array.hpp"#include <algorithm>// and whatever elsetypedef boost::multi_array<int, 2> board;board create_board(unsigned int x, unsigned int y, unsigned int mines = 0) {  // Actually allocating the space is this easy:  board result(boost::extents[x][y]);  // Every element is now 0. If desired, we drop in some 9's for the mines:  if (mines) {    std::fill_n(result.begin(), mines, 9);    // And mix them up    std::random_shuffle(result.begin(), result.end());  }  // The if-check is only an optimization, though; it would still work just fine.}
Quote:Original post by Zahlman
Er, sorry, wasn't really thinking about how that was going to work. random_sample copies from a random subset of elements, whereas we want to put data into a random subset of elements. (Your existing code does nothing sensible, unfortunately, but I suppose that's only to be expected)

Fortunately, the standard library still has our back: we can shuffle the container to get the desired result. We put all the mines at the beginning, and then use std::random_shuffle to distribute them evenly and randomly.

But in any case, you really, really want 1-dimensional containers (or rather, viewable-as-1-dimensional) to work with the standard library. The boost::multi_array is exactly suited to your purposes.

#include "boost/multi_array.hpp"#include <algorithm>// and whatever elsetypedef boost::multi_array<int, 2> board;board create_board(unsigned int x, unsigned int y, unsigned int mines = 0) {  // Actually allocating the space is this easy:  board result(boost::extents[x][y]);  // Every element is now 0. If desired, we drop in some 9's for the mines:  if (mines) {    std::fill_n(result.begin(), mines, 9);    // And mix them up    std::random_shuffle(result.begin(), result.end());  }  // The if-check is only an optimization, though; it would still work just fine.}

The random_shuffle algorithm didn't work on the vector<vector<int> >, only inside a vector<int>, sadly. I'll try to use the boost array.

Quote:Original post by fredrikhcs

The random_shuffle algorithm didn't work on the vector<vector<int> >, only inside a vector<int>, sadly. I'll try to use the boost array.


Naturally; standard library algorithms don't know anything about the elements of their containers, they just operate on the containers. A vector<vector<int> > is a vector of vector<int>s, and it's the vector<int>s that get shuffled around.

This topic is closed to new replies.

Advertisement