Snake.io

Autores: Juan Carlos Moreno García, Jhostin Davis Ortiz Moreno y Alessandro Nuzzi Herrero

Grupo 7 / 2022-2023

Introducción

El juego de la serpiente, o mejor conocido como Snake Game, es considerado un clásico desde su lanzamiento a mediados de la época de los 70s.

Si bien es cierto que a día de hoy es un juego muy simple y para algunos un poco más de lo que gustaría, muchos juegos interesantes han surgido a raíz de él, entre ellos el conocido slither.io.

Originalmente, pensamos en recrear el juego original y llevarlo a un sistema empotrado, pero en vista de que se convertía en un proyecto un tanto simple, decidimos añadir nuevas funcionalidades. Nuestro proyecto se basa fuertemente en estas ideas y trata de llevar a una máquina física lo que vemos como mero software en dispositivos móviles y de sobremesa.

Es por eso último que nuestra máquina ofrece una experiencia única, fruto de una idea que lleva con nosotros mucho tiempo pero con un toque más moderno y competitivo, el juego snake.io para dos jugadores.

Para poder crear este juego, hemos necesitado componentes variados: matriz de LED RGB, joysticks analógicos, lector de tarjetas SD y altavoces, entre los componentes más destacados.

A continuación, se explicarán más detenidamente las diferentes partes que juntas componen la totalidad del proyecto, pasando por los componentes hardware utilizados, el software que controla toda la lógica del juego y todos los problemas que hemos afrontado, con nuestra forma de solucionarlos. Añadiremos también un apartado explicando las ideas a mejorar que nos han ido surgiendo por el camino y que finalmente no se han implementado, pero que en un futuro podrían ser factibles.

Desarrollo e Implementación

Para desarrollar la totalidad del proyecto, decidimos abordarlo de manera incremental, realizando pequeños avances, añadiendo funcionalidades, probando y estudiando el comportamiento de cada uno de los diferentes componentes por separado…hasta conseguir una versión final completamente funcional. 

Los pasos seguidos para implementar el juego han sido los siguientes::

  • Selección del hardware: Lo primero de todo fue decidir qué componentes íbamos a utilizar para implementar el juego. En un principio pensamos en utilizar 4 pantallas led 8×8, pero la gestión de estas era muy complicada, además de que ocuparían la mayoría de salidas de la placa de Arduino y solo tenían un color, por lo que no se distinguirían fácilmente las serpientes. Por ello, finalmente optamos por utilizar una matriz led rgb de 16×16, que facilitaba mucho su uso y podíamos emplear varios colores.
  • Detectar el movimiento del joystick: Una vez elegidos los componentes, el primer paso fue conectar un joystick a la placa y estudiar su funcionamiento y los valores que leía en función del movimiento, para poder así crear una función que determinara la dirección (arriba, abajo, izquierda o derecha).
  • Encender un led concreto: A continuación, procedimos a estudiar el funcionamiento de la matriz led con el uso de la librería recomendada. Fue en este punto donde nos dimos cuenta que la matriz era en realidad una tira de leds colocada de manera serpenteante, por lo que tuvimos que crear una función que transformara la posición del led que queríamos encender dada en fila y columna, a la posición real del led.
  • Mover el led encendido por la pantalla: Una vez sabíamos cómo encender un led concreto en la pantalla, lo siguiente fue simular que ese led se moviera por la pantalla, encendiendo el siguiente led y apagando el anterior.
  • Mover el led en función de la dirección del joystick: Acto seguido, conseguimos que el movimiento del led fuera en función de la dirección marcada por el joystick.
  • Mover 2 leds de manera independiente con los joystick: Posteriormente, añadimos un segundo joystick y encendimos otro led, consiguiendo que estos se movieran de manera independiente por la pantalla
  • Añadir el cuerpo de las serpientes: Con esto conseguido, el siguiente paso fue crear el struct que contiene la información de las serpientes y poder almacenar así todas las posiciones que corresponden al cuerpo de cada serpiente. Para simular su movimiento, simplemente eliminamos la última posición del cuerpo de la serpiente y añadimos la nueva en función de su dirección. Al inicio del juego, las serpientes tendrán un tamaño de 5 posiciones.
  • Implementar la lógica de ganador: En este punto el juego estaba casi terminado, por lo que procedimos a implementar la lógica que indica cuándo un jugador ha ganado. Esto sucederá únicamente cuando la cabeza de una de las serpientes se choque contra el cuerpo de la otra, siendo esta última la ganadora. Mencionar que, en caso de que ambas serpientes choquen de frente, se establecerá de manera aleatoria quién ha sido el ganador.
  • Añadir la lógica de las manzanas: Para finalizar la lógica del juego, simplemente faltaba añadir las manzanas de manera aleatoria por la pantalla y, en caso de que una serpiente pase por esa posición, la serpiente aumentará de tamaño en una unidad. Para ello, simplemente no borramos la última posición de la serpiente en esa iteración.
  • Añadir música y sonidos: Una vez implementada toda la lógica del juego, pasamos a añadir la música de fondo y los sonidos de inicio, comer una manzana y fin del juego.
  • Empaquetar todo en una caja: Por último, diseñamos una caja con dos orificios para empaquetar todo, simulando una consola portátil con dos mandos.

Parte Hardware

Para el desarrollo de este proyecto, hemos necesitado dos microcontroladores Arduino UNO, una matriz LED RGB de 16×16, dos altavoces compatibles con Arduino, dos joysticks, un lector de tarjetas SD y cables para realizar las conexiones.

Uno de los Arduino se ha utilizado para conectar los joysticks, la matriz LED y un altavoz. La función de este microcontrolador era principalmente gestionar todo aquello relacionado con el juego, así como los sonidos asociados a este.

Diseño hardware microcontrolador principal simulado en TinkerCad

Por otro lado, el otro microcontrolador Arduino se ha utilizado para gestionar la música de fondo del juego. Se ha conectado el lector de tarjetas SD y uno de los dos altavoces. De esta manera, reproduce el archivo de música que tenga almacenado la tarjeta SD en el altavoz.

Diseño hardware microcontrolador de música simulado en TinkerCad

Idealmente, se podría haber hecho el mismo proyecto con un único microcontrolador que incluyese cierto grado de concurrencia y permitiese la reproducción de música en paralelo al control del juego.

Por último, mencionar que ambos microcontroladores son alimentados con pilas de 9V.

Implementación final

Materiales, costes y precios

A continuación se muestra una tabla con los materiales y componentes utilizados para la realización del proyecto, así como su precio y el coste real que ha tenido sobre nosotros:

NombreCostePrecio
Módulo Lector SD5,49 €5,49 €
2 Altavoces 3W9,99 €9,99 €
2 Pilas 9V3,18 €3,18 €
Matriz LED RGB 16×1620,00 €28,99 €
2 Arduino Uno20-30 €
1 Protoboard4,79 €
1 Mini-Protoboard2,33 €
2 Joystick10 €
Cables5 €
Pegamento6,29 €
Pintura madera4 €
Madera10 €
Imanes7,32 €
Total38,66 €147,38 €

Parte Software

En cuanto al software del proyecto, mencionar que el código está modularizado en pequeñas funciones, evitando el código repetido y favoreciendo la reutilización de código, y está diseñado para poder ser escalable. Esto es posible gracias a la creación de un struct llamado snake_t que contiene toda la información necesaria de cada serpiente, como son su identificador, la posición de la cabeza, las posiciones del cuerpo, su color, el joystick asociado, la dirección, etc. Además, contamos un con array de snakes, una por cada jugador (en este caso 2) y las funciones se encargan de recorrer este array, por lo que, en caso de aumentar el número de jugadores, no sería necesario modificar el código.

Código principal

#include <Adafruit_NeoPixel.h>
#include <LinkedList.h>

#ifdef __AVR__
  #include <avr/power.h>
#endif

// Struct that contains snake data
struct snake_t {
  int id;
  int read[2] = {512, 512}; // Joystick read value
  int joystick[2]; // Joystick pins for X and Y axis
  int head[2]; // Snake head position (X, Y)
  LinkedList<int> tail = LinkedList<int>(); // Snake tail positions, including head
  uint32_t color;
  uint32_t headColor;
  char direction; // Snake direction
};

const int SIDE = 16; // Matrix side
const int PLAYERS = 2; // Number of players

// Dictionary used to calculate the real direction of the snake, taking into account that the movement of one snake is opposite to the other
const char DICT[2][4] = {{'d', 'u', 'r', 'l'}, {'u', 'd', 'l', 'r'}};

const int LED_PIN = 3; // Output pin to LED matrix
const int SPEAKER_PIN = 9; // Output pin to speaker

Adafruit_NeoPixel strip = Adafruit_NeoPixel(256, LED_PIN, NEO_GRB + NEO_KHZ800); // LED matrix object

// Initial values for snakes
const int JOYSTICK_PINS_0[2] = {A0, A1};
const int JOYSTICK_PINS_1[2] = {A2, A3};
const int INITIAL_HEAD_POS_0[2] = {0, 0};
const int INITIAL_HEAD_POS_1[2] = {15, 15};
const int INITIAL_TAIL_0[5] = {0, 1, 2, 3, 4};
const int INITIAL_TAIL_1[5] = {240, 241, 242, 243, 244};
const uint32_t SNAKE_COLOR_0 = strip.Color(255, 0, 0);
const uint32_t SNAKE_COLOR_1 = strip.Color(0, 0, 255);

// Numbers
const int RED_THREE[13] = {89, 88, 87, 86, 73, 54, 56, 55, 41, 22, 25, 24, 23};
const int BLUE_THREE[13] = {230, 231, 232, 233, 217, 198, 199, 200, 185, 166, 167, 168, 169};
const int RED_TWO[14] = {89, 88, 87, 86, 73, 57, 56, 55, 54, 38, 25, 24, 23, 22};
const int BLUE_TWO[14] = {230, 231, 232, 233, 214, 201, 198, 199, 200, 185, 166, 167, 168, 169};
const int RED_ONE[10] = {23, 40, 55, 72, 87, 24, 39, 56, 71, 88};
const int BLUE_ONE[10] = {168, 183, 200, 215, 232, 167, 184, 199, 216, 231};

// WIN letters
const int RED_WIN[23] = {
  125, 130, 157, 99, 123, 101, 121, 134, 153,      // W
  104, 119, 136, 151,                              // I
  106, 117, 138, 149, 139, 115, 109, 114, 141, 146 // N
};
const int BLUE_WIN[23] = {
  109, 114, 141, 147, 139, 149, 137, 118, 105,    // W
  103, 120, 135, 152,                             // I
  98, 125, 130, 157, 131, 123, 101, 122, 133, 154 // N
};

// Musical notes in 5 scales
const int C[5] = {131, 262, 523, 1046, 2093};  // Do
const int CS[5] = {139, 277, 554, 1108, 2217}; // Do#
const int D[5] = {147, 294, 587, 1175, 2349};  // Re
const int DS[5] = {156, 311, 622, 1244, 2489}; // Re#
const int E[5] = {165, 330, 659, 1319, 2637};  // Mi
const int F[5] = {175, 349, 698, 1397, 2794};  // Fa
const int FS[5] = {185, 370, 740, 1480, 2960}; // Fa#
const int G[5] = {196, 392, 784, 1568, 3136};  // Sol
const int GS[5] = {208, 415, 831, 1661, 3322}; // Sol#
const int A[5] = {220, 440, 880, 1760, 3520};  // La
const int AS[5] = {233, 466, 932, 1866, 3729}; // La#
const int B[5] = {247, 494, 988, 1976, 3951};  // Si

snake_t snakes[PLAYERS];
int winner;
int apple;
int delayValue;

void setup() {
  #if defined (__AVR_ATtiny85__)
    if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
  #endif

  randomSeed(analogRead(1));
  strip.begin();
  strip.setBrightness(100);
  strip.show(); 

  setupInitialValues();
}

void setupInitialValues() {
  setupSnake(&snakes[0], 0, JOYSTICK_PINS_0, INITIAL_HEAD_POS_0, INITIAL_TAIL_0, strip.Color(255, 0, 0), strip.Color(255, 0, 255), 'l');
  setupSnake(&snakes[1], 1, JOYSTICK_PINS_1, INITIAL_HEAD_POS_1, INITIAL_TAIL_1, strip.Color(0, 0, 255), strip.Color(255, 0, 255), 'r');
  delayValue = 200;
  initialAnimation();
  setApplePosition();
  printApple();
}

void setupSnake(snake_t *snake, int id, int joystick[2], int head[2], int tail[4], uint32_t color, uint32_t headColor, char direction) {
  snake->id = id;
  snake->joystick[0] = joystick[0];
  snake->joystick[1] = joystick[1];
  snake->head[0] = head[0];
  snake->head[1] = head[1];

  for (int i = 0; i < 5; i++) {
    snake->tail.add(tail[i]);
  }

  snake->color = color;
  snake->headColor = headColor;
  snake->direction = direction;
}

void initialAnimation() {
  // Red 3
  for (int i = 0; i < 13; i++) {
    strip.setPixelColor(RED_THREE[i], SNAKE_COLOR_0);
  }
  // Blue 3
  for (int i = 0; i < 13; i++) {
    strip.setPixelColor(BLUE_THREE[i], SNAKE_COLOR_1);
  }
  strip.show();
  emitSound(E[3], 100);
  noTone(SPEAKER_PIN);
  delay(900);
  turnOffLeds();  

  // Red 2
  for (int i = 0; i < 14; i++) {
    strip.setPixelColor(RED_TWO[i], SNAKE_COLOR_0);
  }
  // Blue 2
  for (int i = 0; i < 14; i++) {
    strip.setPixelColor(BLUE_TWO[i], SNAKE_COLOR_1);
  }
  strip.show();
  emitSound(E[3], 100);
  noTone(SPEAKER_PIN);
  delay(900);
  turnOffLeds(); 

  // Red 1
  for (int i = 0; i < 10; i++) {
    strip.setPixelColor(RED_ONE[i], SNAKE_COLOR_0);
  }
  // Blue 1
  for (int i = 0; i < 10; i++) {
    strip.setPixelColor(BLUE_ONE[i], SNAKE_COLOR_1);
  }
  strip.show();
  emitSound(E[3], 100);
  noTone(SPEAKER_PIN);
  delay(900);
  turnOffLeds(); 

  emitSound(C[4], 500);
  noTone(SPEAKER_PIN);
}

void loop() {
  winner = isEndGame();

  if (winner == -1)
    mainGame();
  else {
    endGameScreen();
  }
}

void mainGame() {
  for (int i = 0; i < PLAYERS; i++) {
    getMovement(&snakes[i]);
    moveSnake(&snakes[i]);
  }
  
  // As time passes, snakes move faster
  if (delayValue > 0)
    delay(delayValue--);
}

void getMovement(snake_t *snake) {
  snake->read[0] = analogRead(snake->joystick[0]);
  delay(100);                 
  snake->read[1] = analogRead(snake->joystick[1]);

  if (snake->read[0] >= 900) {
    snake->direction = DICT[snake->id][0];
  } else if (snake->read[0] < 200) {
    snake->direction = DICT[snake->id][1];
  } else if (snake->read[1] < 200) {
    snake->direction = DICT[snake->id][2];
  } else if (snake->read[1] >= 900) {
    snake->direction = DICT[snake->id][3];
  } 
}

void moveSnake(snake_t *snake) {
  if (!eatenApple(snake)) {
    int pos = snake->tail.pop();
    strip.setPixelColor(pos, strip.Color(0, 0, 0));
    strip.show();
  } else {
    emitSound(E[3], delayValue / 10);
    noTone(SPEAKER_PIN);
    setApplePosition();
    printApple();
  }

  nextRow(snake);
  nextColumn(snake);
  snake->tail.add(0, transform(snake->head[0], snake->head[1]));

  for (int i = 1; i < snake->tail.size(); i++) {
    strip.setPixelColor(snake->tail.get(i), snake->color);
  }

  strip.setPixelColor(snake->tail.get(0), snake->headColor);
  strip.show();
}

void nextRow(snake_t *snake) {
  switch (snake->direction) {
    case 'u':
      snake->head[0] += 1;
      break;
    case 'd':
      snake->head[0] -= 1;
      break;
  }

  if (snake->head[0] < 0)  
    snake->head[0] = SIDE - 1;
  
  snake->head[0] = snake->head[0] % SIDE;
}

void nextColumn(snake_t *snake) {
  switch (snake->direction) {
    case 'r': 
      snake->head[1] += 1;
      break;
    case 'l':
      snake->head[1] -= 1;
      break;
  }

  if (snake->head[1] < 0)  
    snake->head[1] = SIDE - 1;
  
  snake->head[1] = snake->head[1] % SIDE;
}

// Transform matrix position given in row and column to the actual matrix position
int transform(int row, int column) {
  if (row % 2 == 1)
    return row * SIDE + SIDE - (column + 1);
  
  return row*SIDE + column;
}

void setApplePosition() {
  apple = transform(random(SIDE), random(SIDE));

  for (int i = 0; i < PLAYERS; i++) {
    for (int j = 0; j < snakes[i].tail.size(); j++) {
      if (apple == snakes[i].tail.get(j))
        setApplePosition();
    }
  }
}

void printApple() {
  strip.setPixelColor(apple, strip.Color(255, 255, 0));
}

bool eatenApple(snake_t *snake) {
  return snake->tail.get(0) == apple;
}

int isEndGame() {
  int randValue = random(2);

  if (randValue == 0) {
    if (checkWinner(&snakes[0], &snakes[1]))
      return 0;
    if (checkWinner(&snakes[1], &snakes[0]))
      return 1;
  } else {
    if (checkWinner(&snakes[1], &snakes[0]))
      return 1;
    if (checkWinner(&snakes[0], &snakes[1]))
      return 0;
  }

  return -1;
}

// Check if snake_l head (looser) is in snake_w tail (winner)  
bool checkWinner(snake_t *snake_w, snake_t *snake_l) {
  for (int i = 0; i < snake_w->tail.size(); i++) {
    if (snake_l->tail.get(0) == snake_w->tail.get(i)){
      return true;
    }
  }

  return false;
}

void endGameScreen() {
  playEndSong();
  winnerScreen(snakes[winner].color);
  resetGame();
}

void playEndSong() {
  emitSound(G[2], 500); noTone(SPEAKER_PIN); delay(100);
  emitSound(G[2], 500); noTone(SPEAKER_PIN); delay(100);
  emitSound(G[2], 500); noTone(SPEAKER_PIN); delay(100);
  emitSound(DS[2], 500); noTone(SPEAKER_PIN); delay(1);
  emitSound(AS[2], 125); noTone(SPEAKER_PIN); delay(25);
  emitSound(G[2], 500); noTone(SPEAKER_PIN); delay(100);
  emitSound(DS[2], 500); noTone(SPEAKER_PIN); delay(1);
  emitSound(AS[2], 125); noTone(SPEAKER_PIN); delay(25);
  emitSound(G[2], 500); noTone(SPEAKER_PIN);
}

void emitSound(int frec, int t) {
  tone(SPEAKER_PIN, frec);      
  delay(t);                
}

void winnerScreen(uint32_t color) {
  winnerAnimation(color);
  turnOffLeds();

  for (int i = 0; i < 5; i++) {
    writeWin(color);
    delay(500);
    turnOffLeds();
    delay(500);
  }
}

// Color the full LED matrix the winner snake color
void winnerAnimation(uint32_t color) {
  for (int i = 0; i < 512; i++) {
    strip.setPixelColor(random(256), color);
    strip.show();
  }
}

void writeWin(uint32_t color) {
  for (int i = 0; i < 23; i++) {
    if ((winner % 2) == 0)
      strip.setPixelColor(RED_WIN[i], color);
    else
      strip.setPixelColor(BLUE_WIN[i], color);
  }

  strip.show();
}

void turnOffLeds() {
  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, strip.Color(0, 0, 0));   
  }

  strip.show();
}

void resetGame() {
  for (int i = 0; i < PLAYERS; i++) {
    snakes[i].tail.clear();
  }

  setupInitialValues();
}

Código de control de la música

#include <SimpleSDAudio.h>

const int SD_PIN = 10 // SD card cs pin

void setup() {
  SdPlay.setSDCSPin(SD_PIN); 

  if (!SdPlay.init(SSDA_MODE_FULLRATE | SSDA_MODE_MONO | SSDA_MODE_AUTOWORKER)) {
    while (1); 
  }
  
  if (!SdPlay.setFile("ms.wav")) { 
    while (1);
  }
}

void loop(void){
  SdPlay.play(); // Play music

  while (!SdPlay.isStopped())  {
    delay(100);
  }
}

Casos de uso

Hablaremos ahora sobre los diferentes casos de uso que tiene el juego:

  • Jugar partida: El jugador podrá conectar las dos placas a una fuente de alimentación, como pueden ser pilas de 9V, para empezar una partida y usar cualquiera de los dos joysticks disponibles para controlar a la serpiente roja o a la azul mediante movimientos horizontales y verticales. Al principio aparecerá una cuenta atrás de 3 segundos y, acto seguido, las serpientes empezarán a moverse. La trayectoria original de ambas serpientes está establecida de tal manera que no sea posible chocar una con la otra hasta que alguno de los jugadores realice un movimiento, para evitar así una muerte indeseada. También cabe mencionar que existen límites de pantalla, es decir, en caso de sobrepasar un borde de la pantalla, la serpiente aparecerá por el lado contrario. Además, a diferencia del juego original, las serpientes sí pueden pasar sobre su propio cuerpo sin morir.
  • Comer manzana: El jugador podrá moverse hasta el LED iluminado de color amarillo para comerse la manzana y que el cuerpo de su serpiente aumente en una unidad.
  • Incremento de la velocidad: Las serpientes empiezan moviéndose a una velocidad normal. Sin embargo, conforme va pasando el tiempo, estas van a empezar a moverse cada vez más rápido, hasta alcanzar la máxima velocidad.
  • Chocar con oponente: Si el jugador es lo suficientemente habilidoso, podrá conseguir que el oponente choque con su cuerpo y así ganar la partida, mostrando la animación de victoria con el color de la serpiente del ganador y  empezar después una nueva partida.
  • Reiniciar partida: Los jugadores podrán reiniciar en cualquier momento la partida pulsando el botón de reinicio de la propia placa de Arduino.

Problemas y soluciones encontradas

De cara a los problemas que nos hemos encontrado a lo largo del desarrollo del proyecto, citamos a continuación los más significativos y la forma de lidiar que hemos tenido con ellos:

  • Encontrar una matriz 16×16 LED: Al principio del proyecto nos fue sumamente complicado dar con una matriz que cumpliera todos los requisitos necesarios. Empezamos a buscar por grandes distribuidoras como pueden ser Amazon o AliExpress, llegando a desistir de este camino por superar el presupuesto o por el hecho de que el componente no llegaría a tiempo. Finalmente, conseguimos un buen componente a un precio asequible en el mercado de segunda mano online a través de un vendedor de Wallapop.
  • Cable de la matriz LED: A la hora de integrar de manera aislada la matriz LED a la placa Arduino, la matriz no dio ningún tipo de problema. Sin embargo, al integrar la pantalla con el resto de componentes y pegarla a la lámina de contrachapado, esta empezó a dar problemas debido a que los contactos de voltaje y tierra se ensuciaban e impedían el correcto uso de la placa. Esto nos provocó retrasos en el desarrollo e incluso nos llevó a pensar en la rotura de una de las placas Arduino Uno.
  • Ejecución en paralelo de la música: Una vez hecha la lógica del juego y probada de manera que se mostraba robusta, tratamos de integrar la música leída desde una tarjeta SD conectada a la placa Arduino mediante un módulo lector de SD. El problema llegaba en que la música tenía que reproducirse continuamente y sin interferir en la experiencia de juego. Al no existir la ejecución en paralelo en Arduino Uno, acabamos desarrollando finalmente dos códigos, uno encargado de la lógica de juego general y otro dedicado al propio módulo lector de tarjetas SD, cada uno de estos cargado a una placa Arduino, siendo este el principal motivo por el cual necesitamos de una placa Arduino adicional.
  • Uso de librerías: Al principio nos fue difícil recurrir a una implementación de la lógica de juego y del control de componentes sin el uso de librerías, siendo esta la razón por la que hemos importado una serie de librerías que nos ayudan a gestionar de forma más sencilla, rápida y eficiente el uso de los componentes en el propio código.

Mejoras a futuro del proyecto

Mencionaremos ahora las mejoras a incluir en un futuro que hemos pensado que mejorarían la experiencia general de nuestro proyecto y que finalmente por diversos motivos, entre los que se encuentra la falta de tiempo, no han sido implementadas:

  • Añadir más jugadores: Nuestro código está pensado para ser fácilmente escalable de tal forma que sería posible que más jugadores se unieran a la misma partida sin problemas. Bastaría simplemente con añadir el hardware necesario (un joystick por cada nuevo jugador), teniendo en cuenta el límite de entradas analógicas de Arduino, que en el caso de alcanzarse, sería necesario incluir un multiplexor para permitir añadir más entradas. Con respecto al software, solo haría falta incluir una nueva serpiente al inicializar el juego y modificar la función de condición de final de juego.
  • Interruptor: Podría ser interesante utilizar un interruptor que controle las pilas de ambas placas Arduino, dado que actualmente el juego se inicia nada más recibir voltaje.
  • Mejorar la estructura del artefacto: Si bien es verdad que la máquina es compacta, sería conveniente reforzar la estructura de contrachapado de la que está fabricada y los cables que la componen internamente para evitar cualquier comportamiento no deseado.
  • Mejorar conexiones: A pesar de que la gestión del cableado es correcta, convendría un refuerzo de estos para evitar que no se desconecte ninguno y el proyecto no funcione como debiera.
  • Mandos más cómodos: A pesar de que los mandos que ofrecemos en el modelo actual se prestan a ser cómodos y no desconcentrar de la propia partida, podrían haberse fabricado con un diseño más amigable al usuario.
  • Mejor sonido: Aunque la calidad de sonido actualmente es decente, convendría mejorarla de cara al futuro, debido a que, por limitaciones de la librería usada, el control de volumen transmitido por los altavoces es fijo y no se puede subir ni bajar. Además, la calidad de sonido queda condicionada por los 8 bits de la propia placa Arduino y el formato .WAV soportado por ambas librerías y la placa.
  • Microcontrolador más apropiado para ejecución de tareas en paralelo: Sería posible tratar de conseguir un microcontrolador más potente que reemplazase las dos placas Arduino actualmente utilizadas por el dispositivo, al poder este soportar operaciones ejecutadas en paralelo a través del propio hardware, dado que es tediosa e ineficiente la propia implementación de esto en Arduino (simulado por software). 
  • Añadir sonidos y mejorar la reproducción de los actuales: En relación con el punto anterior, al conseguir la ejecución de tareas en paralelo, se podría conseguir un mejor comportamiento del sonido y la música del juego, pudiendo controlar cuándo reproducir y parar la melodía principal del juego, además de añadir más sonidos.
  • Mando inalámbrico con teléfono móvil: De cara a futuras iteraciones del proyecto, sería posible utilizar un módulo de conexión inalámbrica a la placa Arduino que permitiera el uso de un teléfono móvil como mando para jugar a snake.io.

Vídeo explicativo del proyecto

Video desarrollo proyecto

Vídeo funcionando

Video ejemplo funcionamiento

Documentación adicional

También te podría gustar...

1 respuesta

  1. Luis Mendoza dice:

    El archivo del audio que usan lo podrían compartir por favor.

Deja una respuesta

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