Tarea
|
Subtarea
|
Descripción
|
1
|
|
Versión inicial
- Completada la plantilla del juego de la vida
- Completado README.md
|
2
|
2.1
|
Intercambiar mundos en lugar de copiarlos
- Se utiliza un array de 3 dimensiones para guardar los 2 mundos
- Se van intercambiando los mundos "actual" y "siguiente" en cada iteración
- Se elimina la función `gol_copy`, ahora innecesaria
|
2.2
|
Crear makefile y dividir el programa
- Se crean "gol.h" y "gol.c"
- Se crea un makefile para compilar y limpiar el repositorio
- Se añade un .gitignore para ignorar los productos de la compilación
|
3
|
3.1
|
Encapsular el juego de la vida en una estructura
- Se encapsulan las variables `worlds` y `current_world` en la estructura `gol`
- La función `gol_step` se encarga ahora de gestionar la variable `curren_world`
|
3.2
|
Ocultar todas las funciones que el usuario no necesite
- Se ocultan `gol_get_cell` y `gol_count_neighbours`
- Se declaran como `static` y se elimina el prefijo `gol_`
|
4
|
4.1
|
Memoria dinámica I: Añadir objetivos `debug` y `release`
Se añaden dos nuevos objetivos al makefile que modifican los flags de gcc para
crear dos ejecutables distintos según convenga.
|
4.2
|
Memoria dinámica I: Reserva dinámica de memoria para el mundo
- Ahora los mundos se reservan dinámicamente con `malloc`
- La nueva función `gol_alloc` se encarga de la esta reserva y `gol_free` de la liberación
- Cada mundo es un puntero a un puntero a un boleano, ie un vector de vectores. Se realiza una llamada a malloc para guardar un vector de punteros a filas y luego se realiza otra llamada por cada fila para reservarla.
- Para guardar la referencia a los dos mundos se utiliza un array de dos punteros. El mundo actual y el próximo siempre están en la misma posición de este array, y en cada iteración se intercambian las direcciones de los dos punteros.
- Se usa un enumerado para identificar los índices del mundo actual y el siguiente
|
5
|
5.1
|
Memoria dinámica II: Implementar test de memoria
- Se crea un nuevo archivo `mem_test.c` con otro `main()` que realiza un número finito de iteraciones del juego sin requerir la interacción del usuario, ni imprimir nada por pantalla
- Se añade un nuevo objetivo (`test`) al makefile que primero compilar el ejecutable `mem_test` con opciones de depuración y con todas las optimizaciones. Después lanzará este ejecutable con valgrind para comprobar fugas de memoria
|
5.2
|
Memoria dinámica II: Una sola reserva de memoria por mundo
- Los mundos son un único puntero a un bloque de memoria dónde está la matriz entera. Se realiza una llama a malloc por mundo.
- Ahora hay que calcular el offset de cada célula a mano, por lo que los accesos al mundo siempre se hacen a través de `get_cell` y la nueva función `set_cell`
- Las funciones `set_cell` y `get_cell` reciben el mundo sobre el que actuar (actual o siguiente)
- Se saca la lógica para comprobar los límites del mundo a la función `fix_coords` para poder utilizarla en `set_cell` y `get_cell` sin repetir código
|
6
|
6.1
|
Memoria dinámica III: Macro funcional para acceder a las células
Utilizar una macro para reutilizar y evitar repetir el mismo código en
`get_cell` y `set_cell`
|
6.2
|
Memoria dinámica III: Una reserva de memoria para los dos mundos
- La memoria para los dos mundos se reserva de una vez. La referencia a este bloque de memoria se guarda en la estructura del objeto
- Se utiliza la misma lógica que antes para acceder a las células, con dos punteros a los mundos que vamos intercarbiando.
|
7
|
7.1
|
Objetos: Reserva dinámica de memoria para la estructura `gol`
- Se cambia ligeramente la interfaz de la función `gol_alloc` para que devuelva un puntero a `struct gol` en lugar de recibirlo.
- Ahora se reservan dinámicamente dos bloques de memoria: uno para la estructura `struct gol` y otro para los arrays del mundo.
- La función `gol_free` debe liberar los dos bloques de memoria.
|
7.2
|
Objetos: Ocultar la estructura `gol`
Mediante una declaración adelantada de `struct gol` en `gol.h` conseguimos
ocultar los campos de nuestro objeto (privatizar) a los usuarios de la librería.
|
8
|
8.1
|
Utilizar getopt para recibir argumentos de línea de comandos
- El programa ahora recibe como argumentos el ancho y el alto del mundo, además de el argumento `-u, --usage` que imprime el siguiente mensaje de ayuda:
Usage: ./gol -w <width> -h <height>
-u, --usage: Prints this help message
-w, --width: Width of the world
-h, --height: Height of the world
- Es obligatorio especificar el tamaño del mundo, si no se especifica se imprime un mensaje de error y se termina el programa.
- Se define el tipo `struct gol_options` para guardar los parámetros de entrada: `int width`, `int height` y `bool usage`
- Se crean dos funciones estáticas que encapsulan la lógica de tratar los argumentos y de imprimir el mensaje de ayuda:
- `static bool parse_args(struct gol_options *gol_opt, int argc, char *argv[]);`
- `static void print_usage(const char *argv0);`
|
8.2
|
Implementar la opción de inicializar el mundo aleatoriamente
- Se añade un argumento opcional con un parámetro obligatorio. Indica el patrón de inicialización del mundo: "glider" o "random"
- Se añade otro argumento opcional con parámetro obligatorio. Proporcionará la semilla para `srand()`. Si no se pasa este parámetro, se toma como semilla el tiempo actual. Esta es ahora la salida de `./gol -u`:
Usage: ./gol -w <width> -h <height> [-r[<seed>]]
-u, --usage: Prints this help message
-w, --width: Width of the world
-h, --height: Height of the world
-s, --Seed [seed]: Seed for random world.
-p, --pattern [pattern]: Initialization pattern:
- glider
- random
- Se modifica el método `gol_init()` para recibir un enumerado, `enum gol_init_pattern`, que esta definido en `gol.h`. El método `gol_init()` llama a los métodos privados `gol_init_rand()` o `gol_init_glider()`, según el patrón de inicialización seleccionado.
- Se crea una función auxiliar en `main.c` que transforma del string recibido como argumento a el enumerado que acepta `gol_init()`
- Se modifica `mem_test.c` para adaptarlo al nuevo `gol_init()`
|
8
|
9.1
|
Cargar y guardar el mundo desde un archivo binario
- Implementar los métodos `gol_save()` y `gol_load()` para guardar y cargar el mundo desde un archivo binario. Estas funciones simplemente guardan el array tal cual en el archivo especificado y leen el archivo guardándolo en el array.
bool gol_load(struct gol *gol, const char *file);
bool gol_save(const struct gol *gol, const char *file);
- Modificar el main para que acepte los siguientes comandos, además de 'q' para cerrar:
- 'l': Si se pulsa este caracter, llamar a una función estática `load_world()` que pida al usuario que inserte el nombre de un archivo. Luego carga este archivo con la función `gol_load()`
- 's': Si se pulsa este caracter, llamar a una función estática `save_world()` que pida al usuario que inserte el nombre de un archivo. Luego guarda este archivo con la función `gol_save()`
- Si cualquiera de los nuevos comandos falla, se informa al usuario del error y se continua la ejecución. Para que el usuario pueda leer el mensaje de error se llama a `getchar()`, así se pausa la ejecución hasta que se pulse un caracter.
|
9.1
|
Guardar y cargar el mundo desde un archivo de texto
Modificar `gol_save()` y `gol_load()` para guardar y cargar el estado del
mundo en un formato de texto parecido al que se usa en `gol_print()`.
Ejemplo de archivo para mundo de 10x20:
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . # . . . . . . . . . . . . .
. . . . . . . # . . . . . . . . . . . .
. . . . . # # # . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
|
...
|
...
|
...
|