d8888 8888888888 8888888b. .d8888b. |
d88888 888 888 "Y88b d88P Y88b |
d88P888 888 888 888 Y88b. |
d88P 888 8888888 888 888 "Y888b. | Algoritmos e
d88P 888 888 888 888 "Y88b. | Estruturas de Dados I
d88P 888 888 888 888 "888 |
d8888888888 888 888 .d88P Y88b d88P |
d88P 888 8888888888 8888888P" "Y8888P" |
Parafraseando o documento guia da atividade:
"O Jogo da Vida é um autômato celular que evolui de acordo com regras simples, mas gera padrões complexos. Ele é representado por uma matriz 2D de células, cada uma podendo estar viva ou morta."
"O objetivo deste trabalho é revisar os conceitos de manipulação de matrizes, controle de fluxo e lógica de jogo."
No contexto da disciplina, podemos então entender o Jogo da Vida como um processo de manipulção de matrizes 2D, que precisa obedecer a uma lógica de programação para poder cumprir com um conjunto de regras. A intenção aqui é que o programa seja capaz de verificar a matriz com base nessas regras, e que consiga reproduzir o que acontece quando elas são aplicadas diversas vezes sobre matrizes resultantes. Também é importante que o programa guarde em um arquivo a matriz original e as novas matrizes que são geradas.
- O tabuleiro é iniciado na forma de uma matriz quadrática de dimensão NxN;
- O tabuleiro é iniciado aleatoriamente com células vivas (1) e mortas (0);
- Uma célula viva (1) com menos de dois vizinhos vivos morre (solidão);
- Uma célula viva (1) com mais de três vizinhos vivos morre (superpopulação);
- Uma célula viva (1) com dois ou três vizinhos vivos vive;
- Uma célula morta (0) com exatamente três vizinhos vivos se torna viva (reprodução).
Antes de iniciar propriamente o projeto, também foi necessário levar em conta algumas considerações que foram dadas durante as aulas de laboratório:
-
Forneça a prmieira matriz (aleatoriamente ou produzida manualmente): Ela pode ser encontrada e/ou modificada no arquivo input.mps, localizado na pasta datasets;
-
Tenha cerca do dobro de células mortas (0) para a quantidade de células vivas (1): Isso é relevante para que a execução possa ter várias gerações até que o tabuleiro zere completamente.
-
No terminal, apresentar apenas mensagens de execução de algoritmo e processo finalizado: A prioridade é que o arquivo gerações.mps contenha o histórico de modificações das matrizes.
-
O ideal é trabalhar com 2 matrizes, que podem ser descarregadas uma na outra: Essa é literalmente a base da lógica por trás do projeto e da modificação das matrizes, que foi discutida durante as aulas de laboratório como uma parte relevante da solução.
-
int getNumGeracoes()
: é uma função que pode ao usuário, através do terminal, o número de gerações de novas matrizes na execução. Assim, retorna esse valor como um inteiro. -
int tamMatrizInicial()
: é uma função que lê a primeira linha do arquivo input.mps e retona o número N que representa a dimensão da matriz NxN. -
void lerMatrizInicial(int **matrizInicial, int tamMatriz1)
: recebe o ponteiro da matriz inicial (criada na main) e o tamanho da matriz obtido pela função anterior, e abre o arquivo input.mps novamente para fazer a leitura da matriz que está contida nele. -
void exibirMatrizAtual(int **matrizInicial, int tamMatriz1)
: recebe o ponteiro da matriz inicial para exibir no terminal o que seria o estado atual do tabuleiro. -
void salvarMatrizInicial(int **matrizInicial, int tamMatriz1)
: recebe o ponteiro da matriz inicial (criada na main) e o tamanho da matriz para salvar a matriz obtida através do arquivo input.mps no arquivo geracoes.mps. -
void gerarMatrizAuxiliar(int **matrizInicial, int **matrizAuxiliar, int tamMatriz1)
: recebe o ponteiro da matriz inicial, o de uma matriz auxiliar e seu tamanho para preencher a matriz auxiliar, com base nas regras do Jogo da Vida. Para isso, é necessário também percorrer a matriz internamente com laços de repetição que verifiquem células válidas da matriz. A partir disso, a função percorre a matriz inicial com um contador de vizinhos vivos para cada célula, e aplica as condições de vida ou morte da célula, assim preenchendo a matriz auxiliar. -
void salvarMatrizAuxiliar(int **matrizAuxiliar, int tamMatriz1)
: recebe o ponteiro da matriz auxiliar, junto de seu tamanho, para salvar a matriz obtida no arquivo geracoes.mps. -
void editarMatrizInicial(int **matrizInicial, int **matrizAuxiliar, int tamMatriz1)
: essa função recebe os ponteiros das matrizes inicial e auxiliar para substituir o conteúdo da matriz inicial pelo conteúdo da matriz auxiliar. Assim, uma nova matriz auxiliar pode ser gerada e sobrescrita, que é salva no arquivo geracoes.mps e mais uma vez pode sobrescrever a matriz inicial. Após isso, o terminal também mostra o tabuleiro após as mudanças. Assim, o ciclo é repetido em um laço de repetição que equivale a quantidade de gerações desejada pelo usuário.
Considerando a necessidade da disciplina em testar programas em um ambiente Linux, a atividade desse repositório foi testada em uma máquina com as seguintes especificações:
5
1 0 1 0 1
0 0 0 0 0
0 1 0 1 0
0 0 0 0 0
1 0 1 0 1
7
1 0 1 0 1 0 1
0 0 0 0 0 0 0
0 1 0 1 0 1 0
1 0 1 0 1 0 1
0 1 0 1 0 1 0
0 0 0 0 0 0 0
1 0 1 0 1 0 1
9
1 0 1 0 1 0 1 0 1
0 0 0 0 0 0 0 0 0
0 1 0 1 0 1 0 1 0
1 0 1 0 1 0 1 0 1
0 1 0 1 0 1 0 1 0
1 0 1 0 1 0 1 0 1
0 1 0 1 0 1 0 1 0
0 0 0 0 0 0 0 0 0
1 0 1 0 1 0 1 0 1
Aqui estão listados os principais arquivos que o programa precisa para que a lógica implementada na atividade funcione devidamente. Arquivos de compilação/execução serão citados posteriormente:
-
input.mps
: Possui na primeira linha um número que repesenta as dimensões da matriz quadrática. As próximas linhas possuem as linhas da matriz inicial em si. -
geracoes.mps
: É um arquivo vazio, que vai armazenar tanto a matriz original quanto as novas matrizes que serão geradas.
main.cpp
: Aqui, as funções dos arquivos de cabeçalho são chamadas e variáveis são criadas a partir delas. Apresenta, de forma resumida, a lógica por trás da lógica aplicada para a resolução do exercício.jogoDaVida.hpp
: É um arquivo de cabeçalho para as funções do código.jogoDaVida.cpp
: É um arquivo que contém todas as funções usadas no código.
O projeto disponibilizado possui um arquivo Makefile (disponibilizado pelo professor Michel) que realiza todo o procedimento de compilação e execução. Para tanto, temos as seguintes diretrizes de execução:
Comando | Função |
---|---|
make clean |
Apaga a última compilação realizada contida na pasta build |
make |
Executa a compilação do programa utilizando o gcc, e o resultado vai para a pasta build |
make run |
Executa o programa da pasta build após a realização da compilação |