Implementación del Clásico Juego Buscaminas en C

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 con MINA y las casillas seguras con VACIO_MINAS inicialmente. 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 como CUBIERTO y 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);
}

Etiquetas: C programación juegos algoritmos Matrices

Publicado el 7-18 01:28