Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Programming

Creando vida: el juego de la vida de Conway

by
Read Time:13 minsLanguages:

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

A veces, incluso un simple conjunto de reglas básicas puede darte resultados muy interesantes. En este tutorial desarrollaremos el motor central del Juego de la vida de Conway desde cero.

Nota: Aunque este tutorial está escrito usando C # y XNA, debería poder usar las mismas técnicas y conceptos en casi cualquier entorno de desarrollo de juegos 2D.


Introducción

El juego de la vida de Conway es un autómata celular que fue ideado en la década de 1970 por un matemático británico llamado, bueno, John Conway.

Dada una grilla de celdas bidimensional, con algunos "on" o "alive" y otros "off" o "dead", y un conjunto de reglas que rigen cómo cobran vida o mueren, podemos tener una "forma de vida" interesante "desplegarse justo en frente de nosotros. Entonces, simplemente dibujando algunos patrones en nuestra grilla, y luego comenzando la simulación, podemos ver las formas de vida básicas evolucionar, extenderse, extinguirse y eventualmente estabilizarse. Descargue los archivos fuente finales o consulte la demostración a continuación:

Ahora, este "Juego de la vida" no es estrictamente un "juego": es más una máquina, principalmente porque no hay jugador ni objetivo, simplemente evoluciona según sus condiciones iniciales. No obstante, es muy divertido jugar con él, y existen muchos principios de diseño de juegos que pueden aplicarse a su creación. Entonces, sin más preámbulos, ¡comencemos!

Para este tutorial, seguí y construí todo en XNA porque es con lo que me siento más cómodo. (Si está interesado, hay una guía para comenzar a usar XNA). Sin embargo, debería poder seguir el entorno de desarrollo de juegos 2D con el que está familiarizado.


Creando las celdas

El elemento más básico en el Juego de la vida de Conway son las células, que son las "formas de vida" que forman la base de toda la simulación. Cada celda puede estar en uno de dos estados: "vivo" o "muerto". En aras de la coherencia, nos atenderemos a esos dos nombres para los estados de celda para el resto de este tutorial.

Las células no se mueven, simplemente afectan a sus vecinos en función de su estado actual.

Ahora, en términos de programación de su funcionalidad, existen los tres comportamientos que debemos darles:

  1. Necesitan hacer un seguimiento de su posición, límites y estado, para que puedan hacer clic y dibujar correctamente.
  2. Deben alternar entre vivos y muertos cuando se hace clic, lo que permite al usuario hacer realidad cosas interesantes.
  3. Deben dibujarse como blanco o negro si están vivos o muertos, respectivamente.

Todo lo anterior se puede lograr creando una clase Cell, que contendrá el siguiente código:


La cuadrícula y sus reglas

Ahora que cada célula se comportará correctamente, necesitamos crear una grilla que los mantenga a todos, e implementar la lógica que les diga a cada uno si debe cobrar vida, mantenerse vivo, morir o permanecer muerto (¡no zombies!).

Las reglas son bastante simples:

  1. Cualquier celda viva con menos de dos vecinos vivos muere, como si estuviera causada por la población insuficiente.    
  2. Cualquier célula viva con dos o tres vecinos vivos vive para la próxima generación.
  3. Cualquier celda viva con más de tres vecinos vivos muere, como por sobrepoblación.
  4. Cualquier celda muerta con exactamente tres vecinos vivos se convierte en una célula viva, como por reproducción..

Aquí hay una guía visual rápida de estas reglas en la imagen a continuación. Cada celda resaltada por una flecha azul se verá afectada por su regla numerada correspondiente más arriba. En otras palabras, la celda 1 morirá, la celda 2 permanecerá viva, la celda 3 morirá y la celda 4 cobrará vida

Entonces, como la simulación del juego ejecuta una actualización a intervalos de tiempo constantes, la grilla verificará cada una de estas reglas para todas las celdas de la grilla. Eso se puede lograr colocando el siguiente código en una nueva clase a la que llamaré Grid:

Lo único que nos falta de aquí es el método mágico GetLivingNeighbors, que simplemente cuenta cuántos de los vecinos de la celda actual están actualmente vivos. Entonces, agreguemos este método a nuestra clase Grid:

Tenga en cuenta que en el código anterior, la primera declaración if de cada par simplemente verifica que no estamos en el borde de la cuadrícula. Si no tuviéramos esta verificación, nos encontraríamos con varias excepciones por exceder los límites de la matriz. Además, dado que esto llevará a que count nunca se incremente cuando revisemos los bordes, eso significa que el juego "asume" que los bordes están muertos, por lo que equivale a tener un borde permanente de celdas blancas y muertas alrededor de las ventanas de nuestro juego.


Actualizando la Grilla en Discretos Pasos

Hasta ahora, toda la lógica que hemos implementado es sólida, pero no se comportará correctamente si no nos aseguramos de que nuestra simulación se ejecute en pasos de tiempo discretos. Esta es solo una forma elegante de decir que todas nuestras células se actualizarán al mismo tiempo, por razones de coherencia. Si no implementamos esto, obtendríamos un comportamiento extraño porque el orden en que se verificaban las células importaría, por lo que las reglas estrictas que acabamos de establecer se derrumbarían y se produciría un mini-caos.

Por ejemplo, nuestro ciclo anterior verifica todas las celdas de izquierda a derecha, por lo que si la celda de la izquierda que acabamos de verificar cobra vida, esto cambiaría el recuento de la celda en el centro que estamos comprobando y podría hacer que cobre vida . Pero, si estuviéramos revisando de derecha a izquierda, la celda de la derecha podría estar muerta, y la celda de la izquierda aún no ha cobrado vida, por lo que nuestra celda central se mantendría muerta. ¡Esto es malo porque es inconsistente! Deberíamos poder verificar las celdas en el orden aleatorio que queramos (¡como una espiral!) Y el siguiente paso siempre debe ser idéntico.

Afortunadamente, esto es realmente bastante fácil de implementar en el código. Todo lo que necesitamos es tener una segunda grilla de celdas en la memoria para el próximo estado de nuestro sistema. Cada vez que determinamos el siguiente estado de una celda, la almacenamos en nuestra segunda grilla para el siguiente estado de todo el sistema. Luego, cuando encontramos el siguiente estado de cada celda, las aplicamos todas al mismo tiempo. Entonces podemos agregar una matriz 2D de booleanos nextCellStates como una variable privada, y luego agregar este método a la clase Grid:

Finalmente, no olvide corregir su método de actualización Update arriba para asignar el resultado al siguiente estado en lugar del actual, luego llame a SetNextState al final del método de actualización Update, justo después de que se completen los bucles.


Dibujando la Grilla

Ahora que hemos terminado las partes más complicadas de la lógica de la grilla, necesitamos poder dibujarla en la pantalla. La grilla dibujará cada celda al llamar a sus métodos de sorteo uno a la vez, de modo que todas las células vivas serán negras, y las muertas serán blancas.

La cuadrícula actual no necesita dibujar nada, pero es mucho más clara desde la perspectiva del usuario si agregamos algunas líneas de cuadrícula. Esto permite al usuario ver más fácilmente los límites de las celdas y también comunica el sentido de la escala, así que creemos un método Draw de la siguiente manera:

Tenga en cuenta que en el código anterior, tomamos un solo píxel y lo estiramos para crear una línea muy larga y delgada. Su motor de juego particular podría proporcionar un método simple DrawLine donde puede especificar dos puntos y tener una línea dibujada entre ellos, lo que lo haría aún más fácil que el anterior.


Agregar lógica de juego de alto nivel

En este punto, tenemos todas las piezas básicas que necesitamos para hacer funcionar el juego, solo tenemos que juntarlo todo. Entonces, para empezar, en la clase principal de tu juego (la que comienza todo), necesitamos agregar algunas constantes como las dimensiones de la grilla y la velocidad de cuadros (cuán rápido se actualizará), y todas las otras cosas que necesitamos como la imagen de un solo píxel, el tamaño de la pantalla, etc.

También debemos inicializar muchas de estas cosas, como crear la cuadrícula, establecer el tamaño de la ventana del juego y asegurarnos de que el mouse esté visible para que podamos hacer clic en las celdas. Pero todas estas cosas son específicas del motor y no son muy interesantes, así que nos saltaremos el tema y llegaremos a lo bueno. (Por supuesto, si está siguiendo en XNA, puede descargar el código fuente para obtener todos los detalles).

Ahora que tenemos todo listo y listo, ¡deberíamos poder ejecutar el juego! Pero no tan rápido, porque hay un problema: no podemos hacer nada porque el juego siempre se está ejecutando. Es básicamente imposible dibujar formas específicas porque se romperán a medida que las dibujas, así que realmente necesitamos poder detener el juego. También sería bueno si pudiéramos limpiar la parrilla si se convierte en un desastre, porque nuestras creaciones a menudo crecerán fuera de control y dejarán un desastre.

Entonces, agreguemos un código para pausar el juego cada vez que se presione la barra espaciadora, y borramos la pantalla si se presiona la tecla de retroceso:

También sería útil que dejáramos en claro que el juego estaba en pausa, así que al escribir nuestro método Draw, agreguemos un código para que el fondo se vuelva rojo y escriba "Paused" en segundo plano:

¡Eso es! ¡Todo debería estar funcionando ahora, así que puedes darle un giro, dibujar algunas formas de vida y ver qué pasa! Ve y explora patrones interesantes que puedes hacer volviendo a consultar la página de Wikipedia. También puede jugar con la velocidad de fotogramas, el tamaño de la celda y las dimensiones de la cuadrícula para ajustarla a su gusto.


Agregar mejoras

En este punto, el juego es completamente funcional y no hay pena en terminarlo aqui. Pero una molestia que podría haber notado es que los clics del mouse no siempre se registran cuando intenta actualizar una celda, de modo que cuando hace clic y arrastra el mouse por la cuadrícula, dejará una línea punteada en lugar de un sólido. Esto sucede porque la velocidad a la que las células se actualizan también es la velocidad a la que se controla el mouse, y es demasiado lenta. Por lo tanto, simplemente tenemos que desacoplar la velocidad a la que se actualiza el juego, y la velocidad a la que se lee la entrada.

Comience definiendo la tasa de actualización y la velocidad de fotogramas por separado en la clase principal:

Ahora, al inicializar el juego, use la velocidad de fotogramas (FPS) para definir qué tan rápido leerá la entrada y el dibujo del mouse, lo que debería ser un agradable y suave 60 FPS como mínimo:

Luego, agregue un temporizador a su clase Grid, de modo que solo se actualice cuando lo necesite, independientemente de la velocidad de cuadros:

Ahora, deberías poder ejecutar el juego a la velocidad que quieras, incluso 5 actualizaciones por segundo muy lentas para que puedas observar cuidadosamente cómo se desarrolla la simulación, sin dejar de dibujar líneas suaves y agradables a una tasa de fotogramas sólida.


Conclusión

Ahora tiene un Juego de la vida suave y funcional en sus manos, pero en caso de que quiera explorarlo más, siempre hay más ajustes que puede agregar. Por ejemplo, la cuadrícula actualmente asume que más allá de sus bordes, todo está muerto. Podrías modificarlo para que la rejilla se enrolle, ¡así un planeador volaría para siempre! No hay escasez de variaciones en este popular juego, así que deja volar tu imaginación.

Gracias por leer, ¡espero que hayas aprendido algunas cosas útiles hoy!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.