Estoy contento de anunciar que al fin he terminado el videojuego que comencé hace casi un mes. Se trata del juego de la serpiente, viborita, snake o como lo conozcas; pero en Arduino usando una LCD.
Antes que nada te invito a ver la primera parte en donde todavía no terminaba el proyecto, el cual pausé porque no tenía los componentes necesarios, pero recientemente llegó mi pedido y pude terminar el proyecto.
Vamos a usar cosas simples, pero si eres un principiante te recomiendo leer los enlaces que dejo a continuación. Para empezar, vamos a generar números aleatorios con Arduino; esto servirá para colocar la comida de manera aleatoria.
En segundo lugar vamos a usar un Joystick conectado al Arduino.
Es como combinar el circuito de LCD I2C con Arduino y Arduino con Joystick. Aquí está:
Hay que notar que no he conectado el botón del joystick, pues por ahora no tiene ningún uso.
Nota: he usado un Arduino MEGA pero en un UNO debería funcionar como un encanto.
Como lo dije, una de las mejoras o actualizaciones es que se coloca comida de manera aleatoria en el escenario.
Para dibujar la comida tenemos dos funciones; una de ella calcula las coordenadas y la otra la coloca sobre el escenario:
// Calcula coordenadas aleatorias para colocar la comida
void randomizarComida() {
comidaX = random(0, ANCHURA_TABLERO);
comidaY = random(0, ALTURA_TABLERO);
}
// Coloca la comida en el escenrio
void acomodarComida() {
escenario[comidaY][comidaX] = 1;
}
Le damos seguimiento a la posición de la comida y calculamos si la serpiente colisiona con la misma, es decir, sabemos si la serpiente come, valga la redundancia, comida (podemos imaginar que la serpiente es una anaconda y la comida es un ratón).
De igual modo ahora el puntaje se aumenta de manera correcta.
// Saber en dónde está la comida para, más tarde, saber si la serpiente colisiona
int comidaX, comidaY;
// El puntaje. Se desborda cuando llega a 32767, creo
int puntaje = 0;
bool colisionaConComida() {
return serpiente[0].x == comidaX && serpiente[0].y == comidaY;
}
void loop() {
// ...
if (colisionaConComida()) {
puntaje++;
randomizarComida();
agregarPedazo(0, 0);// De hecho la posición del pedazo no importa al momento de agregar
}
// ...
}
También tenemos una función que lee un joystick y devuelve la dirección hacia la que se debe dirigir la serpiente:
// Leer del joystick
int obtenerDireccion() {
int valorX = analogRead(pinX),
valorY = analogRead(pinY);
if (valorX > 1000) {
return DIRECCION_ARRIBA;
} else if (valorX < 200) {
return DIRECCION_ABAJO;
}
if (valorY > 1000) {
return DIRECCION_DERECHA;
} else if (valorY < 200) {
return DIRECCION_IZQUIERDA;
}
// Regresamos algo inválido, pues no se cambió,
// y dependemos de la función cambiarDireccion
return -1;
}
Me di cuenta de que no era necesario invocar al método clear
de la pantalla, pues en cada momento se estaba dibujando el nuevo valor; eso redujo que la pantalla parpadee y sea desagradable a la vista.
Finalmente dejaré la prueba de que todo funciona. Primero, el código:
/*
Programado por Luis Cabrera Benito
14 de febrero de 2020
____ _____ _ _ _
| _ \ | __ \ (_) | | |
| |_) |_ _ | |__) |_ _ _ __ _____| |__ _ _| |_ ___
| _ <| | | | | ___/ _` | '__|_ / | '_ \| | | | __/ _ \
| |_) | |_| | | | | (_| | | / /| | |_) | |_| | || __/
|____/ \__, | |_| \__,_|_| /___|_|_.__/ \__, |\__\___|
__/ | __/ |
|___/ |___/
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
// Para leer el Joystick
const int pinX = 0,
pinY = 1;
LiquidCrystal_I2C pantalla(DIRECCION_LCD, ANCHURA_LCD, ALTURA_LCD);
int escenario[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;
// Saber en dónde está la comida para, más tarde, saber si la serpiente colisiona
int comidaX, comidaY;
// El puntaje. Se desborda cuando llega a 32767, creo
int puntaje = 0;
// Leer del joystick
int obtenerDireccion() {
int valorX = analogRead(pinX),
valorY = analogRead(pinY);
if (valorX > 1000) {
return DIRECCION_ARRIBA;
} else if (valorX < 200) {
return DIRECCION_ABAJO;
}
if (valorY > 1000) {
return DIRECCION_DERECHA;
} else if (valorY < 200) {
return DIRECCION_IZQUIERDA;
}
// Regresamos algo inválido, pues no se cambió,
// y dependemos de la función cambiarDireccion
return -1;
}
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;
escenario[x][y] = 1;
}
}
// Calcula coordenadas aleatorias para colocar la comida
void randomizarComida() {
comidaX = random(0, ANCHURA_TABLERO);
comidaY = random(0, ALTURA_TABLERO);
}
// Coloca la comid en el escenrio
void acomodarComida() {
escenario[comidaY][comidaX] = 1;
}
void setup() {
randomSeed(analogRead(0));
pantalla.init();
pantalla.backlight();
for (int i = 0; i < 3; i++) {
agregarPedazo(5, i);
}
randomizarComida();
pantalla.setCursor(0, 0);
pantalla.print("Snake");
pantalla.setCursor(0, 1);
pantalla.print("By Parzibyte");
delay(700);
pantalla.clear();
}
void dibujarPuntaje() {
pantalla.setCursor(6, 0);
pantalla.print("SCORE");
pantalla.setCursor(6, 1);
pantalla.print(puntaje);
}
void dibujarMatriz() {
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 (escenario[indice][inicio + 0] == 1)
numero += 16;
if (escenario[indice][inicio + 1] == 1)
numero += 8;
if (escenario[indice][inicio + 2] == 1)
numero += 4;
if (escenario[indice][inicio + 3] == 1)
numero += 2;
if (escenario[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++) {
escenario[y][x] = 0;
}
}
}
bool colisionaConComida() {
return serpiente[0].x == comidaX && serpiente[0].y == comidaY;
}
void loop() {
limpiarMatriz();
cambiarDireccion(obtenerDireccion());
moverSerpiente();
colocarSerpienteEnMatriz();
acomodarComida();
dibujarMatriz();
if (colisionaConComida()) {
puntaje++;
randomizarComida();
agregarPedazo(0, 0);// De hecho la posición del pedazo no importa al momento de agregar
}
dibujarPuntaje();
delay(10);// Mientras menor sea, más rápido va la serpiente, pero menor sensibilidad tiene el joystick
}
Aquí la demostración de mí jugando:
Lo tengo de igual modo en vídeo:
Por cierto, mi mayor puntaje creo que fue 169:
PD: lo sé, mi pantalla está oxidada, le tendré más cuidado.
Dejo el enlace a mi repositorio de GitHub en donde actualizaré el código si lo hago algún día; pero por ahora estoy muy satisfecho con el resultado. No olvides leer la primera parte, pues explico detalles importantes.
De hecho nadie solicitó este juego y no fue un requisito de ningún modo, simplemente fue un capricho. Lo intenté hacer hace algún largo tiempo y no pude, pero al intentarlo de nuevo, lo logré y quedó estupendo.
Si quieres puedes leer más sobre Arduino o Electrónica en mi blog.
Hoy te voy a presentar un creador de credenciales que acabo de programar y que…
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…
En este artículo te voy a enseñar cómo usar un "top level await" esperando a…
Esta web usa cookies.
Ver comentarios
Buenas tardes, queria consultar que libreria estan utilizando ya que liquidCrystal.h de arduino no me toma el programa
Creo que es LiquidCrystal_I2C
hay un error, no sé a que se refiera, ya pude la dirección LCD, y las librerías y me dice esto:
es un texto más grande pero se va repitiendo esto varias veces
In file included from C:\Program Files\WindowsApps\ArduinoLLC.ArduinoIDE_1.8.49.0_x86__mdqgnx93n4wtt\hardware\arduino\avr\cores\arduino/Arduino.h:32:0,
from sketch\Proyecto.ino.cpp:1:
C:\Program Files\WindowsApps\ArduinoLLC.ArduinoIDE_1.8.49.0_x86__mdqgnx93n4wtt\hardware\arduino\avr\cores\arduino/binary.h:58:0: note: this is the location of the previous definition
#define B00000100 4
C:\Program Files\WindowsApps\ArduinoLLC.ArduinoIDE_1.8.49.0_x86__mdqgnx93n4wtt\hardware\arduino\avr\cores\arduino/binary.h:58:19: error: expected ',' or '...' before numeric constant
#define B00000100 4
^
C:\Users\Pedro\OneDrive\Documentos\Arduino\libraries\LiquidCrystal_I2C/LiquidCrystal_I2C.h:51:12: note: in expansion of macro 'B00000100'
#define En B00000100 // Enable bit
^~~~~~~~~
Hola. Solo atiendo consultas en https://parzibyte.me/#contacto
Saludos!