Buscaminas en C++ con programación orientada a objetos - Tablero con minas y celdas

Buscaminas en C++ con Programación orientada a objetos (POO)

En este post vamos a programar el juego de Buscaminas (Minesweeper) en C++ o CPP usando el paradigma de la programación orientada a objetos o POO.

Anteriormente ya había mostrado este mismo juego pero en C, y aunque el código de C es compatible con C++ en este caso lo hice de nuevo y orientado a objetos en C++.

Clase celda

El juego de buscaminas se compone de un tablero y celdas. La celda puede tener o no tener una mina dentro, y puede estar descubierta o no.

En caso de que la celda esté descubierta se debe mostrar la cantidad de celdas contiguas que pueden tener una mina.

class Celda
{
private:
	int x, y;
	bool mina, descubierta;

public:
	Celda(int x, int y, bool tieneMina)
	{
		this->x = x;
		this->y = y;
		this->mina = tieneMina;
		this->descubierta = false;
	}
	void imprimir()
	{
		cout << "Celda en " << this->x << ", " << this->y << " con mina? " << this->mina << "\n";
	}

	bool establecerMina(bool tieneMina)
	{
		if (this->tieneMina())
		{
			return false;
		}
		else
		{
			this->mina = tieneMina;
			return true;
		}
	}

	bool tieneMina()
	{
		return this->mina;
	}

	bool estaDescubierta()
	{
		return this->descubierta;
	}

	void setDescubierta(bool descubierta)
	{
		this->descubierta = descubierta;
	}
};

También tiene la opción de imprimir el estado de la celda, el constructor y algunos getters y setters.

Esos métodos nos permitirán saber si la celda está descubierta, tiene mina y para colocar una mina o establecerla como descubierta.

Los getters, setters y constructores son algo esencial de la programación orientada a objetos.

Tablero de juego de Buscaminas en C++

Ahora veamos la clase Tablero que tendrá las celdas. En este caso estoy ocupando un vector de vectores compuesto de celdas (de la clase Celda).

El tablero de buscaminas tiene varios métodos, vamos a ir explicándolos. Tenemos la función de obtener la representación de una mina, que devuelve el carácter que debe mostrarse.

	string obtenerRepresentacionMina(int x, int y)
	{
		Celda c = this->contenido.at(y).at(x);
		if (c.estaDescubierta() || this->modoProgramador)
		{
			if (c.tieneMina())
			{
				return "*";
			}
			else
			{
				int cantidad = this->minasCercanas(y, x);
				string cantidadComoCadena = "";
				stringstream ss;
				ss << cantidad;
				ss >> cantidadComoCadena;
				return cantidadComoCadena;
			}
		}
		else
		{
			return ".";
		}
	}

Si la mina está descubierta se hace otra verificación. En caso de que la celda tenga una mina, se muestra un asterisco. Si no, se muestra la cantidad de celdas con minas cercanas.

En caso de que la celda no esté descubierta o el modo programador esté desactivado entonces se muestra un punto.

Minas cercanas

Necesitamos una función que nos diga cuántas minas cercanas hay en una celda. Para ello recorremos y contamos todas las celdas contiguas, evitando los límites obviamente.

	int minasCercanas(int fila, int columna)
	{
		int conteo = 0, filaInicio, filaFin, columnaInicio, columnaFin;
		if (fila <= 0)
		{
			filaInicio = 0;
		}
		else
		{
			filaInicio = fila - 1;
		}

		if (fila + 1 >= this->altura)
		{
			filaFin = this->altura - 1;
		}
		else
		{
			filaFin = fila + 1;
		}

		if (columna <= 0)
		{
			columnaInicio = 0;
		}
		else
		{
			columnaInicio = columna - 1;
		}
		if (columna + 1 >= this->anchura)
		{
			columnaFin = this->anchura - 1;
		}
		else
		{
			columnaFin = columna + 1;
		}
		int m;
		for (m = filaInicio; m <= filaFin; m++)
		{
			int l;
			for (l = columnaInicio; l <= columnaFin; l++)
			{
				if (this->contenido.at(m).at(l).tieneMina())
				{
					conteo++;
				}
			}
		}
		return conteo;
	}

Fíjate que en la línea 44 estamos usando los métodos de la celda y la programación orientada a objetos por ejemplo la función tieneMina está definida en la clase Celda.

Descubrir una mina

Tenemos la función que descubre una celda e indica si ahí había una mina o no:

/*
    Regresa false si había una mina. true en caso contrario
  */
bool descubrir(int x, int y)
{
  this->contenido.at(y).at(x).setDescubierta(true);
  Celda celda = this->contenido.at(y).at(x);
  if (celda.tieneMina())
  {
    return false;
  }
  return true;
}

En la línea 6 establecemos la celda como descubierta sin importar si tiene una mina o no. Luego comparamos si tiene mina para devolver un booleano indicando si había una mina.

Llenado del tablero

En el constructor de la clase Tablero hacemos el llenado del vector:

Tablero(int altura, int anchura, bool modoProgramador)
	{
		this->altura = altura;
		this->anchura = anchura;
		this->modoProgramador = modoProgramador;
		int x, y;
		for (y = 0; y < this->altura; y++)
		{
			vector<Celda> fila;
			for (x = 0; x < this->anchura; x++)
			{
				fila.push_back((Celda(x, y, false)));
			}
			this->contenido.push_back(fila);
		}
	}

Recuerda que con push_back colocamos un elemento al final de un vector y que este elemento puede ser de cualquier tipo. Tampoco olvides que estamos trabajando con un vector de vectores o una matriz.

Contar celdas sin minas y sin descubrir

Para saber si el jugador gana vamos a necesitar contar las celdas que no tienen minas y que no han sido descubiertas.

Si el conteo es 0 es porque el jugador terminó de descubrir las celdas seguras y por lo tanto puede ganar el juego:

int contarCeldasSinMinasYSinDescubrir()
	{
		int x, y, conteo = 0;
		for (y = 0; y < this->altura; y++)
		{
			for (x = 0; x < this->anchura; x++)
			{
				Celda c = this->contenido.at(y).at(x);
				if (!c.estaDescubierta() && !c.tieneMina())
				{
					conteo++;
				}
			}
		}
		return conteo;
	}

En este caso el algoritmo es simplemente recorrer el tablero, comprobar si la celda no está descubierta y no tiene mina para aumentar la variable de conteo.

Cuando acabamos de recorrer el tablero regresamos el conteo.

Clase juego

Veamos la última clase que compone a este juego de Buscaminas en C++ o CPP y es la del juego, que se encarga de conectar el tablero con lo que el usuario ingresa.

En esta clase es en donde se solicita la fila y columna al usuario, se imprime el tablero, etcétera.

También aquí es en donde se colocan las minas aleatoriamente dentro de las celdas, obteniendo las coordenadas como números aleatorios.

Nota: recuerda que si el usuario descubre una celda con mina, automáticamente pierde.

class Juego
{
private:
	Tablero tablero;
	int cantidadMinas;

	int aleatorio_en_rango(int minimo, int maximo)
	{
		return minimo + rand() / (RAND_MAX / (maximo - minimo + 1) + 1);
	}

	int filaAleatoria()
	{
		return this->aleatorio_en_rango(0, this->tablero.obtenerAltura() - 1);
	}

	int columnaAleatoria()
	{
		return this->aleatorio_en_rango(0, this->tablero.obtenerAnchura() - 1);
	}

public:
	Juego(Tablero tablero, int cantidadMinas)
	{
		this->tablero = tablero;
		this->cantidadMinas = cantidadMinas;
		this->colocarMinasAleatoriamente();
	}

	void colocarMinasAleatoriamente()
	{
		int x, y, minasColocadas = 0;

		while (minasColocadas < this->cantidadMinas)
		{
			x = this->columnaAleatoria();
			y = this->filaAleatoria();
			if (this->tablero.colocarMina(x, y))
			{
				minasColocadas++;
			}
		}
	}

	/*
		solicitarFila y solicitarColumna piden la fila y columna del 1 al N, pero
		recordemos que los índices se manejan del 0 al N-1, por eso es que se resta 1
	*/

	int solicitarFila()
	{
		int fila = 0;
		cout << "Ingresa la fila: ";
		cin >> fila;
		return fila - 1;
	}

	int solicitarColumna()
	{
		int columna = 0;
		cout << "Ingresa la columna: ";
		cin >> columna;
		return columna - 1;
	}

	bool jugadorGana()
	{
		int conteo = this->tablero.contarCeldasSinMinasYSinDescubrir();
		if (conteo == 0)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	void iniciar()
	{
		int fila, columna;
		while (true)
		{
			this->tablero.imprimir();
			fila = this->solicitarFila();
			columna = this->solicitarColumna();
			bool ok = this->tablero.descubrir(columna, fila);
			if (!ok)
			{
				cout << "Perdiste\n";
				// El modo programador te permite ver todo. Entonces lo activamos y volvemos a imprimir. No hay problema porque el jugador ya perdió
				this->tablero.setModoProgramador(true);
				this->tablero.imprimir();
				break;
			}

			if (this->jugadorGana())
			{
				cout << "Ganaste\n";
				this->tablero.setModoProgramador(true);
				this->tablero.imprimir();
				break;
			}
		}
	}
};

En esta clase es en donde todos los elementos interactúan, se comprueba si el jugador gana, etcétera. Y podemos crear varias instancias de esta clase, lo cual es algo bonito de la programación orientada a objetos.

Poniendo todo junto

Buscaminas en C++ con programación orientada a objetos - Tablero con minas y celdas
Buscaminas en C++ con programación orientada a objetos – Tablero con minas y celdas

Ahora que tenemos nuestras clases del juego buscaminas programado en C++ podemos instanciarla desde el main y comenzar a jugar:

int main()
{
	srand(getpid());
	int filas = 3;
	int columnas = 3;
	int minas = 2;
	bool modoProgramador = false; // Poner en true para depurar
	Juego juego(Tablero(filas, columnas, modoProgramador), minas);
	juego.iniciar();
}

Simplemente construimos nuestro objeto de la clase Juego pasándole un tablero y la cantidad de minas que debe colocar.

Al tablero le indicamos las filas, columnas y si estamos en modo programador. En este caso para la salida de la imagen utilicé 6 filas, 6 columnas y 4 minas.

Código fuente

El código fuente completo con todos los archivos te lo dejo en GitHub.

Basta con compilar main.cpp. Si tienes g++ entonces ejecuta g++ main.cpp -o main.exe y luego ejecuta ./main.exe. G++ es compatible con Windows, Mac y Linux. Incluso en Android.

Para terminar te dejo con más proyectos de C++ en mi blog.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *