arduino

Juego de la serpiente (snake) en Arduino

Hoy vamos a ver el desarrollo del videojuego snake en Arduino usando una LCD de 16 x 2.

Lo que haremos será dibujar una serpiente por la pantalla y permitir que se mueva, creciendo en su tamaño, comiendo y refrescando la LCD con las nuevas posiciones.

Es decir, un juego snake completo pero usando un Arduino y una LCD como hardware, nada de computadoras.

Aunque parece simple la verdad es que fue algo difícil.

Snake en Arduino

Antes de comenzar el post dejo el vídeo de demostración. Estoy usando un Arduino Mega, pero supongo que en el UNO debe funcionar muy bien. También uso una LCD (obviamente) con un módulo I2C.

Quiero recalcar que es la primera parte; el primer avance, y no la versión final, pero la parte que hace posible al juego ya está terminada.

Motivación

Lo hice por diversión, ya que anteriormente había querido hacerlo pero me ganó el estrés o lo que sea que haya sido; pero ahora, con más experiencia intenté hacerlo de nuevo y avancé bastante.

Además, quería procrastinar al máximo para evitar hacer mi tarea, así que limpié mi viejo Arduino MEGA, conecté todo y comencé a programar.

Tamaño de los pixeles

La LCD que tengo es de 16 x 2 y permite usar 32 pixeles si lo vemos de ese modo. Pero, ¿Alguien imagina una serpiente que ocupe toda la pantalla y que sea así de gigante?

Aquí la LCD traída de mi post sobre LCD y Arduino:

Entonces una serpiente de ese tamaño no se vería nada bien; no habría espacio. Así que me pregunté…

¿Y si ocupo un pixel pequeño? ya que si nos fijamos, cada cuadro tiene 40 cuadros más pequeños dentro del mismo:

Cuadrícula de LCD

De este modo la serpiente tendría más espacio, ya que suponiendo que ocupara toda la pantalla se tendría el total de 1280 pixeles, con una altura de 16 y una anchura de 80.

A partir de este momento llamaremos pixel a los cuadros pequeños (los que en total son 1280).

Creando una forma personalizada

Pero la LCD no es un lienzo en donde puedas pintar cada pixel, ya que dibuja en un cuadro grande. La buena noticia es que se puede crear una forma personalizada definiendo un arreglo de tipo byte de longitud 8 en donde cada elemento representa un número binario indicando la distribución de la fila.

Por ejemplo, lo siguiente dibujará una sonrisa:

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

byte smiley[8] = {
  B00000,
  B10001,
  B00000,
  B00000,
  B10001,
  B01110,
  B00000,
};

void setup() {
  lcd.createChar(0, smiley);
  lcd.begin(16, 2);  
  lcd.write(byte(0));
}

void loop() {}

Un 1 es un pixel encendido, un 0 es un pixel apagado. Eso abre un montón de posibilidades ya que podríamos dibujar a la serpiente en cualquier lugar, incluso dibujar su cuerpo por partes.

Los bytes son 8 bits

Aunque tenemos un byte en la forma B00100 en realidad podría ser convertido a decimal por su posición, terminando en que B00100 es 4. Entonces podemos definir el dato (o la fila de la forma) como un byte o un decimal.

Si no entiendes bien, mira la conversión de números binarios a decimal.

Creando figura personalizada a partir de matriz

El juego de snake en Arduino cuenta con una matriz que es el tablero de juego. Al inicio la llené manualmente:

int prueba[16][20] = {
    {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
    {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
    {0, 0, 1, 1, 1,/*  */1, 1, 1, 1, 1, /*  */1, 1, 1, 1, 1,/*  */0, 0, 0, 0, 0,},
    {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 1,/*  */0, 0, 0, 0, 0,},
    {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 1,/*  */0, 0, 0, 0, 0,},
    {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 1,/*  */1, 1, 1, 0, 0,},
    {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 1, 0, 0,},
    {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 1, 0, 0,},

    /***************************************************************************/    {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 1, 0, 0,},
    {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 1, 0, 0,},
    {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 1,/*  */1, 1, 1, 0, 0,},
    {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 1,/*  */0, 0, 0, 0, 0,},
    {0, 0, 0, 0, 0,/*  */0, 0, 1, 1, 1, /*  */1, 1, 1, 1, 1,/*  */0, 0, 0, 0, 0,},
    {0, 0, 0, 0, 0,/*  */0, 0, 1, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
    {0, 0, 0, 0, 0,/*  */0, 0, 1, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
    {0, 0, 0, 0, 0,/*  */0, 0, 1, 1, 1, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  };

Pero como cada cuadro grande tiene 8 x 5 cuadros tuve que leer en porciones de ese tamaño:

byte figura[8];
int numeroFigura = 0;
for (int cuadritoX = 0; cuadritoX < 4; cuadritoX++) {
  for (int cuadritoY = 0; cuadritoY < 2; cuadritoY++) {
    // Magia aquí
  }
}

Por cada cuadro gigante tenía que convertir cada fila en un byte y lo colocaba dentro del arreglo que formaría mi carácter personalizado más tarde:

for (int x = 0; x < 8; x++) {
  int numero = 0;
  int indice = cuadritoY == 0 ? x : (x + 8);
  int inicio = cuadritoX * 5;
  // Quién te conoce math.pow
  if (prueba[indice][inicio + 0] == 1)numero += 16;
  if (prueba[indice][inicio + 1] == 1)numero += 8;
  if (prueba[indice][inicio + 2] == 1)numero += 4;
  if (prueba[indice][inicio + 3] == 1)numero += 2;
  if (prueba[indice][inicio + 4] == 1)numero += 1;

  ///
  figura[x] = numero;
}

Después de llenar el arreglo, lo almacenaba en la LCD y creaba un char, aumentando además un contador que llevaba el registro de qué número de figura iba:

pantalla.createChar(numeroFigura, figura);
pantalla.setCursor(cuadritoX, cuadritoY); // X, Y
pantalla.write(byte(numeroFigura));
numeroFigura++;

Dibujando matriz en pantalla LCD

El código completo que dibuja la matriz, convirtiendo los ceros y unos en bytes, además de guardar y crear la figura es el siguiente:

byte figura[8];
int numeroFigura = 0;
for (int cuadritoX = 0; cuadritoX < 4; cuadritoX++) {
  for (int cuadritoY = 0; cuadritoY < 2; cuadritoY++) {
    for (int x = 0; x < 8; x++) {
      int numero = 0;
      int indice = cuadritoY == 0 ? x : (x + 8);
      int inicio = cuadritoX * 5;
      // Quién te conoce math.pow
      if (prueba[indice][inicio + 0] == 1)numero += 16;
      if (prueba[indice][inicio + 1] == 1)numero += 8;
      if (prueba[indice][inicio + 2] == 1)numero += 4;
      if (prueba[indice][inicio + 3] == 1)numero += 2;
      if (prueba[indice][inicio + 4] == 1)numero += 1;

      ///
      figura[x] = numero;
    }


    pantalla.createChar(numeroFigura, figura);
    pantalla.setCursor(cuadritoX, cuadritoY); // X, Y
    pantalla.write(byte(numeroFigura));
    numeroFigura++;
  }
}

Ese es el problema más complejo al que me enfrenté, porque aunque el código es pequeño, lo complejo fue llegar a la solución ya que las formas personalizadas se crean y dibujan en tiempo de ejecución.

Ahora que muestro este método también debo mostrar el que limpia la matriz:

void limpiarMatriz() {

  for (int y = 0; y < 16; y++) {
    for (int x = 0; x < 20; x++) {
      prueba[y][x] = 0;
    }
  }
}

Nos estamos quedando sin figuras personalizadas

Si has estado poniendo atención y siguiendo el código, te darás cuenta de que no se está usando toda la pantalla; y eso es porque solo podemos crear 8 figuras personalizadas (estoy limitado por la tecnología de mi tiempo.jpg)

Así que esa es la razón por la que no se ocupa toda la pantalla y solo 8 cuadros.

Primer dibujo de la serpiente

Pero bueno, con la matriz que se veía así (he marcado los unos para mostrar cómo se verá la serpiente):

Matriz para snake en Arduino

Se dibujaba lo siguiente:

Snake dibujada en LCD usando Arduino

Cuando llegué a este punto supe que lo había logrado y que era totalmente posible hacer el juego. Digamos que había terminado la rutina de programación para dibujar el escenario; lo demás era implementar la funcionalidad del juego.

Programando juego de la serpiente en Arduino

Ahora que había terminado la parte del dibujo en la matriz lo demás fue implementar la serpiente. Me inspiré del snake que hice con JavaScript, así que solo tenía que portarlo a C++, pues el Luis del pasado ya se había quebrado la cabeza programando al mismo.

Comenzamos definiendo a la serpiente con una clase, y un arreglo que iba a tener los pedazos de la serpiente:

class PedazoSerpiente {
  public:
    int x, y;

    PedazoSerpiente(int a, int b) {
      x = a; y = b;
    }

    PedazoSerpiente() {
    }
};


PedazoSerpiente serpiente[MAXIMA_LONGITUD_SERPIENTE];

El arreglo es de tamaño definido, el mismo tiene el valor de todos los pixeles que puede usar la serpiente. También necesité una variable para llevar el conteo de cuánto medía la serpiente (es decir, la longitud del arreglo era la capacidad, pero esta variable era la longitud)

Dirección de la serpiente

La dirección de la serpiente es importante; la he definido como 4 enteros usando #define. La función que cambia la dirección y valida las cosas es la siguiente:

#define DIRECCION_DERECHA 0
#define DIRECCION_IZQUIERDA 1
#define DIRECCION_ARRIBA 2
#define DIRECCION_ABAJO 3

int direccion = DIRECCION_DERECHA;

void cambiarDireccion(int nuevaDireccion) {
  if (
    nuevaDireccion != DIRECCION_DERECHA
    && nuevaDireccion != DIRECCION_IZQUIERDA
    && nuevaDireccion != DIRECCION_ARRIBA
    && nuevaDireccion != DIRECCION_ABAJO
  ) {
    return;
  }
  if (
    (nuevaDireccion == DIRECCION_DERECHA || nuevaDireccion == DIRECCION_IZQUIERDA)
    && (direccion == DIRECCION_DERECHA || direccion == DIRECCION_IZQUIERDA)
  ) return;

  if (
    (nuevaDireccion == DIRECCION_ARRIBA || nuevaDireccion == DIRECCION_ABAJO)
    && (direccion == DIRECCION_ARRIBA || direccion == DIRECCION_ABAJO)
  ) return;

  direccion = nuevaDireccion;
}

Por defecto la dirección es la derecha, así que la serpiente avanza hacia la derecha.

Agregar pedazo a la serpiente

Cuando la serpiente come, se hace más grande. Veamos la función que agrega un pedazo junto con algunas constantes para validar los límites:

#define ALTURA_TABLERO 16
#define ANCHURA_TABLERO 20
#define MAXIMA_LONGITUD_SERPIENTE (ALTURA_TABLERO * ANCHURA_TABLERO)

void agregarPedazo(int x, int y) {
  if (longitudSerpiente >= MAXIMA_LONGITUD_SERPIENTE) return;
  if (x + 1 >= ANCHURA_TABLERO || x < 0)return;
  if (y + 1 >= ALTURA_TABLERO || y < 0)return;
  serpiente[longitudSerpiente] = PedazoSerpiente(x, y);
  longitudSerpiente++;
}

Movimiento de la serpiente

Ya tenemos dirección y tamaño de la serpiente, pero todavía no la estamos moviendo; es decir, no estamos actualizando sus coordenadas en cada paso. El método que lo hace es:

void moverSerpiente() {
  for (int i = longitudSerpiente - 1; i >= 1; i--) {
    serpiente[i].x = serpiente[i - 1].x;
    serpiente[i].y = serpiente[i - 1].y;
  }
  switch (direccion) {
    case DIRECCION_DERECHA:
      if (serpiente[0].x + 1 >= ANCHURA_TABLERO)serpiente[0].x = 0;
      else serpiente[0].x++;
      break;
    case DIRECCION_IZQUIERDA:
      if (serpiente[0].x <= 0)serpiente[0].x = ANCHURA_TABLERO - 1;
      else serpiente[0].x--;
      break;
    case DIRECCION_ARRIBA:
      if (serpiente[0].y <= 0)serpiente[0].y = ALTURA_TABLERO - 1;
      else serpiente[0].y--;
      break;
    case DIRECCION_ABAJO:
      if (serpiente[0].y + 1 >= ALTURA_TABLERO)serpiente[0].y = 0;
      else serpiente[0].y++;
      break;
  }
}

Valida la dirección y si se toca un borde lo convierte en un portal que mueve la serpiente al borde contrario.

Dibujar serpiente en matriz

La serpiente en sí ya está preparada para ser pintada en cualquier lugar, y la matriz del juego ya está siendo dibujada en la LCD; lo que falta aquí es agregar la serpiente dentro de la matriz y el método que lo hace es:

void colocarSerpienteEnMatriz() {
  for (int i = 0; i < longitudSerpiente; i++) {
    int x = serpiente[i].y,
        y = serpiente[i].x;
    prueba[x][y] = 1;
  }
}

El puntaje

Como gran parte de la pantalla quedará desocupada se me ocurrió dibujar el puntaje ahí. Por el momento es un número estático, sin embargo es fácil de convertir en uno dinámico:

void dibujarPuntaje() {
  pantalla.setCursor(6, 0);
  pantalla.print("SCORE");
  pantalla.setCursor(6, 1);
  pantalla.print("666");
}

Movimiento artificial de la serpiente

Como no he colocado los métodos con los que se movería la serpiente decidí generar un número aleatorio para moverla. Primero alimenté al generador:

randomSeed(analogRead(0));

Después generé números aleatorios:

cambiarDireccion(random(0, 15));

Aunque la dirección solo va del 0 al 3 quise darle más aleatoridad generando números en un rango más amplio, para que no se moviera en cada paso y se viera más natural.

Poniendo todo junto

Serpiente en Arduino cambiando de dirección

El código completo queda así:

/*

    Programado por Luis Cabrera Benito
   ____          _____               _ _           _
  |  _ \        |  __ \             (_) |         | |
  | |_) |_   _  | |__) |_ _ _ __ _____| |__  _   _| |_ ___
  |  _ <| | | | |  ___/ _` | '__|_  / | '_ \| | | | __/ _ \
  | |_) | |_| | | |  | (_| | |   / /| | |_) | |_| | ||  __/
  |____/ \__, | |_|   \__,_|_|  /___|_|_.__/ \__, |\__\___|
         __/ |                               __/ |
        |___/                               |___/


    Blog:       https://parzibyte.me/blog
    Ayuda:      https://parzibyte.me/blog/contrataciones-ayuda/
    Contacto:   https://parzibyte.me/blog/contacto/
*/#include <LiquidCrystal_I2C.h>
#define ANCHURA_LCD 16
#define ALTURA_LCD 2
#define DIRECCION_LCD 0x3F // Si no sabes la dirección, visita https://parzibyte.me/blog/2018/02/02/obtener-direccion-modulo-i2c-lcd-arduino/
#define ALTURA_TABLERO 16
#define ANCHURA_TABLERO 20
#define MAXIMA_LONGITUD_SERPIENTE (ALTURA_TABLERO * ANCHURA_TABLERO)


#define DIRECCION_DERECHA 0
#define DIRECCION_IZQUIERDA 1
#define DIRECCION_ARRIBA 2
#define DIRECCION_ABAJO 3


LiquidCrystal_I2C pantalla(DIRECCION_LCD, ANCHURA_LCD, ALTURA_LCD);
int prueba[ALTURA_TABLERO][ANCHURA_TABLERO] = {
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},

  /***************************************************************************/  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
  {0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0, /*  */0, 0, 0, 0, 0,/*  */0, 0, 0, 0, 0,},
};

class PedazoSerpiente {
  public:
    int x, y;

    PedazoSerpiente(int a, int b) {
      x = a; y = b;
    }

    PedazoSerpiente() {
    }
};


PedazoSerpiente serpiente[MAXIMA_LONGITUD_SERPIENTE];
int longitudSerpiente = 0;
int direccion = DIRECCION_DERECHA;

void cambiarDireccion(int nuevaDireccion) {
  if (
    nuevaDireccion != DIRECCION_DERECHA
    && nuevaDireccion != DIRECCION_IZQUIERDA
    && nuevaDireccion != DIRECCION_ARRIBA
    && nuevaDireccion != DIRECCION_ABAJO
  ) {
    return;
  }
  if (
    (nuevaDireccion == DIRECCION_DERECHA || nuevaDireccion == DIRECCION_IZQUIERDA)
    && (direccion == DIRECCION_DERECHA || direccion == DIRECCION_IZQUIERDA)
  ) return;

  if (
    (nuevaDireccion == DIRECCION_ARRIBA || nuevaDireccion == DIRECCION_ABAJO)
    && (direccion == DIRECCION_ARRIBA || direccion == DIRECCION_ABAJO)
  ) return;

  direccion = nuevaDireccion;
}

void agregarPedazo(int x, int y) {
  if (longitudSerpiente >= MAXIMA_LONGITUD_SERPIENTE) return;
  if (x + 1 >= ANCHURA_TABLERO || x < 0)return;
  if (y + 1 >= ALTURA_TABLERO || y < 0)return;
  serpiente[longitudSerpiente] = PedazoSerpiente(x, y);
  longitudSerpiente++;
}

void moverSerpiente() {
  for (int i = longitudSerpiente - 1; i >= 1; i--) {
    serpiente[i].x = serpiente[i - 1].x;
    serpiente[i].y = serpiente[i - 1].y;
  }
  switch (direccion) {
    case DIRECCION_DERECHA:
      if (serpiente[0].x + 1 >= ANCHURA_TABLERO)serpiente[0].x = 0;
      else serpiente[0].x++;
      break;
    case DIRECCION_IZQUIERDA:
      if (serpiente[0].x <= 0)serpiente[0].x = ANCHURA_TABLERO - 1;
      else serpiente[0].x--;
      break;
    case DIRECCION_ARRIBA:
      if (serpiente[0].y <= 0)serpiente[0].y = ALTURA_TABLERO - 1;
      else serpiente[0].y--;
      break;
    case DIRECCION_ABAJO:
      if (serpiente[0].y + 1 >= ALTURA_TABLERO)serpiente[0].y = 0;
      else serpiente[0].y++;
      break;
  }
}

void colocarSerpienteEnMatriz() {
  for (int i = 0; i < longitudSerpiente; i++) {
    int x = serpiente[i].y,
        y = serpiente[i].x;
    prueba[x][y] = 1;
  }
}

void setup() {
  randomSeed(analogRead(0));
  pantalla.init();
  pantalla.backlight();
  for (int i = 0; i < 20; i++) {
    agregarPedazo(5, i);
  }




}

void dibujarPuntaje() {
  pantalla.setCursor(6, 0);
  pantalla.print("SCORE");
  pantalla.setCursor(6, 1);
  pantalla.print("666");
}

void dibujarMatriz() {
  pantalla.clear();
  byte figura[8];
  int numeroFigura = 0;
  for (int cuadritoX = 0; cuadritoX < 4; cuadritoX++) {
    for (int cuadritoY = 0; cuadritoY < 2; cuadritoY++) {
      for (int x = 0; x < 8; x++) {
        int numero = 0;
        int indice = cuadritoY == 0 ? x : (x + 8);
        int inicio = cuadritoX * 5;
        // Quién te conoce math.pow
        if (prueba[indice][inicio + 0] == 1)numero += 16;
        if (prueba[indice][inicio + 1] == 1)numero += 8;
        if (prueba[indice][inicio + 2] == 1)numero += 4;
        if (prueba[indice][inicio + 3] == 1)numero += 2;
        if (prueba[indice][inicio + 4] == 1)numero += 1;

        ///
        figura[x] = numero;
      }


      pantalla.createChar(numeroFigura, figura);
      pantalla.setCursor(cuadritoX, cuadritoY); // X, Y
      pantalla.write(byte(numeroFigura));
      numeroFigura++;
    }
  }


}

void limpiarMatriz() {

  for (int y = 0; y < 16; y++) {
    for (int x = 0; x < 20; x++) {
      prueba[y][x] = 0;
    }
  }
}

void loop() {
  //pantalla.noBlink();
  limpiarMatriz();
  cambiarDireccion(random(0, 15));
  moverSerpiente();
  colocarSerpienteEnMatriz();
  dibujarMatriz();
  dibujarPuntaje();
  //  pantalla.blink();
  delay(50);

}

Puedes verlo en mi perfil de GitHub. También tengo la demostración en mi canal de YouTube.

Conclusión

Como puedes ver, el juego no ha sido terminado; falta colocar la comida y mover a la serpiente con un dispositivo como botones o un joystick; cosa que haré en un futuro.

La razón por la que pausé el desarrollo es que no pude conectar los botones porque están en mal estado, pero tan pronto compre nuevos componentes terminaré la serpiente.

Actualización: ¡el juego está terminado! míralo aquí.

Te invito a seguirme para que, cuando publique la segunda parte seas el primero en enterarte.

También dejo enlaces para aprender más sobre Arduino, ver el juego de snake en JavaScript o ver el juego de la batalla naval en Arduino.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Entradas recientes

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

3 días hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

3 días hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

3 días hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

3 días hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

3 días hace

Solución: Apache – Server unable to read htaccess file

Ayer estaba editando unos archivos que son servidos con el servidor Apache y al visitarlos…

4 días hace

Esta web usa cookies.