SNAKE CON ARDUINO

INTRODUCCIÓN, IDEAS Y OBJETIVOS

El proyecto que hemos desarrollado utilizando Arduino se trata del videojuego clásico conocido como «Snake», implementado con una matriz LED 8×8, un joystick, una pantalla LCD y un zumbador.

El juego de Snake es ampliamente reconocido y ha sido disfrutado por jugadores de todas las edades, como todos lo habíamos jugado, nuestro objetivo era crear el juego, inicialmente solo iba a haber un modo de juego, pero mas tarde, pensamos que desarrollar un segundo modo de juego podía hacer nuestro programa más completo. Consiste en controlar una serpiente que se desplaza por una cuadrícula, con el objetivo de comer la comida dispersa en el tablero para aumentar su tamaño. Sin embargo, debemos evitar chocar con nuestra propio cuerpo, ya que eso significaría el fin del juego.

En este proyecto, hemos utilizado una placa de Arduino, una matriz LED 8×8 y un joystick para crear una experiencia interactiva. La matriz LED se encarga de mostrar el tablero de juego, mientras que el joystick nos permite controlar la dirección de la serpiente. Además, hemos implementado efectos de sonido con un zumbador y un sistema de puntuación para hacer el juego más completo y emocionante que se muestra en la pantalla LCD, además, esta pantalla nos permite elegir los distintos modos de juego.

COMPONENTES

Para desarrollarlo, hemos usado los siguientes componentes:

COMPONENTESCOSTE
Placa Arduino—–
Joystick—–
Zumbador—–
Cables—–
LCD 16X2—-
matriz leds 8×83×16€
Placa protoboard—-

PARTE HARDWARE

Para la parte hardware de este proyecto, hemos necesitado dos pantallas diferentes: una matriz de leds 8×8 para el juego de la serpientes, y una pantalla LCD 16×2 para escribir la puntuación de los jugadores. Además para controlar la serpientes hemos necesitado un joystick y para los sonidos hemos utilizado un zumbador.

No nos ha hecho falta ninguna resistencia o potenciómetro para la pantalla LCD ya que los 5V de la placa de Arduino han dado toda la potencia necesaria para todos los componentes del diagrama.

PARTE SOFTWARE

En cuanto al software, una de las primeras cosas que tuvimos que hacer fue ver que librerías debíamos usar para manejar la matriz LED y el LCD.

Creemos que el código es fácil de leer y entender, gracias al uso de nombres de variables y funciones descriptivas y a la estructura clara y ordenada del código.

Al principio, creamos un código para jugar únicamente a un modo de juego, jugando a este modo de juego desde el loop y usando las funciones que también usaríamos posteriormente en el código final. Las funciones utilizadas son:

  • mover_serpiente(direccion);
  • dibujar_serpiente();
  • comprobar_comida();
  • comprobar_choque();
  • delay(velocidad);

Posteriormente, al crear el segundo modo de juego y la posibilidad de elegir juego tuvimos que crear una condición en el if para cuando el juego hubiera terminado y asi poder elegir el modo de juego. El cual se sigue jugando en el loop con las mismas funciones, pero estas cambian dependiendo del modo de juego establecido.

Crear un segundo modo fue fácil ya que el código está organizado en módulos, lo que facilita la reutilización de código y permite una fácil implementación de nuevas funcionalidades.

#include "LedControl.h"
#include "pitches.h"
#include <LiquidCrystal.h>

LedControl lc = LedControl(13, 11, 12, 1);
// Definir los pines de conexión de la pantalla LCD
const int rs = 9, en = 7, d4 = 6, d5 = 5, d6 = 4, d7 = 3;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

const int boton = 2;
int joy_Pin1 = A0;
int joy_Pin2 = A1;
const int BUZZER = 8;

int eje_x;
int eje_y;
int tipo_juego;
bool come = false;
String direccion;
int serpiente_X[36];  // son 36 las posibles posiciones en pantalla para el eje x
int serpiente_Y[36];  // son 36 las posibles posiciones en pantalla para el eje y
int longitudSerpiente;
int comida_X;
int comida_Y;
boolean fin = true;
int velocidad;
bool f[8][8] = {
  { 1, 1, 0, 0, 0, 0, 0, 0 },
  { 1, 1, 0, 1, 1, 0, 0, 0 },
  { 1, 1, 0, 1, 1, 0, 0, 0 },
  { 1, 1, 0, 1, 1, 0, 0, 0 },
  { 1, 1, 0, 1, 1, 0, 0, 0 },
  { 1, 1, 0, 1, 1, 0, 0, 0 },
  { 1, 1, 1, 1, 1, 1, 1, 1 },
  { 1, 1, 1, 1, 1, 1, 1, 1 }
};


int melodia[] = { NOTE_C4, NOTE_E4, NOTE_G4 };
int duracion[] = { 250, 250, 500 };

void setup() {
  Serial.begin(9600);

  lc.shutdown(0, false);
  lc.setIntensity(0, 5);
  lc.clearDisplay(0);  // limpiar la matriz
  lcd.begin(16, 2);    // Inicializa la pantalla LCD con 16 columnas y 2 filas
  lcd.clear();         // Limpia la pantalla LCD
}


void loop() {
  if (fin) {
    lcd.clear();
    lcd.print("1. Facil");
    lcd.setCursor(0, 2);
    lcd.print("2. Dificil");
    int opcion = elegir_juego();
    lcd.clear();
    velocidad = 300;
    come = false;
    if (opcion == 1) {
      lcd.print("Modo facil");
      lcd.setCursor(0, 2);
      lcd.print("Score=1");
      tipo_juego = 1;
      comenzar_juego();
    } else if (opcion == 2) {
      lcd.print("Modo dificil");
      lcd.setCursor(0, 2);
      lcd.print("Score=1");
      tipo_juego = 2;
      comenzar_juego();
    }
    fin = false;
  }
  lc.clearDisplay(0);
  eje_x = analogRead(joy_Pin1);
  eje_y = analogRead(joy_Pin2);
  // en que dirección nos movemos?
  if (eje_y >= 0 && eje_y < 420) {
    if (direccion != "arriba") direccion = "abajo";
  }
  if (eje_y > 580 && eje_y <= 1023) {
    if (direccion != "abajo") direccion = "arriba";
  }
  if (eje_x >= 0 && eje_x < 420) {
    if (direccion != "izquierda") direccion = "derecha";
  }
  if (eje_x > 580 && eje_x <= 1023) {
    if (direccion != "derecha") direccion = "izquierda";
  }
  mover_serpiente(direccion);
  dibujar_serpiente();
  comprobar_comida();
  comprobar_choque();  // comprueba si choca consigo mismo
  delay(velocidad);

}

void comenzar_juego() {
  comida_X = random(0, 8);
  comida_Y = random(0, 8);
  longitudSerpiente = 1;
  direccion = "arriba";
}

void mover_serpiente(String dire) {
  for (int i = longitudSerpiente - 1; i > 0; i--) {
    serpiente_X[i] = serpiente_X[i - 1];
    serpiente_Y[i] = serpiente_Y[i - 1];
  }

  if (dire == "arriba") {
    if (serpiente_Y[0] == 0) {
      serpiente_Y[0] = 7;
    } else {
      serpiente_Y[0]--;
    }
  } else if (dire == "abajo") {
    if (serpiente_Y[0] == 7) {
      serpiente_Y[0] = 0;
    } else {
      serpiente_Y[0]++;
    }
  } else if (dire == "izquierda") {
    if (serpiente_X[0] == 0) {
      serpiente_X[0] = 7;
    } else {
      serpiente_X[0]--;
    }
  } else if (dire == "derecha") {
    if (serpiente_X[0] == 7) {
      serpiente_X[0] = 0;
    } else {
      serpiente_X[0]++;
    }
  }
}

void dibujar_serpiente() {
  for (int i = 0; i < longitudSerpiente; i++) {
    if (come == false || tipo_juego != 2 || longitudSerpiente - 1 != i) {
      lc.setLed(0, serpiente_Y[i], serpiente_X[i], true);  // (nº dispositivo, fila, columna, valor)
    } else {
      come = false;
    }
  }
}

void comprobar_comida() {
  if (serpiente_X[0] == comida_X && serpiente_Y[0] == comida_Y) {
    // si coincide la cabeza de la serpiente con la comida, es que se la ha comido y crece un LED
    if (tipo_juego == 1) {
      longitudSerpiente++;
    } else if (tipo_juego == 2) {
      longitudSerpiente += 2;
      come = true;
      velocidad -= 10;
    }
    lcd.setCursor(6, 2);
    lcd.print(longitudSerpiente);
    playFoodEatenSong();
    // nueva posicion
    bool posicion = false;
    while (!posicion) {
      comida_X = random(0, 8);
      comida_Y = random(0, 8);
      posicion = colocarManzana(comida_X, comida_Y);
    }
  }
  lc.setLed(0, comida_Y, comida_X, true);
  delay(10);  // haremos que parpade
  lc.setLed(0, comida_Y, comida_X, false);
  delay(10);  // haremos que parpade
}

void comprobar_choque() {
  for (int i = 1; i < longitudSerpiente; i++) {
    if (serpiente_X[0] == serpiente_X[i] && serpiente_Y[0] == serpiente_Y[i]) {
      fin = true;
      lcd.setCursor(0, 0);
      lcd.print("   GAME OVER   ");

      mostrarF();
      playGameOverSong();
      delay(2000);
      lc.clearDisplay(0);
    }
  }
}

void playGameOverSong() {
  for (int i = 0; i < sizeof(melodia) / sizeof(melodia[0]); i++) {
    tone(BUZZER, melodia[i], duracion[i]);
    delay(duracion[i]);
    noTone(BUZZER);
  }
}
void playFoodEatenSong() {
  tone(BUZZER, 500, 100);
}

void mostrarF() {
  for (int row = 0; row < 8; row++) {
    for (int col = 0; col < 8; col++) {
      if (f[row][col]) {
        lc.setLed(0, row, col, true);
      } else {
        lc.setLed(0, row, col, false);  // Apaga el LED
      }
      delay(50);  // Espera 50 milisegundos antes de avanzar al siguiente LED
    }
  }
}
int elegir_juego() {
  while (true) {
    eje_x = analogRead(joy_Pin1);
    //eje_y = analogRead(joy_Pin2);
    if (eje_x >= 0 && eje_x < 360) {
      return 2;  // Si el joystick se mueve hacia arriba, selecciona el primer juego
    }
    if (eje_x > 620 && eje_x <= 1023) {
      return 1;  // Si el joystick se mueve hacia abajo, selecciona el segundo juego
    }
  }
}

bool colocarManzana(int x, int y) {
  bool salida = true;
  for (int i = 0; i < longitudSerpiente; i++) {
    if (serpiente_Y[i] == y && serpiente_X[i] == x) {
      salida = false;
      break;
    }
  }
  return salida;
}

CASOS DE USO

  • Jugar al juego Snake: El principal caso de uso es permitir a los usuarios jugar al juego Snake en una matriz LED 8×8 controlada por Arduino. Los usuarios pueden mover la serpiente utilizando un joystick y tratar de comer la comida para aumentar su puntuación. El código se encarga de controlar la lógica del juego, como el movimiento de la serpiente, la detección de colisiones y la actualización de la matriz LED.
  • Selección del modo de juego: El código incluye un menú en la pantalla LCD que permite a los usuarios seleccionar el modo de juego (fácil o difícil) utilizando el joystick. Esto proporciona opciones diferentes en términos de velocidad y dificultad del juego, lo que permite a los usuarios ajustar su experiencia de juego según sus preferencias.
  • Generación aleatoria de comida: El código se encarga de generar aleatoriamente la posición de la comida en la matriz LED. Esto asegura que la comida aparezca en diferentes ubicaciones cada vez que se juega el juego, lo que añade variabilidad y desafío al juego.
  • Efectos de sonido: El código utiliza un buzzer para reproducir efectos de sonido, como la reproducción de una melodía cuando la serpiente come la comida y la reproducción de un sonido de juego terminado cuando la serpiente choca consigo misma. Esto añade elementos auditivos al juego y mejora la experiencia del usuario.

PROBLEMAS Y SOLUCIONES

A lo largo de todo el proyecto hemos encontrado varios problemas, algunos de los mas importantes son:

Crear la parte software debido a la dificultad del algoritmo

Crear el código del programa no fue tarea fácil ya que el algoritmo no era sencillo, así que tuvimos que dedicar más tiempo al código del programa. Puede ser una solución válida para abordar la dificultad del algoritmo en un problema de software. Sin embargo, es importante tener en cuenta que no se puede dejar de lado la parte hardware del proyecto.

Distinguir la manzana de la serpiente

Al principio cuando hacíamos partidas muy largas para comprobar que el juego funcionaba bien nos dimos cuanta que la manzana es muy poco diferenciable de la serpiente ya que el único color de la pantalla de LEDs es el rojo. Una posible solución fue cambiar a una pantalla RGB, pero decidimos hacer parpadear la manzana de tal manera que fuese fácil de ver y de diferenciar con la serpiente.

Conectar la matriz de LEDs junto con la pantalla LCD

En nuestro mapa de conexiones inicial figuraba una resistencia en el voltaje de contraste de la pantalla LCD y en la saturación LED de la misma. Pero vimos que la potencia que le llegaba era muy baja y se veía muy mal. Barajamos la posibilidad de añadir una pila de 9V al circuito, pero tendríamos que volver a medir y ver donde poner mas resistencias, era demasiado engorroso. Finalmente quitamos las resistencias y comprobamos en simulaciones web como TinkerCAD que la pantalla LCD no se quemaba.

POSIBLES MEJORAS

  • Utilizar matriz de Leds RGB: al utilizar una matriz de Leds RGB, se pueden crear efectos visuales en el juego Snake, como la animación de colores o la iluminación de diferentes áreas de la matriz para resaltar diferentes elementos del juego.
  • Incluir más minijuegos: incluir más minijuegos en el proyecto de Arduino del juego Snake puede aumentar la diversión y el interés del usuario en el juego.
  • Música de fondo mientras juegas: agregar música de fondo al juego puede ayudar a crear una ambiente más emocionante y entretenido mientras se juega. La música también puede incrementar el interés y atención de los usuarios.
  • Agregar funcionalidad de pausa: agregar una función de pausa al juego permite al jugador detener el juego temporalmente y reanudarlo más tarde sin tener que comenzar desde cero. Esto puede ser útil si el jugador necesita tomar un descanso o si tiene que hacer otra cosa pero no quiere abandonar el juego por completo.

Video Explicativo

Proyecto Final

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *