Reglas Básicas del Buscaminas
El Buscaminas, un pasatiempo digital que alguna vez fue un componente estándar de los sistemas operativos Windows, es un juego de lógica y deducción. Para entender su implementación, es fundamental repasar sus reglas principales.
Consideremos una configuración común: un tablero de 9x9 casillas. Al inicio de la partida, un número predefinido de minas (por ejemplo, 10) se distirbuye aleatoriamente en el tablero. El objetivo del jugador es descubrir todas las casillas que no contienen minas.
Al seleccionar una casilla:
- Si la casilla contiene una mina, el juego termina inmediatamente en derrota.
- Si la casilla está vacía, se revela un número que indica cuántas minas hay en las ocho casillas adyacentes (incluyendo diagonales). Si este número es cero, la práctica común del juego es revelar automáticamente todas las casillas adyacentes que también son cero, y así sucesivamente, hasta encontrar casillas con números mayores a cero o los límites del área segura.
La condición de victoria se alcanza cuando todas las casillas sin minas han sido reveladas con éxito.
Estructura del Programa Buscaminas
Para construir el Buscaminas en C, organizaremos nuestro código en módulos para una mejor legibilidad y mantenimiento. Esto incluirá la configuración del tablero, la colocación aleatoria de minas, la interfaz de usuario y la lógica de juego.
Definición de Constantes y Tableros
El juego requiere dos representaciones principales del tablero: una que almacena la ubicación de las minas y el recuento de minas adyacentes (oculta al jugador), y otra que muestra el estado actual de las casillas (cubierta, revelada con número, o marcada con bandera). Para simplificar el conteo de minas en los bordes, añadiremos un "padding" (relleno) de una fila y una columna adicional alrededor del área de juego real.
Utilizaremos las siguientes constantes:
#define FILAS_JUEGO 9 // Filas reales del área de juego
#define COLS_JUEGO 9 // Columnas reales del área de juego
#define NUM_MINAS 10 // Cantidad de minas en el tablero
// Dimensiones del tablero con padding para facilitar la lógica de conteo
#define FILAS_TABLERO (FILAS_JUEGO + 2)
#define COLS_TABLERO (COLS_JUEGO + 2)
// Caracteres para la representación del tablero
#define MINA '*' // Carácter para una mina
#define VACIO_MINAS '0' // Carácter para una casilla sin mina en el tablero oculto
#define CUBIERTO '#' // Carácter para una casilla oculta al jugador
#define BANDERA 'F' // Carácter para una casilla marcada con bandera (no implementada en este ejemplo)
Definiremos dos matrices de caracteres:
tableroMinas[FILAS_TABLERO][COLS_TABLERO]: Contiene la ubicación de las minas y los recuentos. Las minas se representan conMINAy las casillas seguras conVACIO_MINASinicialmente. Posteriormente, los recuentos de minas adyacentes se guardan como caracteres numéricos.tableroVisible[FILAS_TABLERO][COLS_TABLERO]: Muestra lo que el jugador ve. Todas las casillas comienzan comoCUBIERTOy se actualizan cuando se revelan.
Menú Principal y Bucle del Juego
La función main manejará el ciclo de vida del programa, incluyendo la inicialización del generador de números aleatorios y el menú de opciones para el usuario.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h> // Para usar el tipo bool
// Definiciones de constantes (FILAS_JUEGO, COLS_JUEGO, etc., irían aquí o en un .h)
#define FILAS_JUEGO 9
#define COLS_JUEGO 9
#define NUM_MINAS 10
#define FILAS_TABLERO (FILAS_JUEGO + 2)
#define COLS_TABLERO (COLS_JUEGO + 2)
#define MINA '*'
#define VACIO_MINAS '0'
#define CUBIERTO '#'
// Prototipos de funciones
void mostrar_menu();
void inicializar_tablero(char tablero[FILAS_TABLERO][COLS_TABLERO], int filas, int cols, char caracter_inicial);
void colocar_minas(char tablero[FILAS_TABLERO][COLS_TABLERO], int filas_juego, int cols_juego, int cantidad_minas);
void mostrar_tablero(char tablero[FILAS_TABLERO][COLS_TABLERO], int filas_juego, int cols_juego, bool mostrar_minas);
int contar_minas_adyacentes(char tablero[FILAS_TABLERO][COLS_TABLERO], int fila, int col);
void jugar_partida(char tableroMinas[FILAS_TABLERO][COLS_TABLERO], char tableroVisible[FILAS_TABLERO][COLS_TABLERO], int filas_juego, int cols_juego);
void ejecutar_juego();
int main() {
srand((unsigned int)time(NULL)); // Inicializar el generador de números aleatorios
int opcion;
do {
mostrar_menu();
printf("Seleccione una opción: ");
if (scanf("%d", &opcion) != 1) { // Capturar la entrada del usuario
printf("Entrada inválida. Por favor, ingrese un número.\n");
// Limpiar el buffer de entrada en caso de entrada no numérica
while (getchar() != '\n');
opcion = -1; // Para asegurar que el bucle continúe si la entrada fue inválida
continue;
}
switch (opcion) {
case 1:
ejecutar_juego(); // Iniciar una nueva partida
break;
case 0:
printf("Saliendo del juego. ¡Hasta pronto!\n");
break;
default:
printf("Opción no válida. Por favor, intente de nuevo.\n");
break;
}
} while (opcion != 0); // El juego continúa hasta que el usuario seleccione '0'
return 0;
}
void mostrar_menu() {
printf("\n--- Buscaminas ---\n");
printf("------------------\n");
printf("1. Jugar Partida\n");
printf("0. Salir\n");
printf("------------------\n");
}
Preparación del Tablero
Enicialización
La función inicializar_tablero llenará una matriz con un carácter específico. Se usará para limpiar el tablero de minas y para ocultar todas las casillas en el tablero visible al inicio.
void inicializar_tablero(char tablero[FILAS_TABLERO][COLS_TABLERO], int filas, int cols, char caracter_inicial) {
for (int i = 0; i < filas; ++i) {
for (int j = 0; j < cols; ++j) {
tablero[i][j] = caracter_inicial;
}
}
}
Colocación Aleatoria de Minas
La función colocar_minas se encarga de posicionar las minas de forma aleatoria en el tablero. Es crucial asegurarse de que no se coloquen minas en la misma posición dos veces.
void colocar_minas(char tablero[FILAS_TABLERO][COLS_TABLERO], int filas_juego, int cols_juego, int cantidad_minas) {
int minas_colocadas = 0;
while (minas_colocadas < cantidad_minas) {
// Genera coordenadas aleatorias dentro del área de juego real (1 a FILAS_JUEGO/COLS_JUEGO)
int fila_aleatoria = rand() % filas_juego + 1;
int col_aleatoria = rand() % cols_juego + 1;
if (tablero[fila_aleatoria][col_aleatoria] == VACIO_MINAS) {
tablero[fila_aleatoria][col_aleatoria] = MINA;
minas_colocadas++;
}
}
}
Mostrar el Tablero
Para la interacción con el usuario, necesitamos una función que imprima el estado actual del tablero visible, incluyendo números de fila y columna para ayudar al jugador a ingresar coordenadas.
void mostrar_tablero(char tablero[FILAS_TABLERO][COLS_TABLERO], int filas_juego, int cols_juego, bool mostrar_minas_debug) {
printf("\n "); // Espacio para la columna de números de fila
for (int c = 1; c <= cols_juego; ++c) {
printf("%d ", c % 10); // Imprimir el último dígito para columnas (1-9)
}
printf("\n---");
for (int c = 1; c <= cols_juego; ++c) {
printf("--");
}
printf("\n");
for (int r = 1; r <= filas_juego; ++r) {
printf("%d|", r % 10); // Imprimir el último dígito para filas (1-9)
for (int c = 1; c <= cols_juego; ++c) {
// Si es un modo de depuración y hay una mina, la muestra
if (mostrar_minas_debug && tablero[r][c] == MINA) {
printf("%c ", MINA);
} else {
printf("%c ", tablero[r][c]);
}
}
printf("\n");
}
printf("\n");
}
Lógica del Juego y Detección de Minas
Conteo de Minas Adyacentes
Cuando el jugador selecciona una casilla segura, es necesario contar cuántas minas hay en las 8 casillas que la rodean. Gracias al "padding" que agregamos al tablero, no necesitamos verificaciones de límites complejas.
int contar_minas_adyacentes(char tablero[FILAS_TABLERO][COLS_TABLERO], int fila, int col) {
int contador = 0;
// Iterar a través de las 8 casillas adyacentes
for (int dr = -1; dr <= 1; ++dr) { // delta de fila (-1, 0, 1)
for (int dc = -1; dc <= 1; ++dc) { // delta de columna (-1, 0, 1)
if (dr == 0 && dc == 0) continue; // Ignorar la propia casilla
// Acceder a la casilla adyacente usando las coordenadas de padding
if (tablero[fila + dr][col + dc] == MINA) {
contador++;
}
}
}
return contador;
}
Gestión de la Partida
La función jugar_partida contiene el bucle principal donde el jugador interactúa con el tablero. Maneja la entrada de coordenadas, la verificación de movimientos y las condiciones de victoria o derrota.
void jugar_partida(char tableroMinas[FILAS_TABLERO][COLS_TABLERO], char tableroVisible[FILAS_TABLERO][COLS_TABLERO], int filas_juego, int cols_juego) {
int celdas_seguras_restantes = filas_juego * cols_juego - NUM_MINAS;
int fila_seleccionada, col_seleccionada;
while (celdas_seguras_restantes > 0) {
mostrar_tablero(tableroVisible, filas_juego, cols_juego, false);
printf("Ingrese coordenadas (fila columna) para revelar: ");
if (scanf("%d %d", &fila_seleccionada, &col_seleccionada) != 2) {
printf("Entrada inválida. Por favor, ingrese dos números.\n");
while (getchar() != '\n'); // Limpiar buffer
continue;
}
// Validar coordenadas ingresadas por el usuario
if (fila_seleccionada < 1 || fila_seleccionada > filas_juego ||
col_seleccionada < 1 || col_seleccionada > cols_juego) {
printf("Coordenadas fuera del rango del tablero de juego. Intente de nuevo.\n");
} else if (tableroVisible[fila_seleccionada][col_seleccionada] != CUBIERTO) {
printf("Esa celda ya ha sido revelada. Elija otra.\n");
} else {
// Verificar si la casilla seleccionada contiene una mina
if (tableroMinas[fila_seleccionada][col_seleccionada] == MINA) {
mostrar_tablero(tableroMinas, filas_juego, cols_juego, true); // Mostrar todas las minas al perder
printf("¡BOOM! Has pisado una mina. ¡Juego Terminado!\n");
celdas_seguras_restantes = -1; // Usar -1 para indicar derrota
break;
} else {
// La casilla es segura, revelar y contar minas adyacentes
int minas_cercanas = contar_minas_adyacentes(tableroMinas, fila_seleccionada, col_seleccionada);
tableroVisible[fila_seleccionada][col_seleccionada] = (char)('0' + minas_cercanas);
celdas_seguras_restantes--;
// Nota: La expansión automática de casillas vacías (cuando minas_cercanas es 0)
// no está implementada en este ejemplo para mantener la simplicidad.
// Se requeriría una función recursiva para lograr este comportamiento.
}
}
}
// Comprobar resultado final del juego
if (celdas_seguras_restantes == 0) {
mostrar_tablero(tableroVisible, filas_juego, cols_juego, false);
printf("¡Felicitaciones! Has despejado todas las minas. ¡Ganaste!\n");
} else if (celdas_seguras_restantes == -1) {
printf("¡Perdiste! Mejor suerte la próxima vez.\n");
}
}
Función Principal de Ejecución del Juego
La función ejecutar_juego orquesta la inicialización de los tableros y el inicio de una nueva partida.
void ejecutar_juego() {
char tableroMinas[FILAS_TABLERO][COLS_TABLERO];
char tableroVisible[FILAS_TABLERO][COLS_TABLERO];
// Inicializar ambos tableros
inicializar_tablero(tableroMinas, FILAS_TABLERO, COLS_TABLERO, VACIO_MINAS);
inicializar_tablero(tableroVisible, FILAS_TABLERO, COLS_TABLERO, CUBIERTO);
// Colocar las minas de forma aleatoria
colocar_minas(tableroMinas, FILAS_JUEGO, COLS_JUEGO, NUM_MINAS);
// Iniciar el ciclo de juego
jugar_partida(tableroMinas, tableroVisible, FILAS_JUEGO, COLS_JUEGO);
}