Análisis de Argumentos de Línea de Comandos con getopt_long en C: Lecciones de Implementación de un Juego de Laberintos

Gestión de Argumentos de Línea de Comandos en C con getopt\_long

La implementación de aplicaciones de consola robustas a menudo requiere un manejo sofisticado de los argumentos pasados por línea de comandos. La biblioteca estándar de C ofrece funciones como getopt y getopt\_long para simplificar esta tarea, evitando la necesidad de analizar manualmente la cadena argv.

Propósito de getopt\_long

La función getopt\_long es una herramienta esencial para analizar opciones largas (como --mapa) y opciones cortas (como -m) en la línea de comandos. Transforma una secuencia de argumentos como:

./laberinto --mapa prueba.map --jugador 1 --mover derecha

en una estructura más manejable de "opción + valor de opción", liberando al desarrollador de la tediosa tarea de comparar cadenas (strcmp).

Esqueleto de Uso Común

El patrón típico de uso para getopt\_long se ve así:

#include <getopt.h> // Para getopt_long y struct option

// Definición de las opciones largas disponibles
static struct option opciones_largas[] = {
    {"mapa", required_argument, 0, 'm'},
    {"jugador", required_argument, 0, 'p'},
    {"mover", required_argument, 0, 'M'},
    {"version", no_argument, 0, 'v'},
    {0, 0, 0, 0} // Marcador de fin de opciones
};

int opcion_actual;
// El bucle procesa cada argumento hasta que no haya más opciones válidas
while ((opcion_actual = getopt_long(argc, argv, "", opciones_largas, NULL)) != -1) {
    switch (opcion_actual) {
        case 'm':
            // 'optarg' contiene el valor del argumento de la opción actual
            ruta_mapa = optarg;
            break;
        case 'p':
            id_jugador = optarg;
            break;
        case 'M':
            accion_movimiento = optarg;
            break;
        case 'v':
            mostrar_version = true;
            break;
        case '?': // Carácter para opciones inválidas o argumentos faltantes (si optstring no empieza por ':')
            return 1; // Indicar error
    }
}

Parámetros de getopt\_long

La firma de la función es:

int getopt_long(int argc, char * const argv[],
                const char *optstring,
                const struct option *longopts,
                int *longindex);

  • argc: Número total de argumentos en la línea de comandos.
  • argv: Array de cadenas que representan los argumentos.
  • optstring: Cadena que define las opciones cortas. Por ejemplo, "m:p:v" indica que -m y -p requieren un argumento, mientras que -v no lo requiere. Si solo se usan opciones largas, se puede pasar "".
  • longopts: Un puntero a un array de estructuras struct option, que describe las opciones largas.
  • longindex: Si no es NULL, se llenará con el índice dentro de longopts de la opción larga que fue encontrada. Comúnmente se pasa NULL.

Estructura struct option

Cada entrada en el array longopts es una struct option y se define con cuatro campos:

{"mapa", required_argument, 0, 'm'}

  • Primer campo (name): El nombre de la opción larga (ej. "mapa" para --mapa).
  • Segundo campo (has\_arg): Especifica si la opción requiere un argumento:
    • no\_argument: La opción no toma ningún valor.
    • required\_argument: La opción debe ir acompañada de un valor.
    • optional\_argument: La opción puede tener un valor opcional.
  • Tercer campo (flag): Usualmente se establece a 0 o NULL. Si se especifica un puntero, el valor de val se almacena en la ubicación apuntada en lugar de ser devuelto por getopt\_long.
  • Cuarto campo (val): El valor que getopt\_long devuelve cuando se encuentra esta opción. A menudo, se usa un carácter corto ASCII para corresponder a una opción corta (ej. 'm').

Es crucial finalizar el array longopts con una entrada nula:

{0, 0, 0, 0}

Sin este terminador, el analizador no sabrá dónde finaliza la tabla de opciones, lo que conduce a un comportamiento indefinido.

Variables Globales Comunes de getopt

  • optarg: Un puntero a la cadena de caracteres del argumento asociado con la opción actual. Por ejemplo, si se procesa --mapa archivo.txt, optarg apuntará a "archivo.txt".
  • optind: El índice en argv del siguiente argumento a procesar. Después de que getopt\_long termina, si optind no es igual a argc, significa que hay argumentos no procesados.
  • optopt: Contiene el valor de la opción no reconocida o faltante si ocurre un error.
  • opterr: Un flag booleano que, si es distinto de cero (por defecto), permite que la biblioteca imprima mensajes de error. Muchos desarrolladores lo configuran a 0 para manejar los errores de forma personalizada.

Detección de Argumentos Requeridos Faltantes

No se debe depender de optarg == NULL para determinar si falta un argumento requerido. En su lugar, el valor de retorno de getopt\_long es clave:

  • Si se encuentra una opción como --mapa archivo y es required\_argument, getopt\_long devuelve el valor especificado (ej. 'm'), y optarg apunta a "archivo".
  • Si falta un argumento requerido, getopt\_long normalmente devuelve '?'.
  • Para distinguir entre una opción ilegal y un argumento faltante, se puede prefijar optstring con un dos puntos (:). En este caso, ':' se devuelve para un argumento faltante, y '?' para una opción desconocida.

Ejemplo de manejo de errores mejorado:

while ((opcion_actual = getopt_long(argc, argv, ":", opciones_largas, NULL)) != -1) {
    switch (opcion_actual) {
        case 'm':
            ruta_mapa = optarg;
            break;
        case ':': // Argumento requerido faltante
            fprintf(stderr, "Error: La opción '-%c' requiere un argumento.\n", optopt);
            return 1;
        case '?': // Opción desconocida
            fprintf(stderr, "Error: Opción desconocida '-%c'.\n", optopt);
            return 1;
    }
}

Secuencia de Uso Recomendada de getopt\_long

Una estrategia robusta para el manejo de argumentos implica una clara separación de responsabilidades:

  1. **Definición:** Establecer struct option\[\] para las opciones largas.
  2. **Análisis Sintáctico:** Usar el bucle while (getopt\_long(...) != -1) para extraer todas las opciones y sus valores.
  3. **Validación Semántica:** Después del análisis, verificar las restricciones semánticas (ej. ¿Se proporcionó --mapa? ¿Es válido el ID del jugador? ¿Es el movimiento una dirección reconocida?).
  4. **Lógica de Negocio:** Una vez que todos los argumentos han sido validados, proceder con la lógica principal de la aplicación (ej. cargar el mapa, mover el jugador, guardar el mapa).

Este enfoque es más estable que realizar operaciones de negocio intensivas o validaciones complejas durante el proceso de análisis, ya que mantiene las capas sintáctica y semántica separadas.

Lecciones Aprendidas Durante la Depuración

Durante el desarrollo y la depuración de una aplicación de juego de laberintos, surgieron varios problemas comunes relacionados con el manejo de argumentos y la gestión de memoria. Estas son las conclusiones clave:

1. Uso de Punteros Nulos Antes de la Verificación

Un error común es intentar desreferenciar un puntero que podría ser NULL. Por ejemplo, llamar a strlen(id\_jugador) cuando id\_jugador no ha sido inicializado porque la opción --jugador no se proporcionó en la línea de comandos. Esto resulta en un fallo de segmentación.

Conclusión: Siempre verificar si un puntero es NULL antes de intentar usarlo (ej. antes de strlen, indexación o conversión de tipo).

2. Asunción de Objetos Inicializados Después de malloc

La función malloc solo asigna un bloque de memoria; no garanitza que su contenido esté inicializado a cero. Por ejemplo, asignar memoria para una estructura Laberinto con malloc y luego intentar acceder a sus campos filas o columnas sin inicializar, puede llevar a usar valores basura como índices.

Conclusión: Para asegurar que la memoria esté inicializada a cero, usar calloc o memset explícitamente, o bien inicializar la estructura al declararla (ej. Laberinto mi\_laberinto = {0};).

3. Verificación de Límites de Arreglos Incorrecta (Intervalo Abierto)

Un error freucente en las comprobaciones de límites es usar un intervalo abierto, como 0 &lt; fila &amp;&amp; fila &lt; laberinto-&gt;filas. Esto excluye incorrectamente la fila 0 y la columna 0, tratando posiciones válidas en los bordes como fuera de límites.

Conclusión: Los índices de arreglos en C están en el rango \[0, n-1\]. La comprobación correcta debe ser 0 &lt;= fila &amp;&amp; fila &lt; laberinto-&gt;filas.

4. Ausencia de Comprobación de Límites en Funciones Auxiliares

Al implementar funciones que acceden a un arreglo bidimensional (como esEspacioVacio), es vital realizar comprobaciones de límites explícitas. Acceder directamente a laberinto-&gt;mapa\[fila\]\[columna\] con coordenadas fuera de rango (ej. (-1, 0) o (filas, 0)) provoca lecturas fuera de límites.

Conclusión: Todas las funciones que acceden a arreglos mediante coordenadas deben primero validar que las coordenadas estén dentro de los límites válidos del arreglo.

5. Omisión de break en Sentencias switch

Olvidar la palabra clave break en un caso de switch causa un "fallthrough" involuntario, donde la ejecución continúa en el siguiente case. Por ejemplo, un --mover válido podría caer en el case '?' y producir un error.

Conclusión: En cada case de un switch, se debe confirmar si el "fallthrough" es intencional o si se necesita un break explícito.

6. Coincidencia Parcial en Lugar de Comparación Exacta

Asumir que un argumento se puede identificar por su primera letra puede ser engañoso. Por ejemplo, si solo se verifica direccion\[0\], "diagonal" podría interpretarse como "abajo". Los valores discretos y limitados deben compararse de forma exacta.

Conclusión: Para conjuntos de valores discretos (ej. direcciones "arriba", "abajo"), utilizar funciones de comparación de cadenas exactas como strcmp.

7. Error en la Lógica de Escritura de Archivos

Un error en la función guardarMapa consistió en abrir el archivo en modo lectura ("r") y luego intentar escribir en la salida estándar con putchar. Esto resultó en que el archivo nunca se guardó correctamente.

Conclusión: Al escribir en archivos, verificar:

  • El modo de apertura del archivo ("w", "a", etc.).
  • Que se esté escribiendo en el puntero FILE \* correcto.
  • Que el archivo se cierre y se sincronicen los datos con el disco.

8. Confusión entre Modos de Salida (Éxito vs. Error)

Imprimir el mapa y luego retornar un código de error (ej. 1) cuando no se especifica --mover confunde al sistema. Una impresión exitosa del mapa indica que la aplicación procesó la solicitud correctamente.

Conclusión: Diferenciar claramente entre un modo de "consulta" (ej. imprimir el mapa) que debe retornar éxito (0) y un modo de "error" que debe retornar un código de fallo.

9. Influencia del Entorno de Prueba en el Estado de getopt\_long

Cuando un marco de pruebas invoca la función main varias veces dentro del mismo proceso, el estado global de getopt\_long (especialmente optind) no se reinicia automáticamente. Esto puede causar que las llamadas subsiguientes a main fallen en el análisis de argumentos.

Conclusión: Si main puede ser llamada repetidamente en un entorno de pruebas, resetear explícitamente las variables globales de getopt\_long (optind = 0; opterr = 0;). Cuando los tests fallan y la ejecución manual funciona, sospechar del modelo de invocación del framework.

Conclusiones Simplificadas Post-Depuración

  • getopt\_long es para la separación sintáctica de argumentos de línea de comandos en opciones estructuradas.
  • Las comprobaciones de punteros nulos, validez de entradas y reglas de negocio deben realizarse después del análisis sintáctico.
  • Nunca operar sobre objetos que puedan ser nulos, no inicializados o fuera de límites antes de verificar su estado.
  • Para operaciones de E/S de archivos, verificar siempre la apertura, el objeto de escritura, el destino de salida y el cierre.
  • La mayoría de los fallos de segmentación no son errores algorítmicos, sino problemas con el ciclo de vida de los objetos, límites, punteros nulos o estados no inicializados.

Código del Juego de Laberintos

A continuación, se presenta la implementación de las funciones del juego de laberintos, incluyendo el manejo de argumentos y la lógica del juego. Se asume la existencia de un archivo de cabecera labyrinth.h con las definiciones de estructuras necesarias.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <getopt.h>

// Definiciones de constantes y estructuras (normalmente en labyrinth.h)
#define MAX_FILAS 100
#define MAX_COLUMNAS 100

typedef struct {
    int fila;
    int columna;
} Posicion;

typedef struct {
    char mapa[MAX_FILAS][MAX_COLUMNAS];
    int filas;
    int columnas;
} Laberinto;

// Prototipos de funciones (normalmente en labyrinth.h)
void imprimirVersion();
bool esJugadorValido(char id_jugador);
bool cargarMapa(Laberinto *laberinto, const char *nombre_archivo);
Posicion buscarJugador(Laberinto *laberinto, char id_jugador);
Posicion buscarPrimerEspacioVacio(Laberinto *laberinto);
bool esEspacioVacio(Laberinto *laberinto, int fila, int columna);
bool moverJugador(Laberinto *laberinto, char id_jugador, const char *direccion_movimiento);
bool guardarMapa(Laberinto *laberinto, const char *nombre_archivo);
bool imprimirMapa(Laberinto *laberinto);
void realizarDFS(Laberinto *laberinto, int fila, int columna, bool visitado[MAX_FILAS][MAX_COLUMNAS]);
bool estanEspaciosVaciosConectados(Laberinto *laberinto);


int main(int argc, char *argv[]) {
    // Restablecer el estado global de getopt_long para evitar interferencias en entornos de prueba
    optind = 0;
    opterr = 0;
    optopt = 0;
    optarg = NULL;

    static struct option opciones_largas[] = {
        {"mapa", required_argument, 0, 'm'},
        {"jugador", required_argument, 0, 'p'},
        {"version", no_argument, 0, 'v'},
        {"mover", required_argument, 0, 'M'},
        {0, 0, 0, 0}
    };

    int opcion_encontrada;
    char *ruta_archivo_mapa = NULL;
    char *direccion_movimiento = NULL;
    char *id_jugador_str = NULL;
    bool mostrar_version = false;

    // Primer paso: Análisis sintáctico de los argumentos de línea de comandos
    while ((opcion_encontrada = getopt_long(argc, argv, "", opciones_largas, NULL)) != -1) {
        switch (opcion_encontrada) {
            case 'm':
                ruta_archivo_mapa = optarg;
                break;
            case 'p':
                id_jugador_str = optarg;
                break;
            case 'v':
                mostrar_version = true;
                break;
            case 'M':
                direccion_movimiento = optarg;
                break;
            case '?': // Opción desconocida
                fprintf(stderr, "Error: Opción desconocida o inválida.\n");
                return 1;
            case ':': // Argumento requerido faltante (aunque optstring es "" aquí, esta lógica es buena práctica si optstring se modifica)
                fprintf(stderr, "Error: Falta el argumento para la opción '-%c'.\n", optopt);
                return 1;
        }
    }

    // Comprobación de argumentos restantes no procesados (no opción-value)
    if (optind != argc) {
        fprintf(stderr, "Error: Argumentos posicionales inesperados después de las opciones.\n");
        return 1;
    }

    // Segundo paso: Validaciones semánticas de los argumentos y lógica de negocio
    if (mostrar_version) {
        // Si se pide la versión, no deben haber otras opciones conflictivas.
        if (ruta_archivo_mapa != NULL || id_jugador_str != NULL || direccion_movimiento != NULL) {
             fprintf(stderr, "Error: La opción --version no puede combinarse con otras opciones.\n");
             return 1;
        }
        imprimirVersion();
        return 0;
    }

    // Opciones --mapa y --jugador son obligatorias para el juego
    if (ruta_archivo_mapa == NULL || id_jugador_str == NULL) {
        fprintf(stderr, "Error: Las opciones --mapa y --jugador son obligatorias para iniciar el juego.\n");
        return 1;
    }

    // Validar el ID del jugador
    if (strlen(id_jugador_str) != 1 || !esJugadorValido(id_jugador_str[0])) {
        fprintf(stderr, "Error: El ID del jugador debe ser un dígito único (0-9).\n");
        return 1;
    }
    char id_jugador_char = id_jugador_str[0];

    Laberinto mi_laberinto = {0}; // Inicializar la estructura del laberinto a ceros
    if (!cargarMapa(&mi_laberinto, ruta_archivo_mapa)) {
        // fprintf ya se hizo en cargarMapa o perror
        return 1;
    }

    if (!estanEspaciosVaciosConectados(&mi_laberinto)) {
        fprintf(stderr, "Error: El mapa contiene espacios vacíos no conectados, lo que lo hace inválido.\n");
        return 1;
    }

    // Si no se especifica movimiento, solo imprimir el mapa
    if (direccion_movimiento == NULL) {
        imprimirMapa(&mi_laberinto);
        return 0;
    }

    // Realizar el movimiento del jugador
    if (!moverJugador(&mi_laberinto, id_jugador_char, direccion_movimiento)) {
        fprintf(stderr, "Error: No se pudo mover al jugador '%c' en la dirección '%s'. Revise las reglas del laberinto.\n", id_jugador_char, direccion_movimiento);
        return 1;
    }

    // Guardar el estado del mapa después del movimiento
    if (!guardarMapa(&mi_laberinto, ruta_archivo_mapa)) {
        // fprintf ya se hizo en guardarMapa o perror
        return 1;
    }

    return 0; // Éxito
}

void imprimirVersion() {
    printf("Este es el Juego de Laberintos - Versión 1.0\n");
}

bool esJugadorValido(char id_jugador) {
    return (id_jugador >= '0' && id_jugador <= '9');
}

bool cargarMapa(Laberinto *laberinto, const char *nombre_archivo) {
    if (laberinto == NULL || nombre_archivo == NULL) {
        fprintf(stderr, "Error: Puntero nulo para laberinto o nombre de archivo al cargar el mapa.\n");
        return false;
    }

    FILE *archivo_mapa = fopen(nombre_archivo, "r");
    if (archivo_mapa == NULL) {
        perror("Error al abrir el archivo del mapa");
        return false;
    }

    char linea_buffer[MAX_COLUMNAS + 2]; // +1 para '\n', +1 para '\0'
    int columnas_esperadas = -1;
    laberinto->filas = 0; // Asegurar que las filas se reinicien al cargar

    while (fgets(linea_buffer, sizeof(linea_buffer), archivo_mapa) != NULL) {
        if (laberinto->filas >= MAX_FILAS) {
            fprintf(stderr, "Error: El mapa excede el número máximo de filas (%d).\n", MAX_FILAS);
            fclose(archivo_mapa);
            return false;
        }

        size_t longitud_linea = strcspn(linea_buffer, "\n"); // Longitud sin el '\n'
        if (linea_buffer[longitud_linea] == '\n') { // Si hay un salto de línea, lo terminamos
            linea_buffer[longitud_linea] = '\0';
        } else if (longitud_linea == sizeof(linea_buffer) - 1) {
             // La línea era demasiado larga y no cabía en el buffer, o no tenía \n
             fprintf(stderr, "Error: Una línea del mapa es demasiado larga o le falta un salto de línea.\n");
             fclose(archivo_mapa);
             return false;
        }

        if (columnas_esperadas == -1) {
            columnas_esperadas = (int)longitud_linea;
            if (columnas_esperadas == 0) { // Mapa vacío o línea vacía
                fprintf(stderr, "Error: La primera línea del mapa está vacía.\n");
                fclose(archivo_mapa);
                return false;
            }
        }

        if ((int)longitud_linea != columnas_esperadas) {
            fprintf(stderr, "Error: Filas con longitud inconsistente. Fila %d tiene %zu columnas, esperaba %d.\n",
                    laberinto->filas, longitud_linea, columnas_esperadas);
            fclose(archivo_mapa);
            return false;
        }
        // Esta comprobación es redundante si sizeof(linea_buffer) ya lo restringe a MAX_COLUMNAS+1
        // pero se mantiene por claridad de intención.
        if (longitud_linea > MAX_COLUMNAS) { 
            fprintf(stderr, "Error: Una fila excede el número máximo de columnas (%d).\n", MAX_COLUMNAS);
            fclose(archivo_mapa);
            return false;
        }


        laberinto->columnas = (int)longitud_linea; // Asignar al miembro de la estructura
        strncpy(laberinto->mapa[laberinto->filas], linea_buffer, longitud_linea + 1); // +1 para el nulo terminador
        laberinto->filas++;
    }
    fclose(archivo_mapa);
    return true;
}

Posicion buscarJugador(Laberinto *laberinto, char id_jugador) {
    Posicion pos_jugador = {-1, -1};
    if (laberinto == NULL) return pos_jugador;

    for (int r = 0; r < laberinto->filas; ++r) {
        for (int c = 0; c < laberinto->columnas; ++c) {
            if (laberinto->mapa[r][c] == id_jugador) {
                pos_jugador.fila = r;
                pos_jugador.columna = c;
                return pos_jugador; // Jugador encontrado
            }
        }
    }
    return pos_jugador; // Jugador no encontrado
}

Posicion buscarPrimerEspacioVacio(Laberinto *laberinto) {
    Posicion pos_vacia = {-1, -1};
    if (laberinto == NULL) return pos_vacia;

    for (int r = 0; r < laberinto->filas; ++r) {
        for (int c = 0; c < laberinto->columnas; ++c) {
            if (laberinto->mapa[r][c] == '.') {
                pos_vacia.fila = r;
                pos_vacia.columna = c;
                return pos_vacia;
            }
        }
    }
    return pos_vacia; // No se encontraron espacios vacíos
}

bool esEspacioVacio(Laberinto *laberinto, int fila, int columna) {
    if (laberinto == NULL) return false;
    // Primera comprobación: ¿está dentro de los límites del laberinto?
    if (fila < 0 || fila >= laberinto->filas || columna < 0 || columna >= laberinto->columnas) {
        return false;
    }
    // Segunda comprobación: ¿es un punto (espacio vacío)?
    return (laberinto->mapa[fila][columna] == '.');
}

// Tabla de desplazamiento para las 4 direcciones
const Posicion desplazamientos_dir[] = {
    {-1, 0}, // Arriba
    {1, 0},  // Abajo
    {0, -1}, // Izquierda
    {0, 1}   // Derecha
};

bool moverJugador(Laberinto *laberinto, char id_jugador, const char *direccion_movimiento) {
    if (laberinto == NULL || direccion_movimiento == NULL) return false;

    int indice_direccion = -1;
    if (strcmp(direccion_movimiento, "up") == 0) {
        indice_direccion = 0;
    } else if (strcmp(direccion_movimiento, "down") == 0) {
        indice_direccion = 1;
    } else if (strcmp(direccion_movimiento, "left") == 0) {
        indice_direccion = 2;
    } else if (strcmp(direccion_movimiento, "right") == 0) {
        indice_direccion = 3;
    } else {
        return false; // Dirección no reconocida
    }

    Posicion pos_actual = buscarJugador(laberinto, id_jugador);
    if (pos_actual.fila == -1) {
        // Si el jugador no está en el mapa, intentar colocarlo en el primer espacio vacío
        pos_actual = buscarPrimerEspacioVacio(laberinto);
        if (pos_actual.fila == -1) {
            return false; // No hay jugador y no hay espacios vacíos para colocarlo
        }
    }

    Posicion delta = desplazamientos_dir[indice_direccion];
    Posicion pos_siguiente = {pos_actual.fila + delta.fila, pos_actual.columna + delta.columna};

    if (!esEspacioVacio(laberinto, pos_siguiente.fila, pos_siguiente.columna)) {
        return false; // La celda destino no es un espacio vacío o está fuera de límites
    }

    // Realizar el movimiento
    laberinto->mapa[pos_actual.fila][pos_actual.columna] = '.';
    laberinto->mapa[pos_siguiente.fila][pos_siguiente.columna] = id_jugador;
    return true;
}

bool guardarMapa(Laberinto *laberinto, const char *nombre_archivo) {
    if (laberinto == NULL || nombre_archivo == NULL) {
        fprintf(stderr, "Error: Puntero nulo para laberinto o nombre de archivo al guardar el mapa.\n");
        return false;
    }

    FILE *archivo_salida = fopen(nombre_archivo, "w");
    if (archivo_salida == NULL) {
        perror("Error al abrir el archivo para guardar el mapa");
        return false;
    }

    for (int r = 0; r < laberinto->filas; ++r) {
        // Escribir la línea del mapa (asumiendo que está null-terminada)
        if (fputs(laberinto->mapa[r], archivo_salida) == EOF) {
            fclose(archivo_salida);
            perror("Error al escribir la línea del mapa");
            return false;
        }
        // Escribir el salto de línea al final de cada fila
        if (fputc('\n', archivo_salida) == EOF) {
            fclose(archivo_salida);
            perror("Error al escribir el salto de línea");
            return false;
        }
    }

    fclose(archivo_salida);
    return true;
}

bool imprimirMapa(Laberinto *laberinto) {
    if (laberinto == NULL) return false;

    for (int r = 0; r < laberinto->filas; ++r) {
        // Imprimir cada carácter de la fila, seguido de un salto de línea al final
        for (int c = 0; c < laberinto->columnas; ++c) {
            putchar(laberinto->mapa[r][c]);
        }
        putchar('\n');
    }
    return true;
}

// Función de búsqueda en profundidad (DFS) para verificar conectividad
void realizarDFS(Laberinto *laberinto, int fila, int columna, bool visitado[MAX_FILAS][MAX_COLUMNAS]) {
    // Comprobación de límites y tipo de celda (debe ser un espacio vacío '.' y no visitado)
    if (!esEspacioVacio(laberinto, fila, columna) || visitado[fila][columna]) {
        return;
    }

    visitado[fila][columna] = true; // Marcar la celda como visitada

    // Explorar vecinos en las 4 direcciones
    for (int i = 0; i < 4; ++i) {
        int nueva_fila = fila + desplazamientos_dir[i].fila;
        int nueva_columna = columna + desplazamientos_dir[i].columna;
        // La recursión solo se produce si el vecino es un espacio vacío y no ha sido visitado
        if (esEspacioVacio(laberinto, nueva_fila, nueva_columna) && !visitado[nueva_fila][nueva_columna]) {
            realizarDFS(laberinto, nueva_fila, nueva_columna, visitado);
        }
    }
}

bool estanEspaciosVaciosConectados(Laberinto *laberinto) {
    if (laberinto == NULL) return false;

    bool celdas_visitadas[MAX_FILAS][MAX_COLUMNAS];
    memset(celdas_visitadas, 0, sizeof(celdas_visitadas)); // Inicializar todo a false

    Posicion primer_vacio = buscarPrimerEspacioVacio(laberinto);

    if (primer_vacio.fila == -1) {
        return true; // Si no hay espacios vacíos, se consideran conectados
    }

    // Iniciar el DFS desde el primer espacio vacío encontrado
    realizarDFS(laberinto, primer_vacio.fila, primer_vacio.columna, celdas_visitadas);

    // Verificar si todos los espacios vacíos fueron visitados
    for (int r = 0; r < laberinto->filas; ++r) {
        for (int c = 0; c < laberinto->columnas; ++c) {
            if (laberinto->mapa[r][c] == '.' && !celdas_visitadas[r][c]) {
                return false; // Se encontró un espacio vacío no conectado
            }
        }
    }
    return true; // Todos los espacios vacíos están conectados
}

Etiquetas: C Programming command-line arguments getopt_long parsing debugging

Publicado el 8-6 20:48