Gameduino

Índice

  1. Introducción
  2. Hardware y montaje
  3. Desarrollo del proyecto
  4. Código
  5. Problemas encontrados
  6. Funcionamiento

Introducción

El proyecto que presentamos se trata de una consola hecha con Arduino. Esta es capaz de ejecutar varios juegos, nos encontramos el clásico Snake, y el juego del Dinosaurio.

Hardware y montaje

La parte hardware de nuestro sistema ha sido posible hacerla con los siguientes componentes:

COMPONENTEPRECIO
Protoboard – 400 puntos6,00€
Botones7,98€
Pantalla ILI934115,99€
Interruptor3,00€
Carcasa impresa en 3D45,00€
Arudino UNO R3Incluido en la caja
LEDIncluido en la caja
Pila 9vIncluido en la caja
CablesIncluido en la caja
TOTAL78,00€

Carcasa

Para la carcasa hemos optado por hacer un diseño con las medidas específicas necesarias. Este diseño se hizo para poder contratar los servicios de una persona, quien nos dió el modelo 3D de la carcasa listo para imprimir, esto nos costó 20,00€. Finalmente, el coste de la impresión 3D fueron 25,00€ suponiendo un total de 45,00€.

Montaje

Para mantener los componentes en su sitio, como la pantalla, botones, led o buzzer, se usó cola de pegar. En cuanto a los cables, estos se ordenaron y se juntaron con pequeñas bridas, soldándolos para mantener una conexión estable en todo momento y evitar desconexiones imprevistas. Por otra parte, para que los componentes dentro de la caja no se movieran una vez cerrada, se usaron pequeños trozos de esponja en estos huecos libres para mantener los componentes como el Arduino y la protoboard empotrados e inmóviles en su sitio.

El montaje se documenta con las siguientes imágenes.

Desarrollo del proyecto

Después de establecer la idea del proyecto, la parte inicial en la que nos enfocamos fue en el código, ya que este es de gran importancia para su funcionamiento. Usamos páginas de simuladores de circuitos tal como Tinkercad o Wokwi, con el objetivo de simplemente probar el código que creamos, olvidándonos en gran parte del hardware.

Una vez confirmamos el funcionamiento de nuestro código, el paso siguiente fue empezar con el diseño físico y la carcasa. Esto es debido a las limitaciones que nos presenta la caja en cuanto al espacio para los componentes. Determinamos un equilibrio en los tamaños de la carcasa para que esta tuviese suficiente espacio para componentes pero también fuese cómoda de sostener en las manos. Tras el diseño, conseguimos el modelo 3D de la caja y buscamos tiendas de impresión 3D comparando presupuestos, hasta que encontramos una adecuada, obtuvimos la carcasa física y pudimos empezar a instalar los componentes.

Código

Conexiones de pines

CONECTORPIN
Botón UP2
Botón DOWN3
Botón LEFT4
Botón RIGHT5
LED1
BUZZER7
CONECTOR DE PANTALLAPIN
Vcc5V
GNDGND
CS10
RESET8
DC9
Sdi MOSI11
SCK13
LED5V
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>

// TFT LCD pins 
#define TFT_RST 8
#define TFT_DC 9
#define TFT_CS 10

// Input/Output pins 
int BUZZER = 0;
#define LED 1 
#define BTN_UP 2
#define BTN_DWN 3
#define BTN_LFT 4
#define BTN_RGT 5

#define LASOS 465.000
#define LA 440.000
#define SI 493.883
#define DO2 523.251


// Menu variables
int currentSelection = 0;
const int menuLength = 4; 
bool isVolumeOn = false; 

String menuItems[menuLength] = {
  "Dino Game",
  "Snake Game",
  "Opciones",
  "Acerca de"
};

enum AppState {
  MENU,
  DINO_GAME,
  SNAKE_GAME
};

AppState currentState = MENU;

// Screen instantiation
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

// Dino & obstacles sprite arrays
byte STILL_DINO_PART_1[8] = {B00000, B00000, B00010, B00010, B00011, B00011, B00001, B00001};
byte STILL_DINO_PART_2[8] = {B00111, B00111, B00111, B00100, B11100, B11100, B11000, B01000};
byte RIGHT_FOOT_DINO_PART_1[8] = {B00000, B00000, B00010, B00010, B00011, B00011, B00001, B00001};
byte RIGHT_FOOT_DINO_PART_2[8] = {B00111, B00111, B00111, B00100, B11100, B11100, B11000, B00000};
byte LEFT_FOOT_DINO_PART_1[8] = {B00000, B00000, B00010, B00010, B00011, B00011, B00001, B00000};
byte LEFT_FOOT_DINO_PART_2[8] = {B00111, B00111, B00111, B00100, B11100, B11100, B11000, B01000};
byte TWO_CACTUS_PART_1[8] = {B00000, B00100, B00100, B10100, B10100, B11100, B00100, B00100};
byte TWO_CACTUS_PART_2[8] = {B00100, B00101, B00101, B10101, B11111, B00100, B00100, B00100};
byte BIRD_FEATHERS_PART1[8] = {B00001, B00001, B00001, B00001, B01001, B11111, B00000, B00000};
byte BIRD_FEATHERS_PART2[8] = {B00000, B10000, B11000, B11100, B11110, B11111, B00000, B00000};

byte NUMBERS[10][8] = {
  {B01110, B10001, B10011, B10101, B11001, B10001, B01110, B00000}, // 0
  {B00100, B01100, B00100, B00100, B00100, B00100, B01110, B00000}, // 1
  {B01110, B10001, B00001, B00010, B00100, B01000, B11111, B00000}, // 2
  {B11111, B00010, B00100, B00010, B00001, B10001, B01110, B00000}, // 3
  {B00010, B00110, B01010, B10010, B11111, B00010, B00010, B00000}, // 4
  {B11111, B10000, B11110, B00001, B00001, B10001, B01110, B00000}, // 5
  {B00110, B01000, B10000, B11110, B10001, B10001, B01110, B00000}, // 6
  {B11111, B00001, B00010, B00100, B01000, B01000, B01000, B00000}, // 7
  {B01110, B10001, B10001, B01110, B10001, B10001, B01110, B00000}, // 8
  {B01110, B10001, B10001, B01111, B00001, B00010, B01100, B00000}  // 9
};

float gameOver[] = {DO2, SI, LASOS};

// Dino's game variables
int dinoColumn1 = 30, dinoColumn2 = 50, dinoRow = 90;
int flag = 1;
int points = 0, point2 = 0, randomNumber = 0;
int f = 130;
int a = 0, b = 30, c = 40, d = 0, e = 0;
int acceleration = 10;

unsigned long clock = 0, clock2 = 0, clock3 = 0, clock4 = 0;
int period = 100, period2 = 100, period3 = 100, period4 = 800;

int currentSignal = 0, oldSignal = 0;

unsigned long jumpStartTime = 0;
unsigned long JUMP_DURATION = 900;
bool isJumping = false;
bool buttonReleased = true;

// Signal to indicate if any game is active
bool isGameActive = true;

// Snake's game variables
#define SNAKE_MAX_LENGTH 50
int snakeX[SNAKE_MAX_LENGTH];
int snakeY[SNAKE_MAX_LENGTH];
int snakeLength = 5;
int foodX, foodY;
int direction = 0; // 0: Up, 1: Down, 2: Left, 3: Right
int gameSpeed = 200;
unsigned long lastSnakeUpdate = 0;
bool bordersDrawn = false;

void setup() {
  pinMode(BTN_UP, INPUT_PULLUP);
  pinMode(BTN_DWN, INPUT_PULLUP);
  pinMode(BTN_LFT, INPUT_PULLUP);
  pinMode(BTN_RGT, INPUT_PULLUP);
  pinMode(BUZZER, OUTPUT);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, HIGH);

  // Start tft screen
  tft.begin();
  tft.setRotation(3);
  drawMenu();
}

void drawMenu() {
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextSize(2);
  for (int i = 0; i < menuLength; i++) {
    if (i == currentSelection) {
      tft.setCursor(80, 60 + i * 40);
      tft.print(">");
    } else {
      tft.setCursor(80, 60 + i * 40);
      tft.print(" ");
    }

    tft.setCursor(110, 60 + i * 40);
    tft.print(menuItems[i]);
  }
}

void executeSelection() {
  if (currentSelection == 0) { 
    currentState = DINO_GAME;
    tft.fillScreen(ILI9341_BLACK);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE);
    tft.setCursor(104, 10);
    tft.print("Dino Game");

    // Restart variables
    isGameActive = true;
    points = 0;
    point2 = 0;
    period2 = 100;
    
  } else if (currentSelection == 1) {
    currentState = SNAKE_GAME;
    tft.fillScreen(ILI9341_BLACK);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE);
    tft.setCursor(104, 10);
    tft.print("Snake Game");
    isGameActive = true;

    initSnakeGame();
    
  } else if (currentSelection == 2) { 
    showOptionsMenu();
  } else if (currentSelection == 3) {
    printAbout();
  }
}

void printAbout() {
  tft.fillScreen(ILI9341_GREEN);
  tft.setCursor(15, 100);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(2);
  tft.print("Hecho por el grupo 13 ;)");
  delay(1000);
  drawMenu();
}

void handleMenu() {
  if (digitalRead(BTN_UP) == LOW) {
    currentSelection--;
    if (currentSelection < 0) currentSelection = menuLength - 1;
    drawMenu();
    tone(BUZZER, 800, 300);
    delay(200);
  }

  if (digitalRead(BTN_DWN) == LOW) {
    currentSelection++;
    if (currentSelection >= menuLength) currentSelection = 0;
    drawMenu();
    tone(BUZZER, 800, 300);
    delay(200);
  }

  if (digitalRead(BTN_RGT) == LOW) {
    tone(BUZZER, 1000, 300);
    delay(200);
    executeSelection();
  }

}

void initSnakeGame() {
  points = 0;
  snakeLength = 5;
  direction = 3;
  gameSpeed = 200;
  
  for (int i = 0; i < snakeLength; i++) {
    snakeX[i] = tft.width()/2 - i*10;
    snakeY[i] = tft.height()/2;
  }
  if (!bordersDrawn) {
    tft.fillScreen(ILI9341_BLACK);
    drawBorder();
    bordersDrawn = true;
  }
  
  spawnFood();
}

void drawBorder() {
  tft.drawRect(9, 9, tft.width() - 18, tft.height() - 18, ILI9341_WHITE);
}

void spawnFood() {
  bool valid = false;
  while (!valid) {
    valid = true;
    foodX = random(1, (tft.width() - 10) / 10) * 10;
    foodY = random(1, (tft.height() - 10) / 10) * 10;
    for (int i = 1; i < snakeLength; i++) {
      if (foodX == snakeX[i] && foodY == snakeY[i]) {
        valid = false;
      }
    }  
  }
}

void moveSnake() {
  for (int i = snakeLength - 1; i > 0; i--) {
    snakeX[i] = snakeX[i - 1];
    snakeY[i] = snakeY[i - 1];
  }

  if (direction == 0) snakeY[0] -= 10; // Up
  if (direction == 1) snakeY[0] += 10; // Down
  if (direction == 2) snakeX[0] -= 10; // Left
  if (direction == 3) snakeX[0] += 10; // Right
}

bool checkCollision() {
  if (snakeX[0] < 10 || snakeX[0] >= tft.width() - 10 || snakeY[0] < 10 || snakeY[0] >= tft.height() - 10) {
    return true;
  }

  for (int i = 1; i < snakeLength; i++) {
    if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
      return true;
    }
  }
  return false;
}

void gameOverScreen() {
  tft.fillScreen(ILI9341_BLACK);
  delay(100);
  tft.setTextSize(4);
  tft.setTextColor(ILI9341_RED);
  tft.setCursor(50, 50);
  tft.print("GAME OVER");
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(50, 90);
  tft.print("Score: ");
  tft.print(points);
  soundGameOver();
  tft.setTextSize(1);
  tft.setCursor(55, 147);
  tft.print(" Pulsa ");
  tft.setTextColor(ILI9341_GREEN);
  tft.print("GREEN ");
  tft.setTextColor(ILI9341_WHITE);
  tft.print("para volver a jugar");
  tft.setCursor(58, 177);
  tft.print("Pulsa ");
  tft.setTextColor(ILI9341_YELLOW);
  tft.print("YELLOW ");
  tft.setTextColor(ILI9341_WHITE);
  tft.print("para volver al menu");
  isGameActive = false;
}

void drawSprite(byte part1[8], byte part2[8], int x, int y, uint16_t color, int scale = 1) {
  for (int row = 0; row < 8; row++) {
    byte b1 = part1[row];
    byte b2 = part2[row];

    for (int col = 0; col < 5; col++) 
      if (b1 & (1 << (4 - col))) 
        for (int dx = 0; dx < scale; dx++) 
          for (int dy = 0; dy < scale; dy++) 
            tft.drawPixel(x + col * scale + dx, y + row * scale + dy, color);

    for (int col = 0; col < 5; col++) 
      if (b2 & (1 << (4 - col))) 
        for (int dx = 0; dx < scale; dx++) 
          for (int dy = 0; dy < scale; dy++) 
            tft.drawPixel(x + (col + 5) * scale + dx, y + row * scale + dy, color);
  }
}

void drawNumber(byte numero, int x, int y, uint16_t color) {
  for (int row = 0; row < 8; row++) {
    for (int col = 0; col < 5; col++) {
      if (bitRead(NUMBERS[numero][row], 4 - col)) {
        tft.fillRect(x + col * 2, y + row * 2, 2, 2, color);
      }
    }
  }
}

void showOptionsMenu() {
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(15, 40);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(2);

  tft.print("Volumen: ");
  if (isVolumeOn) {
    tft.setTextColor(ILI9341_GREEN);
  } else {
    tft.setTextColor(ILI9341_RED);
  }
  tft.println(isVolumeOn ? "Encendido" : "Apagado");

  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(15, 80);
  tft.print("Pulsa ");
  tft.setTextColor(ILI9341_GREEN);
  tft.print("GREEN ");
  tft.setTextColor(ILI9341_WHITE);
  tft.print("para ");
  if (isVolumeOn) {
    tft.print("apagar");
  } else {
    tft.print("encender");
  }

  tft.setCursor(15, 120);
  tft.print("Pulsa ");
  tft.setTextColor(ILI9341_RED);
  tft.print("RED ");
  tft.setTextColor(ILI9341_WHITE);
  tft.print("para volver al");

  tft.setCursor(15, 160);
  tft.print("menu");

  while (true) {
    if (digitalRead(BTN_DWN) == LOW){
      if (isVolumeOn) {
        BUZZER = 0;
        pinMode(BUZZER, OUTPUT);
      } else {
        BUZZER = 7;
        pinMode(BUZZER, OUTPUT);
      }
      isVolumeOn = !isVolumeOn;

      tft.fillScreen(ILI9341_BLACK);
      tft.setCursor(50, 100);
      tft.print("Volumen ");
      if (isVolumeOn) {
        tft.setTextColor(ILI9341_GREEN);
        tone(BUZZER, LA, 250);
      } else {
        tft.setTextColor(ILI9341_RED);
      }
      tft.print(isVolumeOn ? "Encendido" : "Apagado");
      tft.setTextColor(ILI9341_WHITE);
      
      delay(1000);
    
      drawMenu();
      break;
    } else if (digitalRead(BTN_RGT) == LOW) {
      drawMenu();
      break;
    }
  }
}

void soundGameOver(){
  for(int i = 0; i <=  2; i++){
    tone(BUZZER, round(gameOver[i]));
    delay(500);
  }
  tone(BUZZER, LA);
  delay(1000);
  noTone(BUZZER);
}


void playDinoGame() {
  
  int cactusRow = 0, cactusColumn = 240;
  int birdColumn = 240, birdRow = 70;

  while (isGameActive) {
    tft.drawLine(0, 108, tft.width(), 108, ILI9341_WHITE);
    int dinoMargin = 2;
    // Coordenadas del dino
    int dinoX = dinoColumn1;
    int dinoY = (d == 0) ? dinoRow : 70;  

    int dinoW = 20; 
    int dinoH = 16;

    // Coordenadas del obstáculo
    int obsX = cactusColumn;
    int obsY = cactusRow;
    int obsW = 20;
    int obsH = 16;

    int birdX = birdColumn;
    int birdY = birdRow;
    int birdW = 20;
    int birdH = 16;

    bool groundCollision = (dinoX + dinoMargin) < (obsX + obsW) &&
                  (dinoX + dinoW - dinoMargin) > obsX &&
                  (dinoY + dinoMargin) < (obsY + obsH) &&
                  (dinoY + dinoH - dinoMargin) > obsY;

    bool midAirCollision = (dinoX + dinoMargin) < (birdX + birdW) &&
                  (dinoX + dinoW - dinoMargin) > birdX &&
                  (dinoY + dinoMargin) < (birdY + birdH) &&
                  (dinoY + dinoH - dinoMargin) > birdY;

    // Dino animation (feet movement)
    if (millis() > clock + period) {
      clock = millis();
      if (flag == 1) {
        flag = 2;
      }
      else if (flag == 2) {
        flag = 1;
      }
    }

    // Bird & cactus movement
    if (millis() > clock2 + period2) {
      clock2 = millis();
      cactusColumn -= 5;

      if (cactusColumn < -20) {
        cactusColumn = 240;  // Pantalla completa
        period2 = max(20, period2 - acceleration);  // acelerar sin que baje de 20 ms
        JUMP_DURATION = max(300, JUMP_DURATION - 50);
        randomNumber = random(0, 3);  // 0 = bird, 1-2 = ramas
      }
      f = cactusColumn + 5;
      if (randomNumber == 1 || randomNumber == 2)
        tft.fillRect(f, cactusRow, 20, 20, ILI9341_BLACK);  
      else  
        tft.fillRect(f, birdRow, 20, 10, ILI9341_BLACK);
      a = 1;
    }

    // Draw dino
    if (d == 0) {
      if ((dinoX) >= (birdX + birdW) &&
                  (dinoX + dinoW) <= birdX) {
          tft.fillRect(dinoColumn1, 70, 20, 16, ILI9341_BLACK);
      }
      
      if (flag == 1) {
        tft.fillRect(dinoColumn1 + 10, 90, 20, 16, ILI9341_BLACK); 
        drawSprite(RIGHT_FOOT_DINO_PART_1, RIGHT_FOOT_DINO_PART_2, 30, 90, ILI9341_WHITE, 2);
      }
      if (flag == 2) {
        tft.fillRect(dinoColumn1 - 10, 90, 20, 16, ILI9341_BLACK); 
        drawSprite(LEFT_FOOT_DINO_PART_1, LEFT_FOOT_DINO_PART_2, 30, 90, ILI9341_WHITE, 2);
      }
    }

    // Draw obstacle
    if (a == 1) {
       // Cactus (lower obstacle/hurdle)
      if (randomNumber == 1 || randomNumber == 2) {
        cactusRow = 90;
        drawSprite(TWO_CACTUS_PART_1, TWO_CACTUS_PART_2, cactusColumn, cactusRow, ILI9341_WHITE, 2);
      } else { // Bird (upper obstacle/hurdle)
        tft.fillRect(birdColumn, birdRow, 20, 16, ILI9341_BLACK);
        tft.fillRect(birdColumn + 10, birdRow, 20, 16, ILI9341_BLACK);
        birdColumn = cactusColumn - 10;
        cactusRow = 0;
        drawSprite(BIRD_FEATHERS_PART1, BIRD_FEATHERS_PART2, birdColumn, birdRow, ILI9341_WHITE, 2);
      }
      a = 0;
    }

    if (midAirCollision) {
      tft.fillScreen(ILI9341_BLACK);
      int note[] = {200, 150};
      for (int i = 0; i < 2; i++) {
        tone(BUZZER, note[i], 250);
        delay(200);
      }
      points = point2 * 100 + points;
      gameOverScreen();
      break;
    }

    if (groundCollision) {
      int note[] = {200, 150};
      for (int i = 0; i < 2; i++) {
        tone(BUZZER, note[i], 250);
        delay(200);
      }
      points = point2 * 100 + points;
      gameOverScreen();
      break;
    }

    // Dino jump
    if (digitalRead(BTN_DWN) == LOW) {
      if (!isJumping && buttonReleased && d == 0) {
        isJumping = true;
        buttonReleased = false;
        jumpStartTime = millis();
        d = 1;
        if ((dinoX) >= (obsX + obsW) &&
                  (dinoX + dinoW) <= obsX) {
          tft.fillRect(dinoColumn1, 90, 20, 16, ILI9341_BLACK);
        }
        drawSprite(STILL_DINO_PART_1, STILL_DINO_PART_2, dinoColumn1, 70, ILI9341_WHITE, 2);
        int note[] = {600};
        for (int i = 0; i < 1; i++) {
          tone(BUZZER, note[i], 150);
          delay(20);
        }
      }
    } else {
      buttonReleased = true; 
    }

    if (isJumping && (millis() - jumpStartTime >= JUMP_DURATION)) {
      isJumping = false;
      d = 0;
      
      // Clear jump position and redraw ground sprite
      tft.fillRect(dinoColumn1, 70, 20, 16, ILI9341_BLACK);
      if (flag == 1) {
        drawSprite(RIGHT_FOOT_DINO_PART_1, RIGHT_FOOT_DINO_PART_2, 30, 90, ILI9341_WHITE, 2);
      } else {
        drawSprite(LEFT_FOOT_DINO_PART_1, LEFT_FOOT_DINO_PART_2, 30, 90, ILI9341_WHITE, 2);
      }
    }
       
    // Scoreboard
    if (millis() > clock3 + period3) {
      clock3 = millis();

      tft.fillRect(270, 0, 40, 20, ILI9341_BLACK);

      drawNumber(points % 10, 290, 0, ILI9341_YELLOW);
      drawNumber((points / 10) % 10, 280, 0, ILI9341_YELLOW);

      points++;
      if (points == 100) {
        points = 0;
        point2++;

        if (point2 == 100) point2 = 0;
      }

      if (point2 > 0) {
        drawNumber(point2 % 10, 270, 0, ILI9341_YELLOW); 
      }
    }

  
    currentSignal = digitalRead(BTN_DWN);

    if (currentSignal != oldSignal) {
      tft.fillRect(dinoColumn1, 90, 20, 16, ILI9341_BLACK);
    }

    oldSignal = currentSignal;
  }
  
  // Restart game
  if (!isGameActive && digitalRead(BTN_DWN) == LOW) {
    tft.fillScreen(ILI9341_BLACK);
    delay(300); 
    JUMP_DURATION = 900;
    isGameActive = true;
    points = 0;
    point2 = 0;
    cactusColumn = 240;
    period2 = 100;
    tft.fillScreen(ILI9341_BLACK);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE);
    tft.setCursor(104, 10);
    tft.print("Dino Game");
  }

  if (!isGameActive && digitalRead(BTN_UP) == LOW) {
      tft.fillScreen(ILI9341_BLACK);
      delay(300);
      currentState = MENU;
      drawMenu();
  }
}

void playSnakeGame() {
  
  if (isGameActive){
    static int lastDirection = direction;
  
    if (digitalRead(BTN_UP) == LOW && lastDirection != 1) direction = 0;
    if (digitalRead(BTN_DWN) == LOW && lastDirection != 0) direction = 1;
    if (digitalRead(BTN_LFT) == LOW && lastDirection != 3) direction = 2;
    if (digitalRead(BTN_RGT) == LOW && lastDirection != 2) direction = 3;
  
    
    if (millis() - lastSnakeUpdate > gameSpeed) {
      lastSnakeUpdate = millis();
      lastDirection = direction;

      
      tft.fillRect(snakeX[snakeLength-1], snakeY[snakeLength-1], 10, 10, ILI9341_BLACK);
      
      moveSnake();
      
      // Check food collision
      if (snakeX[0] == foodX && snakeY[0] == foodY) {
        snakeLength++;
        points++;
        tone(BUZZER, 900, 250);
        if (snakeLength >= SNAKE_MAX_LENGTH) snakeLength = SNAKE_MAX_LENGTH;
        spawnFood();
      } else {
        // Redraw food (in case it was cleared)
        tft.fillRect(foodX, foodY, 10, 10, ILI9341_RED);
      }

      tft.fillRect(snakeX[0], snakeY[0], 10, 10, ILI9341_GREEN);
      if (checkCollision()) {
        int note[] = {200, 150};
        for (int i = 0; i < 2; i++) {
          tone(BUZZER, note[i], 250);
          delay(200);
        }
        isGameActive = false;
        bordersDrawn = false;
        gameOverScreen();
        return;
      }
    }
  }

  if (!isGameActive && digitalRead(BTN_DWN) == LOW) {
    delay(300); 
    isGameActive = true;
    initSnakeGame();
  }

  if (!isGameActive && digitalRead(BTN_UP) == LOW) {
      delay(300); 
      currentState = MENU;
      drawMenu();
  }
  
}

// Main function of console
void loop() {
  if (currentState == MENU) {
    handleMenu();
  } else if (currentState == DINO_GAME) {
    playDinoGame();
  } else if (currentState == SNAKE_GAME) {
    playSnakeGame();
  }
}

Problemas encontrados

Diseño

El diseño supuso un gran problema debido al objetivo del proyecto. Un punto clave en el desarrollo de la consola era poner énfasis en el carácter portátil de esta. Esto causó que tuviésemos que tener un compromiso tanto con el tamaño para poder hacer el montaje cómodamente, pero también con la comodidad en las manos de la consola.

Otro problema fue la impresión 3D. La impresión 3D tuvo unas limitaciones que desconocíamos inicialmente, relacionadas sobre todo con la impresión de piezas pequeñas. Antes de llegar al diseño final de la consola, hubo muchos otros diseños que usaban piezas más complejas, pero estas hacían que la impresión se saliese del presupuesto del que disponíamos.

Montaje

En el montaje, un gran problema fue trabajar en el espacio tan reducido del que disponíamos. Aunque esto es algo que derivaba del diseño, no se podía saber con certeza de cuanto espacio dispondríamos hasta tener la carcasa impresa físicamente. Esto hizo que el montaje fuese más complicado de lo que esperábamos inicialmente, y por lo tanto, consumía más tiempo.

El montaje de la pantalla supuso otro problema. Aunque seguimos instrucciones en internet sobre el montaje de la pantalla, durante un largo periodo de tiempo, no conseguíamos que esta funcionase correctamente. Mucha información que encontrábamos era de hace mucho tiempo o trataba con diferentes placas de Arduino. La búsqueda de una solución para esto nos consumía mucho tiempo, ya que no podíamos saber realmente si la pantalla funcionaba, y tampoco podíamos comprobar nuestro código con los componentes reales sin hacer uso de plataformas como Wokwi o TinkerCad.

Por último, de nuevo el aspecto portable de la consola nos causó complejidad, esta vez en cuanto a la rigidez de los componentes dentro del sistema. Al tratarse de una consola portátil, teníamos en cuenta la posibilidad de que sufriese movimientos bruscos, los cuales podrían causar desconexiones de los componentes si estos estaban frágilmente instalados. Para asegurarnos de que esto no causaría un problema, usamos herramientas como cola de pegar, soldador, bridas…

Funcionamiento

Al encender la consola, lo primero que podemos ver es el menú. En este se pueden elegir varias opciones: Jugar al juego del dinosaurio, jugar al juego del Snake, Opciones, y ver los créditos.

Al presionar uno de los dos juegos, este se ejecutará y podrá jugarse todo lo que se quiera, teniendo la opción de volver a jugar o volver al menú en la pantalla de game over de estos juegos.

En la pantalla de opciones, se nos da la posibilidad de apagar o encender el volumen del buzzer, de forma que este suene, o no, en dichos juegos y en el menú al mover el cursos y seleccionar una opción.

Finalmente, en la opción de “Acerca de”, podemos ver los créditos de los creadores de la consola, en este caso, el grupo 13.

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 *