/* Clase Board.
* Maneja los elementos del juego Buscaminas. Consiste en un tablero (Board en inglés)
* que tiene cuadrados (Squares).
*
*
* Includes necesarios para el funcionamiento de esta clase
*
* #include <iostream> | Para usar cout
* #include <vector> | Para usar vector
* #include <stdexcept> | Para manejo de errores */
#include "square.cpp"
#include "random_number.cpp"
class Board {
std::vector< std::vector<Square> > squares;
int width, height;
int left_mines; //Minas que quedan (va descontando cuando el usuario marca una)
int total_mines; //Cantidad de cuadrados que contienen una mina
int total_number_squares; //Cantidad de cuadrados que contienen un número
bool first_choosing;
//Métodos privados
void set_size(int width, int height);
void set_mines(int x, int y);
void set_numbers();
void show_empty_squares(int x, int y);
int count_surrounding_mines(int x, int y);
int shown_squares();
//Métodos públicos
public:
void create(int width, int height, int param_mines);
void choose_square(int x, int y);
void mark_square(int x, int y);
void mark_all_mines();
bool is_valid(int x, int y);
bool no_squares_left();
Square square(int x, int y);
inline int get_width() { return width; }
inline int get_height() { return height; }
inline int get_left_mines() { return left_mines; }
};
void Board::set_size(int param_width, int param_height) {
//Asigna el tamaño al tablero
//Condición: los argumentos deben ser positivos
if(param_width <= 0 || param_height <= 0)
throw std::runtime_error("No puedes crear un tablero con ancho o alto negativos.");
//Método de la clase vector que sirve para inicializarlo. En este caso creo un vector bidimensional con el ancho "param_width" y alto "param_height"
squares.assign(param_width, std::vector<Square>(param_height));
//Guardo el tamaño del tablero en atributos para usarlo más tarde
width = param_width;
height = param_height;
}
void Board::set_mines(int x, int y) {
//Asigna las minas de manera aleatoria a partir del atributo total_mines
//No se pone ninguna mina en el punto x, y
Random_Number random_number = Random_Number();
int x_aux;
int y_aux;
for(int i = 0; i < total_mines;) {
x_aux = random_number.get(0, width-1);
y_aux = random_number.get(0, height-1);
if( !squares[x_aux][y_aux].is_mine() && !squares[x][y].is_mine() ) {
//Si el punto aleatorio no es mina NI el punto entregado como argumento.
squares[x_aux][y_aux].set_mine();
++i;
}
}
}
void Board::set_numbers() {
//Calcula y guarda los números que van en el tablero (y que indican el número de minas que hay alrededor de un cuadrado)
for(int x = 0; x < width; ++x)
for(int y = 0; y < height; ++y) {
//Con estos dos for recorro el tablero completo (se toman todas las combinaciones posibles de x e y)
if( !squares[x][y].is_mine() )
//Si el cuadrado actual es una mina no es necesario asignarle un número.
//Si no es una mina, cuento las minas alrededor y le asigno un número.
//Recordar que x, y son las coordenadas del punto
squares[x][y].set_value( count_surrounding_mines(x, y) );
}
}
void Board::show_empty_squares(int x, int y) {
//Función recursiva que revisa si un cuadrado está vacío y lo muestra. En caso afirmativo revisa los que lo rodean y así sucesivamente.
if(is_valid(x, y) && squares[x][y].get_value() == 0 && squares[x][y].is_hidden()) {
squares[x][y].show();
for(int x_axis = x-1; x_axis <= x+1; ++x_axis)
for(int y_axis = y-1; y_axis <= y+1; ++y_axis) {
//Con estos dos for puedo moverme por los 8 cuadrados circundantes
if(is_valid(x_axis, y_axis) && squares[x_axis][y_axis].get_value() > 0 && !squares[x][y].is_mine())
//Muestro los números que hayan
squares[x_axis][y_axis].show();
show_empty_squares(x_axis, y_axis);
}
}
}
int Board::count_surrounding_mines(int x, int y) {
//Cuenta el número de minas alrededor de un punto
//Condición: el punto debe estar dentro del tabelero
if( !is_valid(x, y) )
throw std::runtime_error("No puedes contar las minas alrededor de un punto que se encuentra fuera del tablero.");
int count = 0;
for(int x_axis = x-1; x_axis <= x+1; ++x_axis)
for(int y_axis = y-1; y_axis <= y+1; ++y_axis)
/*Recorro los 9 puntos, incluyendo el del centro. Pierdo un mínimo de eficiencia, pero el código queda más ordenado. Además, sé que el número del centro no es una mina, por lo que el resultado de la función no cambia*/
if( is_valid(x_axis, y_axis) && squares[x_axis][y_axis].is_mine() )
++count;
return count;
}
int Board::shown_squares() {
int total = 0;
for(int x = 0; x < width; ++x)
for(int y = 0; y < height; ++y)
if( !squares[x][y].is_hidden() && !squares[x][y].is_mine() )
++total;
return total;
}
void Board::create(int param_width, int param_height, int param_mines) {
//Crea el tablero de buscaminas con el tamaño adecuado, asigna las minas y asigna los números.
//Condición: los argumentos deben ser positivos (se comprueba en las otras funciones)
//Condición: la cantidad de minas debe ser positiva (se comprueba aquí)
if(param_mines < 0) throw std::runtime_error("No puedes crear un tablero con un número negativo de minas");
set_size(param_width, param_height); //Asigna al tablero el tamaño adecuado
left_mines = param_mines;
total_mines = param_mines;
total_number_squares = param_width*param_height - param_mines;
first_choosing = true;
}
void Board::choose_square(int x, int y) {
//Hace que el cuadrado elegido muestre su contenido.
//Si el punto x, y está fuera del tablero, la función no hace nada.
if( is_valid(x, y) ) {
if( first_choosing ) {
//Si es la primera vez que se elige un número, pongo las minas excepto en ese lugar y luego proceso la elección.
//De esta manera se hace imposible perder en la primera jugada (es imposible elegir una mina el primer turno)
set_mines(x, y);
set_numbers();
first_choosing = false;
}
if(squares[x][y].get_value() == 0)
//Si el cuadrado elegido está vacío (sin minas ni números)
show_empty_squares(x, y);
else
//Si es un número o una mina
squares[x][y].show();
//En ambos casos, si el cuadrado estaba marcado como mina sumo uno al número de minas restantes
if( squares[x][y].is_marked() ) ++left_mines;
}
}
void Board::mark_square(int x, int y) {
//Marca un cuadrado con un signo para ayudar al usuario a identificar minas.
//La función no hace nada si el punto x, y: está fuera del tablero / no está oculto / ya está marcado
if( is_valid(x, y) && squares[x][y].is_hidden() && !squares[x][y].is_marked() ) {
squares[x][y].mark();
--left_mines;
}
}
void Board::mark_all_mines() {
//Marca todas las minas que no estén marcadas. Así, cuando el usuario gana las puede ver en el tablero.
for(int x = 0; x < width; ++x)
for(int y = 0; y < height; ++y)
if( squares[x][y].is_mine() )
squares[x][y].mark();
left_mines = 0;
}
bool Board::no_squares_left() {
//Calcula el número de cuadrados que están ocultos y no son minas. Devuelve true si no queda ninguno.
return shown_squares() == total_number_squares;
}
bool Board::is_valid(int x, int y) {
//Devuelve true si el punto está dentro del tablero y false si no lo está
return (-1 < x && x < width) && (-1 < y && y < height);
}
Square Board::square(int x, int y) {
//Función que retorna el objeto "Square" dadas las coordenadas x e y.
//Condición: las coordenadas deben apuntar a un "Square" existente.
if( !is_valid(x, y) )
throw std::runtime_error("Board::square no acepta coordenadas de puntos inexistentes.");
return squares[x][y];
}