Skip to Content

Juego de la Vida en una pantalla de matriz de puntos con MAX7219

Juego de la Vida en una pantalla de matriz de puntos con MAX7219

En este tutorial aprenderás cómo implementar el Juego de la Vida en una pantalla de matriz de puntos LED 8×8 usando el controlador MAX7219 y un Arduino. También proporcionaré instrucciones y una implementación para un ESP8266 ESP-12F Mini en lugar del Arduino. Esto te permitirá construir un icónico Juego de la Vida en un Cubo que ejecuta el patrón planeador, mostrado arriba.

¡No puede ser más friki que esto!

Qué es el Juego de la Vida

El Juego de la Vida, también conocido como el Juego de la Vida de Conway, es un autómata celular ideado por el matemático John Horton Conway en 1970. No es un juego tradicional en el sentido de tener jugadores o condiciones de ganar o perder. En cambio, es una simulación que sigue un conjunto de reglas simples y demuestra patrones y comportamientos complejos.

El Juego de la Vida se desarrolla en una cuadrícula de celdas, donde cada celda puede estar viva o muerta. La cuadrícula puede tener cualquier tamaño, pero para este tutorial usaremos una pantalla de matriz de puntos LED 8×8. El juego avanza en pasos de tiempo discretos, donde el estado de cada celda se actualiza según su estado actual y el de sus celdas vecinas.

Reglas

Las reglas del Juego de la Vida son las siguientes:

  1. Una celda viva con menos de dos vecinos vivos muere.
  2. Una celda viva con dos o tres vecinos vivos sigue viva.
  3. Una celda viva con más de tres vecinos vivos muere.
  4. Una celda muerta con exactamente tres vecinos vivos se vuelve viva.

A continuación puedes ver cinco patrones (a,… ,e) y su evolución a lo largo de tres iteraciones (0,… ,2). Esta imagen es de Martin Gardner’s una columna en Scientific American, donde el Juego de la Vida de Conway se popularizó primero.

Example of five patterns from the Game of Life from Martin Gardner's column in Scientific America
Ejemplos de patrones del Juego de la Vida (source)

Estas reglas se aplican simultáneamente a todas las celdas de la cuadrícula, lo que da lugar a patrones y comportamientos fascinantes que emergen con el tiempo. El Juego de la Vida es un ejemplo clásico de autómata celular y ha sido estudiado extensamente por sus propiedades matemáticas y su capacidad para simular sistemas complejos.

Planeador

El patrón más popular en el Juego de la Vida es el «planeador». Un planeador es una configuración de celdas que se mueve diagonalmente a través de la cuadrícula a medida que avanza el juego. Es un patrón autorreplicante que puede usarse para crear otros patrones y estructuras interesantes. Aquí tienes un ejemplo de un planeador en movimiento:

Glider pattern of Game of Life
Planeador (Wikipedia)

Este es solo uno de los muchos patrones interesantes y complejos (link) que se pueden crear en el Juego de la Vida de Conway. Para explorar algunos de estos patrones, echa un vistazo a este Game of Life simulador, que es muy divertido de usar. El juego ha fascinado a la gente durante décadas y ha impulsado investigaciones importantes en matemáticas, informática y biología teórica.

Juego de la Vida en un Cubo

Al implementarlo en una pantalla de matriz de puntos LED 8×8 usando el controlador MAX7219, puedes visualizar el juego de forma tangible. A continuación puedes ver cómo Juego de la Vida en un Cubo que vamos a construir se verá cuando esté terminado.

Game of Life in a Cube
Juego de la Vida en un Cubo

En las siguientes secciones de este tutorial exploraremos los componentes necesarios, cómo conectar la pantalla y el código necesario para implementar el Juego de la Vida.

Componentes necesarios

A continuación encontrarás las piezas necesarias para este proyecto. Si solo quieres probar rápidamente y ejecutar el Juego de la Vida con Arduino, en lugar de comprar la pantalla de matriz de puntos 8×8 sugerida, compra esta (link). Es un módulo más grande y no encajará en el cubo, pero tiene la ventaja de que no necesitas soldar.

Si quieres construir el cubo, necesitarás el ESP8266 ESP-12F Mini (o una placa de tamaño similar) y la pantalla de matriz de puntos 8×8 sugerida. Ten en cuenta que necesitarás poder hacer algo de soldadura sencilla de cables y pines.

Arduino Uno

ESP8266 ESP-12F Mini

USB Data Sync cable Arduino

Cable USB para Arduino UNO

Dupont wire set

Juego de cables Dupont

Pantalla LED de matriz de puntos 8×8 con MAX7219

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Soldadura de la pantalla de matriz de puntos

Si compras la pantalla LED de matriz de puntos 8×8 con MAX7219 listada, notarás que la pantalla LED 8×8 y la placa con el controlador MAX7219 vienen por separado. Necesitarás soldarlos y asegurarte de que la orientación sea correcta. Si el número de parte de la pantalla LED está al frente, debes orientar la placa para que la etiqueta «DISY1» también esté al frente. Mira la imagen abajo.

Soldering of the MAX7219 8x8 Dot Matrix LED Display
Soldadura de la pantalla de matriz de puntos

Si usas los pines headers, esto no es crítico, ya que puedes rotar la pantalla después. Pero si sueldas la pantalla directamente a la placa, tendrás que hacerlo bien.

Por cierto, el MAX7219 es ese chip negro en la placa. Simplifica significativamente el control de la pantalla de matriz 8×8 y reduce especialmente el número de pines GPIO que necesitamos del Arduino. Para más detalles, echa un vistazo a nuestro MAX7219 LED dot matrix display Arduino tutorial y tal vez al datasheet mismo.

En la siguiente sección, te mostraré cómo conectar la pantalla a un Arduino.

Conexión de la pantalla de matriz de puntos

Una vez que tengas la pantalla soldada a la placa, conectarla a un Arduino es sencillo. La imagen abajo muestra el cableado.

Connecting the Dot Matrix Display with MAX7219 to the Arduino
Conexión de la pantalla de matriz de puntos al Arduino

Con esto se completa la conexión de la pantalla al Arduino. Si necesitas más detalles sobre el cableado de los componentes, aquí tienes la tabla completa de conexiones.

Desde Pin Color del cable Hacia Pin
Arduino 5V Rojo Pantalla VCC / 5V
Arduino GND Azul Pantalla GND
Arduino 12 Amarillo Pantalla CLK
Arduino 11 Naranja Pantalla DIN
Arduino 10 Verde Pantalla CS

Código para el Juego de la Vida

En esta sección escribiremos el código que ejecuta el Juego de la Vida en un Arduino y muestra el patrón planeador en la pantalla de matriz de puntos.

Instalar la librería LedControl

Para controlar la pantalla usaremos la LedControl librería. La instalación es sencilla. Solo ve a «Tools» y luego a «Manage Libraries..» en el IDE de Arduino:

Library Manager to install Libraries
Instalar librería vía Library Manager

Luego busca «LedControl» e instala la «LedControl library by Eberhard Fahle». La imagen abajo muestra cómo se ve una vez completada la instalación. Solo toma un segundo.

LedControl library in Library Manager
Librería LedControl instalada

Ahora estamos listos para escribir el código para Arduino.

Código para Arduino

Echa un vistazo rápido al código completo primero para tener una visión general. Explicaré los detalles en las siguientes secciones.

// Game of Life on an 8x8 Dot Matrix Display
#include "LedControl.h"

// Arduino
const byte CLK = 12; 
const byte DIN = 11;  
const byte CS = 10;    

LedControl lc = LedControl(DIN, CLK, CS, 0);

// glider
byte board[8] = {
  B00000000,
  B00000000,
  B00010000,
  B00001000,
  B00111000,
  B00000000,
  B00000000,
  B00000000,
};

void set_bit(byte *b, byte pos, byte val) {
  if (val > 0) {
    *b |= (1 << pos);
  } else {
    *b &= ~(1 << pos);
  }
}

byte get_bit(byte *b, byte pos) {
  return (*b >> pos) & 0x01;
}

void set(byte *board, byte r, byte c, byte val) {
  set_bit(&board[r], c, val);
}

byte get(byte *board, byte r, byte c) {
  return get_bit(&board[r], c);
}

void update_display() {
  lc.clearDisplay(0);
  for (int i = 0; i < 8; i++) {
    lc.setRow(0, i, board[i]);
  }	
}

int count_neighbors(byte r, byte c) {
  int count = 0;
  for (int rd = -1; rd <= 1; rd++) {
    for (int cd = -1; cd <= 1; cd++) {
      if (rd == 0 && cd == 0) continue;
      int nr = (r + rd + 8) % 8;
      int nc = (c + cd + 8) % 8;
      count += get(board, nr, nc);
    }
  }
  return count;
}

void update_board() {
  byte temp[8];
  byte alive;
  for (byte r = 0; r < 8; r++) {
    for (byte c = 0; c < 8; c++) {
      int neighbors = count_neighbors(r, c);
      if (get(board, r, c)) {
        alive = !(neighbors < 2 || neighbors > 3);
      } else {
        alive = neighbors == 3;
      }
      set(temp, r, c, alive);
    }
  }
  memcpy(board, temp, sizeof(board));
}

void setup() {
  Serial.begin(9600);
  lc.shutdown(0, false);
  lc.clearDisplay(0);
}

void loop() {
  for (int step = 0; step < 4; step++) {
    lc.setIntensity(0, step);
    update_display();
    update_board();
    delay(500);
  }
}

Constantes y variables

Comenzamos incluyendo la librería LedControl necesaria y definiendo los pines para el controlador MAX7219. El pin CLK está conectado al pin 12, el pin DIN al 11 y el pin CS al 10. También creamos una instancia de la LedControl clase con los pines especificados.

#include "LedControl.h"

const byte CLK = 12; 
const byte DIN = 11;  
const byte CS = 10;    

LedControl lc = LedControl(DIN, CLK, CS, 0);

Luego definimos el estado inicial del tablero del juego. El tablero se representa como una cuadrícula 8×8 de celdas, donde cada celda puede estar viva (1) o muerta (0). El estado inicial se define usando notación binaria y cada fila es un byte con 8 bits. Como tenemos 8 filas, obtenemos una matriz de bits 8×8 que coincide con la dimensión de nuestra pantalla LED 8×8.

byte board[8] = {
  B00000000,
  B00000000,
  B00010000,
  B00001000,
  B00111000,
  B00000000,
  B00000000,
  B00000000,
};

Funciones auxiliares

Definimos dos funciones auxiliares, set_bit() y get_bit(), para manipular bits individuales en un byte. Estas funciones se usan para establecer o obtener el valor de un bit específico en el tablero del juego.

void set_bit(byte *b, byte pos, byte val) {
  if (val > 0) {
    *b |= (1 << pos);
  } else {
    *b &= ~(1 << pos);
  }
}

byte get_bit(byte *b, byte pos) {
  return (*b >> pos) & 0x01;
}

También definimos dos funciones, set() y get(), para establecer u obtener el valor de una celda específica en el tablero del juego. Estas funciones usan las funciones set_bit() y get_bit() para manipular los bits individuales en el tablero.

void set(byte *board, byte r, byte c, byte val) {
  set_bit(&board[r], c, val);
}

byte get(byte *board, byte r, byte c) {
  return get_bit(&board[r], c);
}

Funciones de visualización y actualización

La función update_display() es responsable de actualizar la pantalla LED de matriz de puntos con el estado actual del tablero del juego. Limpia la pantalla y luego establece las filas de la pantalla basándose en los valores del tablero. Ten en cuenta que establecemos filas completas a la vez, ya que es más simple y rápido que establecer bits individuales.

void update_display() {
  lc.clearDisplay(0);
  for (int i = 0; i < 8; i++) {
    lc.setRow(0, i, board[i]);
  }    
}

La función count_neighbors() cuenta el número de vecinos vivos para una celda dada en el tablero. Itera sobre las celdas vecinas y verifica sus valores usando la función get().

int count_neighbors(byte r, byte c) {
  int count = 0;
  for (int rd = -1; rd <= 1; rd++) {
    for (int cd = -1; cd <= 1; cd++) {
      if (rd == 0 && cd == 0) continue;
      int nr = (r + rd + 8) % 8;
      int nc = (c + cd + 8) % 8;
      count += get(board, nr, nc);
    }
  }
  return count;
}

La función update_board() actualiza el tablero del juego según las reglas del Juego de la Vida. Crea un tablero temporal temp para almacenar los valores actualizados. Para cada celda en el tablero, cuenta el número de vecinos vivos usando la función count_neighbors() y aplica las reglas para determinar si la celda debe estar viva o muerta. Los valores actualizados se establecen en el tablero temporal usando la función set(). Finalmente, los valores actualizados se copian de nuevo al tablero original usando la función memcpy().

void update_board() {
  byte temp[8];
  byte alive;
  for (byte r = 0; r < 8; r++) {
    for (byte c = 0; c < 8; c++) {
      int neighbors = count_neighbors(r, c);
      if (get(board, r, c)) {
        alive = !(neighbors < 2 || neighbors > 3);
      } else {
        alive = neighbors == 3;
      }
      set(temp, r, c, alive);
    }
  }
  memcpy(board, temp, sizeof(board));
}

Funciones setup y loop

En la función setup(), inicializamos la comunicación serial para depuración, desactivamos el modo de apagado del controlador MAX7219 y limpiamos la pantalla.

void setup() {
  Serial.begin(9600);
  lc.shutdown(0, false);
  lc.clearDisplay(0);
}

En la función loop(), ejecutamos el Juego de la Vida durante cuatro pasos. Esto es porque el período del patrón planeador también es de 4 pasos. Después de 4 iteraciones, el planeador muestra el mismo patrón pero se ha desplazado diagonalmente un paso más.

Para cada paso, establecemos la intensidad de la pantalla LED de matriz de puntos. Esto resalta las diferentes fases de la evolución del patrón planeador. Si quieres que el planeador se muestre con brillo constante, puedes cambiarlo aquí. El valor de intensidad va de 0 a 15.

Después actualizamos la pantalla con el estado actual del tablero usando la función update_display(), actualizamos el tablero usando la función update_board() y hacemos una pausa de 500 milisegundos antes del siguiente paso. La pausa es solo para ralentizar el planeador. De nuevo, esto es algo que puedes cambiar fácilmente.

void loop() {
  for (int step = 0; step < 4; step++) {
    lc.setIntensity(0, step);
    update_display();
    update_board();
    delay(500);
  }
}

¡Y eso es todo! Ahora tienes el Juego de la Vida funcionando en un Arduino. Por supuesto, esto es solo el comienzo. Además del planeador, hay muchos otros patrones interesantes que podrías añadir al programa. Esos diferentes patrones podrían seleccionarse mediante un botón, un codificador rotatorio u otro dispositivo de entrada.

Sin embargo, tener todo en una carcasa bonita y la posibilidad de controlar el dispositivo vía una interfaz web sería mucho más genial. Y eso es lo que te mostraré en la siguiente sección.

No implementaremos la interfaz web, pero usaremos el ESP8266 ESP-12F Mini que tiene WiFi, para que podamos añadir esta funcionalidad más adelante. Además, el ESP8266 ESP-12F Mini tiene un tamaño similar al de la pantalla LED, lo que nos permite empaquetar todo ordenadamente en un cubo.

¡Vamos a continuar y montar el Juego de la Vida en un Cubo !

Configurando el ESP8266 ESP-12F Mini

La comunicación con el ESP8266 requiere que se instale un driver CH340. También necesitaremos instalar la placa en el IDE. Cómo hacerlo te lo mostraré en las siguientes secciones.

Instalando el driver CH340g

El driver CH340 es el software para un chip convertidor USB a serie que permite que la placa se comunique con el ordenador vía USB. Este driver se usa comúnmente en placas que no tienen interfaz USB integrada. Lo necesitaremos para programar el ESP8266 a través del puerto COM/USB.

  1. Descarga el  Windows CH340 Driver
  2. Descomprime el archivo
  3. Ejecuta el instalador que descomprimiste
  4. Conecta tu ESP8266 con un cable USB y deberías ver un puerto COM activo en el IDE de Arduino
COM port and board in Arduino IDE
Puerto COM y placa en el IDE de Arduino

Instalando la placa ESP8266

Luego necesitamos instalar la placa ESP8266. Ve a «File» y «Preferences» y escribe la siguiente URL en el campo «Additional Boards Manager URLs»:

http://arduino.esp8266.com/stable/package_esp8266com_index.json

La imagen abajo muestra dónde encontrar el campo de URLs en el diálogo de Preferencias.

Additional Boards Manager  ESP8266
Administrador adicional de placas y ESP8266

Luego abre «Tools», «Board» y «Boards Manager» e instala «esp8266 by ESP8266 Community».

Installation of the ESP8266 board
Instalación de la placa ESP8266

Uf, casi listo. Ahora podemos comunicarnos con la placa y compilar y subir sketches. Pero primero necesitamos cambiar un poco el código, ya que el ESP8266 usará pines diferentes al Arduino para comunicarse con la pantalla LED.

Constantes de pines y cableado

Simplemente reemplaza las constantes de pines en el código que son para Arduino por las siguientes.

// ESP8266 ESP-12F Mini
const byte DIN = 12;  // D6
const byte CS = 14;   // D5
const byte CLK = 13;  // D7

Por supuesto, ahora también necesitamos cablear el ESP8266 y la pantalla de matriz de puntos en consecuencia. Conecta D6 a DIN, D5 a CS y D7 a CLK. Finalmente conecta la alimentación (5V a VCC y G a GND). En la imagen abajo puedes ver cómo debería quedar cuando termines.

Wiring of the ESP8266 and the Dot Matrix Display
Cableado del ESP8266 y la pantalla de matriz de puntos

Revisa bien el cableado y las nuevas constantes de pines y luego podemos subir el sketch del Juego de la Vida al ESP8266. Los cables deben conectarse al lado de entrada, marcado con «-> IN», de la placa de la pantalla.

Selecciona la placa ESP8266 y sube el sketch

Abre el IDE de Arduino y bajo «Tools», «Board», «ESP8266» selecciona la placa «LOLIN(WEMOS) D1 R2 and mini».

Selection of "LOLIN(WEMOS) D1 R2 and mini" board
Selección de la placa «LOLIN(WEMOS) D1 R2 and mini»

Ahora deberías poder compilar y subir el sketch como de costumbre. Y la pantalla debería mostrar el planeador desplazándose por el tablero del juego.

Game of Life running on the ESP8266
Juego de la Vida corriendo en el ESP8266

¡Felicidades! El último paso es construir el cubo y colocar todo dentro.

Construyendo una carcasa

Usé una impresora 3D e imprimí las dos partes que se muestran abajo. Puedes descargar los archivos STL para las dos partes here.

Front and back part of the housing
Parte frontal y trasera de la carcasa

La pantalla de matriz de puntos encaja (justo) a presión en el marco frontal y el ESP8266 se sitúa en el medio entre las dos partes, fijado con un poco de pegamento caliente para mayor estabilidad. Si encajas las dos partes, ya está listo. Las imágenes abajo muestran la carcasa completa con y sin la pantalla y la placa insertadas.

Complete housing with and without display and board inserted.
Carcasa completa con y sin pantalla y placa insertadas

Y eso es todo. Un Juego de la Vida en un Cubo ¡con muy buen aspecto!

Como dije antes, ¡esto es solo el comienzo! Podríamos añadir muchos más patrones además del icónico «planeador». Un patrón similar e interesante es el Lightweight Spaceship. Pruébalo:

// lightweight spaceship
byte board[8] = {
   B00000000,
   B00100100,
   B01000000,
   B01000100,
   B01111000,
   B00000000,
   B00000000,
   B00000000,
};

Y aquí otra idea: con una placa con Wi-Fi dentro del cubo, podría controlarse completamente vía una interfaz web, incluyendo la definición de nuevos patrones para ejecutar. ¡Todo depende de ti!

Resumen

En este tutorial, hemos implementado con éxito el Juego de la Vida en una pantalla de matriz de puntos LED 8×8 usando el controlador MAX7219. Comenzamos entendiendo qué es el Juego de la Vida y cómo funciona. Luego, listamos los componentes necesarios para este proyecto, que incluían una placa Arduino, una pantalla LED de matriz de puntos 8×8 y el controlador MAX7219.

Después, repasamos el proceso de conectar la pantalla LED de matriz de puntos a la placa Arduino usando el controlador MAX7219. Explicamos las conexiones de cableado y proporcionamos un diagrama claro para facilitar el proceso.

Luego, profundizamos en el código para el Juego de la Vida. Explicamos la lógica detrás de la implementación y proporcionamos una guía paso a paso sobre cómo escribir el código. También incluimos el código completo para tu referencia.

Siguiendo las instrucciones de este tutorial, ahora deberías tener un Juego de la Vida funcionando en tu pantalla LED de matriz de puntos. Puedes experimentar con diferentes configuraciones iniciales y observar cómo evolucionan los patrones con el tiempo.

Si tienes más preguntas o ideas interesantes, no dudes en dejar un comentario.

Enlaces

Aquí algunos enlaces adicionales que pueden resultarte interesantes o útiles.