Analizando las minas explosivas

Cuando me inicié en el mundo de la programación de juegos, lo primero que se me ocurrió modelar fue el famoso juego “Buscaminas”.

Para programar este juego lo primero que hice fue entenderlo. El juego consiste en lo siguiente:

-Un tablero de N filas por M columnas en las que hay un numero K de minas distribuidas aleatoriamente.
-La finalidad es destapar todas las casillas excepto las que tienen minas, una vez lograda esa misión, se gana el juego.
-Se tiene una opción de colocar una bandera en donde se cree que pueda existir una mina, de modo que ya el usuario sepa que no debe hacer click en dicha casilla o que lo haga por accidente y pierda.
-Las casillas que no tienen minas tienen un número que representa la cantidad de minas que hay alrededor de ella (mas no indican donde están)
-Las casillas que no tienen ningún número no tienen ninguna mina alrededor.

Lo primero que voy a escribir es como modelé el tablero.

La estructura de datos natural que permite representar un tablero es un arreglo bidimensional (también llamado matriz) de dimensiones N x M, que podría ser de caracteres, pero para nuestro efecto la haremos un poco más compleja, en cada posición de la matriz almacenaremos un caracter y un estado lo cual lo podemos representar en lenguaje C++ de la siguiente manera:

struct Box
{
  char s;
  char c;
};

Donde ‘s’ es el caracter que representará el estado de la casilla (s de status) y ‘c’ el caracter que indica que es lo que hay en la casilla (c de character), los valores que vamos a usar para el caracter son los numeros del ‘0’ al ‘8’ (ambos inclusive) y el ‘*’ para representar una mina y para el estado usaremos las iniciales del nombre de cada estado en ingles, es decir ‘c’ de covered (cubierta), ‘u’ de uncovered (descubierta) y ‘f’ de flag (bandera). Los valores iniciales de Box son s = ‘c’ y c = ‘0’.

Para modelar el tablero entonces definiremos una clase que contiene una matriz dinámica de objetos de tipo Box, las dimensiones de la matriz, cuantas minas tiene el tablero, cuantas banderas se han colocado y cuantas casillas se han destapado.

class Matrix
{
  Box ** matrix;
  size_t w;
  size_t h;
  size_t mines;
  size_t flags;
  size_t uncovered_boxes;
  //...Operaciones privadas y públicas de la clase Matrix
};

Teniendo esto definamos las operaciones que se efectúan en la matriz.

Lo primero en que normalmente pensamos es en una operación de inicialización, es decir, el constructor de la clase, pero para que el constructor arme el tablero, nos valdremos primero de una operación privada a la que llamaremos inc_mines_around(i, j) que se encargará de incrementar el número de minas alrededor de una casilla dada.

El prototipo es como sigue void inc_mines_around(const size_t & i, const size_t & j) y lo que hace es que para todas las posiciones alrededor a (i, j), es decir (i – 1, j – 1), (i – 1, j), (i – 1, j + 1), (i, j – 1), (i, j + 1), (i + 1, j – 1), (i + 1, j), (i + 1, j + 1), verificará que sea una posición válida en la matriz de ser así, entonces incrementa en 1 el valor del caracter de la misma.

Entonces el constructor de Matrix es como sigue:

Matrix(const size_t & w, const size_t & h, const size_t & mines);

Donde w define el ancho que tendrá la matriz, h la altura y mines la cantidad de minas que tendrá el tablero.

La inicialización entonces consiste en dar memoria a la matriz con las dimensiones deseadas, hacer una iteración desde 0 hasta mines, por cada paso en la iteración se genera una posición aleatoria (valiéndonos del generador de números aleatorios de GSL) y colocando un valor c = ‘*’ en la posición dada e invocando a la función inc_mines_around en dicha posición.

Otra operación útil es flag(i, j) la cual coloca o quita una bandera en la posición (i, j) pasada como parámetro, lo primero que hace es validar que no se encuentre ya destapada la casilla, de ser así no hace nada, en caso de estar tapada ve el estado actual si s = ‘f’ entonces hace s = ‘c’ y decrementa el valor de flags en 1 y en caso contrario, si flags = mines entonces no hace nada, sino hace s = ‘f’ e incrementa el valor de flags en 1.

La operación de descubrir una casilla tiene un detalle importante, resulta que si el valor de una casilla esta comprendido entre ‘1’ y ‘8’, la destapamos y ya, pero cuando es un ‘0’ (queriendo decir que no tiene minas alrededor) no tiene sentido para el usuario tener que destapar manualmente todo el contorno de la casilla porque ya sabe que no hay minas alrededor, así que ese proceso se automatiza.

La operación se implementa como sigue

void discover(const size_t & i, const size_t & j)
{
  // Validar que la posicion (i, j) sea valida en la matriz, de no ser asi retornar
  // Verificar el estado de la posicion (i, j), si esta descubierta o tiene bandera retornar
  // Hacer para la posicion (i, j) c = 'u'
  // Hacer el llamado recursivo para todo el entorno de la matriz si c = '0'
  if (matrix[i][j].c == '0')
    {
      // Llamada recursiva para cada casilla que lo rodea
      discover(i - 1, j - 1);
      discover(i - 1, j);
      discover(i - 1, j + 1);
      discover(i, j + 1);
      discover(i + 1, j + 1);
      discover(i + 1, j);
      discover(i + 1, j - 1);
      discover(i, j - 1);
    }
}

También existe una operación que destapa a todas las minas (útil para mostrar todas las minas del tablero cuando se pierde) que consiste en iterar sobre la matriz colocándole el valor s = ‘u’ a cada casilla que contenga mina y no tiene bandera y una operación que verifica si se han destapado todas las casillas posibles y retorna verdadero en caso de que así sea y falso en caso contrario, se implementa verificando si la cantidad de casillas descubiertas es igual a la cantidad de casillas del tablero menos la cantidad de minas.

En el siguiente post voy a escribir acerca de una interfaz gráfica para desarrollar el juego.

Los códigos fuentes del juego están disponibles en: https://github.com/R3mmurd/Mines

Nota: En el código fuente hay algunas diferencias respecto a la explicación acá
en la implementación de Box, sin embargo tienen el mismo significado.

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Posted in C++, Programación, Videojuegos Tagged with: , ,

Deja un comentario

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

*