Evidencia 2: El juego de la vida

Introducción

El Juego de la Vida, conocido también como Vida, es un autómata celular ideado por el matemático británico John Horton Conway.

El juego consiste en una malla infinita bidimensional formada por cuadrados, que representan “células” ya que sus dos únicos estados son estar vivos o estar muertos. Cada celula, viva o muerta, tiene ocho vecinos a su alrededor, y, dependiendo de una serie de reglas que depende de su número de vecinos, dicha célula podrá cambiar a otro estado o quedarse igual en la siguiente generación.

Las reglas del juego son:

  1. Cualquier célula viva con menos de dos vecinos muere.
  2. Cualquier célula viva con más de tres vecinos muere.
  3. Cualquier célula viva con dos o tres vecinos sigue viviendo en la siguiente generación.
  4. Cualquier célula muerta con exactamente tres vecinos se convierte en una célula viva.

Aparentemente el juego es muy complejo por tener muchas reglas, pero esto se puede evitar si simplificamos las reglas, quedando que:

  1. Cualquier célula con menos de dos vecinos o más de tres vecinos estará muerta en la siguiente generación
  2. Cualquier célula con tres vecinos estará viva en la siguiente generación.
  3. Si una de estas dos condiciones no se cumple, la célula no cambiará su estado en la siguiente generación.

Codificación

Código fuente

En mi versión de este autómata (desarrollado en Java) cambió algunas cosas, por ejemplo, en vez de una malla infinita mi algoritmo sólo se aplica a arreglos bidimensionales con dimensiones finitas, lo que afecta un poco a observar patrones que ocurren después de ciertas generaciones.

Mi clase cuenta con tres métodos que me ayudan a desarrollar el juego, aunque sólo dos son relevantes para aplicar las reglas: un método que se encargue de las nuevas generaciones y que, de acuerdo a los resultados del otro método, decidirá si las células viven o mueren de acuerdo a las reglas ya definidas.

public static void main(String[] args) {
		// TODO Auto-generated method stub
		int i, j, ciclos = 10;
		Vida vida = new Vida();
		Random aleatorio = new Random();

		for(i = 0; i < vida.tamaño; i++){
			for(j = 0; j < vida.tamaño; j++){
				vida.casilla[i][j] = aleatorio.nextInt(2);
				}
		}

		System.out.println("Generacion 1\n");
		vida.Imprimir(vida.casilla);

		for(int z = 1; z < ciclos; z++){
			int generacion = z + 1;
			System.out.println("Generacion " + generacion);
			vida.Nuevo_ciclo();
			System.out.println();
			for(i = 0 ; i < vida.tamaño; i++){
				for(j = 0; j < vida.tamaño; j++){
					vida.casilla[i][j] = vida.casilla_nueva[i][j];
				}
			}
			vida.Imprimir(vida.casilla_nueva);
		}
	}

En mi caso, lo que hace el método principal es crear la malla original y dar valores a las células: si es 0 es una célula muerta y si es 1 es una célula viva. Después hace un ciclo for para crear las nuevas generaciones con ayuda del método Nuevo_ciclo, aunque como necesito dos mallas para definir las siguientes generaciones, una vez que la malla nueva esté completa, todo su contenido “regresa” a la malla vieja para que puedan seguir interactuando de manera iterativa.

Al final de dicho ciclo llama al método Imprimir para que aparezcan en pantalla los valores que han tomado las células de la nueva generación en base a lo que ya existía anteriormente.

public void Nuevo_ciclo(){
		int columna, fila;
		for(columna = 0;  columna < this.tamaño; columna++){
			for(fila = 0; fila < this.tamaño; fila++){ 				int n = Vecinos(columna, fila); 				//System.out.println("Vecino de casilla " + columna + fila + ": "+ n); 				if(n > 3 || n < 2){
					this.casilla_nueva[columna][fila] = 0;
				}
				else if(n == 3){
					this.casilla_nueva[columna][fila] = 1;
				}
				else{
					this.casilla_nueva[columna][fila] = this.casilla[columna][fila];
				}
			}
			//System.out.println("------------------------");
		}
	}

El método Nuevo_ciclo consiste en recorrer todas las células de la malla, y para cada célula llama al método Vecinos para que cuente el número de vecinos que tiene a su alrededor. Una vez determinado este número el método determina si la célula estará viva o muerta en la siguiente generación.

public int Vecinos(int columna, int fila){
		int contador = 0;
		for(int i = columna - 1; i <= columna + 1; i++){
			for(int j = fila - 1; j <= fila + 1; j++){
				if(i == -1 || j == -1 || i == tamaño || j == tamaño){
				}
				else{
					if(this.casilla[i][j] == 1 && (i != columna || j != fila)){
						contador++;
					}
				}
				}
			}
		return contador;
		}

El método Vecinos se encarga de hacer otro recorrido, pero al contrario que los otros ya vistos, este recorrido consiste en ir desde -i, -j hasta +i, +j de acuerdo a la posición de la célula analizada. Con una condición if se evita que este recorrido analice posiciones prohibidas en un arreglo, como querer ir a posiciones que no existen en ese arreglo.

Resultados

vida_1

vida_2

Conclusión

Una de las cosas más curiosas de este autómata es que hay una probabilidad de que en algún momento las células se estabilicen, como por ejemplo, que lleguen a quedarse totalmente vivas o muertas o que generen patrones que se repiten indefinidamente.

En mi caso particular de este autómata, si se realiza este experimento en un sistema de dimensiones finitas puede darse el hecho de que en algún momento todas células mueran por razones distintas, aunque yo lo interpreto a que se han agotado los recursos disponibles y por eso ya no pueden seguir viviendo las células. Igual que los seres vivos que compiten por un espacio en donde vivir.

Anuncios
Deja un comentario

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s