Cuatro en raya

Proyecto DSE

Autores: Sergio Zarco Mármol, Cristian Yánez Cervino y Eva Ruiz Aguado.

Introducción

El proyecto que vamos a presentar a continuación consiste en una consola portátil con la que jugar al cuatro en raya. Entre sus características cuenta con un LDR, el cual cambia el color de la pantalla en función de la luz ambiente tanto ( lo que llamamos el modo oscuro o el modo claro del dispositivo), un led RGB que indica el color del jugador al cual le toca jugar (Jugador rojo o azul) y un buzzer que se encarga de reproducir sonido cada vez que se suelta una pieza, se gana la partida, toca un botón o se elige un jugador para comenzar, El sistema está empotrado y usa una batería que permite su fácil movilidad y cómodo uso.

Piezas de Hardware y Costes

ComponenteDetallesPrecio por unidadUnidadesTotal
EELEGOO UNO R319,99€119,99€
Protoboard400 puntos3€13€
Cableado1 metro de cable
21 cables macho-hembra
1€/m
5€/pack de 40
1m
1 pack
6€
Zumbador0,75€10,75€
LED RGB0,56€10,56€
LDR0,67€10,67€
Interruptor de botón0,10€30,30€
Pantalla ILI9341240*320 5V/3.3V15,99€115,99€
CarcasaHecha a medida e impresa en 3D25€125€
Resistencias6 x 1KΩ, 100Ω,
5 x 5KΩ, 4 x 10KΩ
0,02€160,32€
Pila9V3,63€13,63€
Total76,21€

Modelo Carcasa para imprimir en 3D

Implementación del hardware

  • Zumbador: conectamos una de las patas a tierra y la otra pata al controlador del Arduino.
  • LED RGB: conectamos el cátodo a tierra y el pin rojo y azul al controlador con una resistencia de 330Ω. No conectamos el pin verde porque no lo vamos a utilizar y así nos ahorramos un puerto. 
  • Botones: cada botón está conectado por una pin a negativo con una resistencia de 1KΩ, ese mismo pin se conecta a un puerto de control del Arduino con un divisor de tensión y el otro pin estará conectado a positivo.
  • LDR: conectamos uno de los pines a negativo con una resistencia de 1KΩ,el mismo pin se conectará también al controlador con un divisor de tensión y por último conectamos el otro pin a positivo.
  • Pantalla: conectamos los 6 pines de la pantalla con una resistencia de 5KΩ a los controladores del Arduino, luego el pin LED de la pantalla se conecta con una resistencia de 100 Ω a positivo. Por último, conectamos los pines de tierra y positivo de la pantalla.

Conexiones del arduino

Nombre del pinNumero de pin en Arduino
LDR1 (pin analogico)
BOTON_ACCION2
BOTON_DER3
BOTON_IZQ4
BLUE(led)5
RED(led)6
BUZZER7
TFT_RESET8
TFT_DC9
TFT_CS10
TFT_MOSI11
TFT_MISO12
TFT_SCK13

Diagrama de conexiones del proyecto

Pantallas, Diseño y funcionamiento

Videos del sistema

Diseño general del sistema empotrado

Consiste en una pequeña carcasa cómoda y funcional con los botones, LDR, led y pantalla puestos a medida

Tapa de carcasa

La tapa de la carcasa se puede abrir y se puede sacar a conveniencia, además los cables están soldados para mantener el sistema sin desconexiones

Circuito interno empotrado

El sistema está lo más empotrado posible permitiendo cerrarse y usarse cómodamente de manera portátil

Menú

el menú del juego puede estar tanto en modo oscuro como en modo claro dependiendo de la luz ambiente en el momento, la pantalla consta de un botón para jugar y una flecha que parpadea y que puedes accionar.

Elegir Turno

Para elegir turno se hace un pequeño preámbulo cambiando la luz del led y la imagen de pantalla a azul y rojo hasta que se escoge al jugador sonando en el proceso una música

Pantalla del Juego

El juego empezará en modo claro o en modo oscuro, se podrán soltar fichas y moverse por el tablero con libertad

Victorias

La victorias pueden ser horizontales, verticales o diagonales

Pantalla del Final

Indicará que persona ganó o si se empató

Implementación del software

El sistema se ha diseñado como una máquina de estados, que va cambiando de estado según van ocurriendo diferentes eventos como la pulsación de botones o condiciones que se cumplen.

Dichos estados se han representado mediante funciones, de forma que cada función implementa la lógica de un estado, y la lógica del cambio de estado al siguiente. Los estado son:

  • Menú principal
  • Eligiendo turno inicial
  • En partida
  • Comprobando victoria
  • Mostrando ganador

Además, se han utilizado otras funciones auxiliares que mejoran la semántica y facilitan la lectura del código.

Código empleado

Para poder utilizar la pantalla, se han incluido las siguientes librerías, que permiten utilizar la pantalla de una manera más cómoda y fácil:

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>

El código comienza con una sección de constantes, que se utilizan para establecer valores de pines, además de otros valores utilizados recurrentemente, que facilitan la lectura y el mantenimiento del código:

// Valores notas
#define NOTE_A3 220
#define NOTE_D4 294
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_G5 784
#define NOTE_G2 98
#define NOTE_A2 110
#define NOTE_C4 262
#define NOTE_E4 330
#define NOTE_G4 392
#define REST 0

// Pines pantalla
#define TFT_RESET 8
#define TFT_DC 9
#define TFT_CS 10
#define TFT_MOSI 11
#define TFT_MISO 12
#define TFT_SCK 13

// Otros pines
#define LDR 1
#define BOTON_ACCION 2
#define BOTON_DER 3
#define BOTON_IZQ 4
#define BLUE 5
#define RED 6
#define BUZZER 7

// Medidas del tablero
#define FILAS 6
#define COLUMNAS 7

// Constantes del tablero
#define X 1
#define O 2
#define E 3

// Delay para la pulsación de botones IZQ y DER
#define DELAY 350

// Color morado del empate y el error
#define ILI9341_PURPLE 0x780F

// sonidos
#define SONIDOBOTON 392
#define SONIDOERROR 110

Variables globales

Se han utilizado algunas variables globales, que deben ser persistentes entre diferentes iteraciones de loop

int colorFondo;                // Color del fondo

// Referencia a la pantalla
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RESET, TFT_MISO);          
int ganador;                   // Ganador de la partida
int contFich;                  // Fichas colocadas en el tablero en una partida
int fichasColumna[COLUMNAS];   // Fichas colocadas en cada columna
int tablero[FILAS][COLUMNAS];  // Matriz para el tablero

setup

La hemos utilizado para establecer ciertos pines como entrada y salida, además de para inicializar la pantalla.

void setup() {
  // Establecer modo de los pines
  pinMode(BOTON_ACCION, INPUT);
  pinMode(BOTON_IZQ, INPUT);
  pinMode(BOTON_DER, INPUT);
  pinMode(RED, OUTPUT);
  pinMode(BLUE, OUTPUT);
  pinMode(BUZZER, OUTPUT);

  //Inicializar la pantalla
  tft.begin();
  tft.setRotation(3);
  tft.setTextSize(8);
  delay(1000);
}

loop

La función loop inicializa las variables del tablero para que comience la partida vacío. Después, llama a las funciones pintarMenu, jugarPartida y mostrarGanador, que son los principales estados del sistema

void loop() {
  // Inicializar fichas por columna a 0
  for (int i = 0; i < COLUMNAS; i++) {
    fichasColumna[i] = 0;
  }

  // Inicializar tablero vacío
  for (int i = 0; i < FILAS; i++) {
    for (int j = 0; j < COLUMNAS; j++) {
      tablero[i][j] = 0;
      fichasColumna[j] = 0;
    }
  }

  pintarMenu();
  jugarPartida();
  mostrarGanador();
}

pintarMenu

Esta función se encarga de pintar el menú en la pantalla. Después, pinta una flecha que parpadea pintándola en negro y blanco, y comprueba si se pulsa el botón para jugar la partida, en cuyo caso finaliza hace sonar una nota en el buzzer

void pintarMenu() {


  //nos aseguramos de que cada vez que se pinte el menú esté apagado el led

  digitalWrite(RED, LOW);
  digitalWrite(RED, LOW);

  //Se usa el valor que genera el LDR para decidir el color del texto y fondos

  int val = analogRead(LDR);

  if (val < 350) {
    colorFondo = ILI9341_BLACK;
    tft.fillScreen(ILI9341_BLACK);
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
    tft.setCursor(25, 75);
    tft.setTextSize(5);
    tft.print("4 en raya");

    tft.setCursor(10, 205);
    tft.setTextSize(3);
    tft.fillRoundRect(103, 145, 120, 50, 10, ILI9341_WHITE);
    tft.setCursor(115, 155);
    tft.setTextColor(ILI9341_BLACK, ILI9341_WHITE);
    tft.print("Jugar!");

  } else {

    colorFondo = ILI9341_WHITE;

    tft.fillScreen(ILI9341_WHITE);
    tft.setTextColor(ILI9341_BLACK, ILI9341_WHITE);
    tft.setCursor(25, 75);
    tft.setTextSize(5);
    tft.print("4 en raya");

    tft.setCursor(10, 205);
    tft.setTextSize(3);
    tft.fillRoundRect(103, 145, 120, 50, 10, ILI9341_BLACK);
    tft.setCursor(115, 155);
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
    tft.print("Jugar!");
  }

  //Boton de jugar partida con una flecha que parpadea

  long currentMilis = millis();
  long time = currentMilis;
  int color = 1;
  tft.fillTriangle(35, 147, 70, 170, 35, 192, ILI9341_BLACK);

  while (digitalRead(BOTON_ACCION) == LOW) {
    currentMilis = millis();
    if (currentMilis - time > 850) {
      if (color == 0) {
        tft.fillTriangle(35, 147, 70, 170, 35, 192, ILI9341_BLACK);
        color = 1;
        time = currentMilis;
      } else {
        tft.fillTriangle(35, 147, 70, 170, 35, 192, ILI9341_WHITE);
        color = 0;
        time = currentMilis;
      }
    }
  }

  //sonido al presionar el botón

  tone(BUZZER, SONIDOBOTON, 200);
  delay(200);
  noTone(BUZZER);
}

jugarPartida

Esta función implementa toda la lógica necesaria para jugar una partida. Para ello, elegirá un turno inicial aleatorio mediante la función elegirTurnoInicial(), pintará el tablero en la pantalla, y a partir de ahí, irá alternando turnos hasta que alguien gane o se llene el tablero.

Cada turno consistirá en dejar que el jugador elija la columna en la que quiere colocar su ficha, comprobando mediante sentencias if si se han pulsado los botones laterales.

Esto sucederá hasta que se pulse el botón central, momento en el que se colocará la ficha en la columna elegida, colocándose lo más abajo posible, implementado mediante el array fichasColumna, que representa las fichas colocadas en cada columa.

Por último, antes de cambiar de turno, llama a la función comprobarVictoria() para ver si ha ganado alguien, y comprobará si se ha llenado el tablero, para informar del empate.


void jugarPartida() {
  int turno = elegirTurnoInicial();
  pintarTablero();
  int victoria = 0;
  contFich = 0;
  int columna = 0;

  while (victoria == 0) {
    uint16_t color;
    (turno == 1) ? color = ILI9341_RED : color = ILI9341_BLUE;

    // Comprobar botones cambio de columna
    long time = millis();
    long currentMilis = time;
    int columnaActual = columna;

    int currentParpadeo = millis();
    int parpadeo = millis();

    while (digitalRead(BOTON_ACCION) == LOW) {

      //para el RGB

      if (turno == 1) {
        digitalWrite(RED, HIGH);  // Enciende el LED rojo
        digitalWrite(BLUE, LOW);  // Apaga el LED azul
      } else {
        digitalWrite(RED, LOW);    // Apaga el LED rojo
        digitalWrite(BLUE, HIGH);  // Enciende el LED azul
      }

      columnaActual = columna;
      currentParpadeo = millis();

      if (currentParpadeo - parpadeo > 400) {
        parpadeo = currentParpadeo;

        if (color != colorFondo && colorFondo != ILI9341_BLACK) {
          color = ILI9341_WHITE;
        } else if (color != colorFondo && colorFondo == ILI9341_BLACK) {
          color = ILI9341_BLACK;
        } else {
          color = (turno == 1) ? ILI9341_RED : ILI9341_BLUE;
        }
      }

      tft.fillCircle(columnaActual * 40 + 40, 30, 10, color);
      currentMilis = millis();

      if (digitalRead(BOTON_IZQ) == HIGH && currentMilis - time > DELAY) {
        time = currentMilis;
        if (--columna < 0) columna = COLUMNAS - 1;
        tone(BUZZER, NOTE_E5, 100);
        delay(100);
        noTone(BUZZER);
      }
      if (digitalRead(BOTON_DER) == HIGH && currentMilis - time > DELAY) {
        time = currentMilis;
        columna = (++columna) % COLUMNAS;
        tone(BUZZER, NOTE_E5, 100);
        delay(100);
        noTone(BUZZER);
      }
      if (columnaActual != columna) tft.fillCircle(columnaActual * 40 + 40, 30, 10, colorFondo);
    }
    if (fichasColumna[columna] >= FILAS) {
      // poner una X cuando no esté permitido poner más fichas
      tft.fillCircle(columnaActual * 40 + 40, 30, 10, colorFondo);
      drawX(columnaActual * 40 + 40, 30, 10, ILI9341_PURPLE);
      tone(BUZZER, SONIDOERROR, 100);
      delay(200);
      drawX(columnaActual * 40 + 40, 30, 10, colorFondo);
      continue;
    }

    tablero[FILAS - 1 - fichasColumna[columna]][columna] = turno;

    // Dibujar ficha en el tablero
    uint16_t colorFicha;
    (turno == 1) ? colorFicha = ILI9341_RED : colorFicha = ILI9341_BLUE;
    tft.fillCircle(40 * columna + 40, 210 - 30 * fichasColumna[columna]++, 10, colorFicha);
    tft.fillCircle(columnaActual * 40 + 40, 30, 10, colorFondo);
    sonidoColocarFicha();

    contFich++;

    delay(DELAY);

    if (contFich == FILAS * COLUMNAS) {
      ganador = E;
      victoria = E;
      contFich = 0;
    }

    if (victoria == 0) {
      if (victoria = comprobar_victoria(turno) != 0) ganador = turno;
    }

    turno = (turno == 1) ? 2 : 1;  // Cambiar turno
  }
}

elegirTurnoInicial

Esta función elige un número aleatorio entre 1 y 2, y hace una animación alternando colores, que finaliza en el color del turno inicial. Durante esta animación, el buzzer va haciendo sonidos.

int elegirTurnoInicial() {

  //Inicializaciones
  randomSeed(millis());

  int val = analogRead(LDR);

  if (val < 350) {
    colorFondo = ILI9341_BLACK;
  }else{
    colorFondo = ILI9341_WHITE;
  }

  tft.fillScreen(colorFondo);
  tft.setTextSize(3);
  tft.setTextColor(ILI9341_BLUE, colorFondo);

  // Elegir aleatoriamente el turno inicial
  int turnoInicial = random() % 2 + 1;  // Genera un número aleatorio entre 1 y 2

  // Hacer que el LED parpadee de azul a rojo
  int ledColor = ILI9341_BLUE;
  int parpadeo = millis();

  int centroX = tft.width() / 2;
  int centroY = tft.height() / 2;
  int radioFicha = 30;

  for (int i = 0; i < 10; ++i) {  // Parpadear 10 veces tanto el circulo en pantalla como el led

    if (ledColor == ILI9341_BLUE) {
      digitalWrite(BLUE, HIGH);
      digitalWrite(RED, LOW);
      tft.fillCircle(centroX, centroY, radioFicha, ILI9341_BLUE);
      tone(BUZZER, NOTE_A2, 200);  // Tono grave para el color azul

    } else {
      digitalWrite(BLUE, LOW);
      digitalWrite(RED, HIGH);
      tft.fillCircle(centroX, centroY, radioFicha, ILI9341_RED);
      tone(BUZZER, NOTE_G2, 200);  // Tono grave para el color rojo
    }


    delay(200);  // Pausa entre cambios de color
    ledColor = (ledColor == ILI9341_BLUE) ? ILI9341_RED : ILI9341_BLUE;
  }

  //finalmente para mostrar al decidido comprobamos el turno inicial
  // e imprimimos un msj

  if (turnoInicial == 1) {

    digitalWrite(BLUE, LOW);
    digitalWrite(RED, HIGH);
    tft.fillCircle(centroX, centroY, radioFicha, ILI9341_RED);

    tft.setTextColor(ILI9341_RED, colorFondo);
    tft.setCursor(60, 170);
    tft.print("Jugador Rojo");
    tft.setCursor(80, 200);
    tft.print("Comienza!!");

  } else if (turnoInicial == 2) {
    digitalWrite(BLUE, HIGH);
    digitalWrite(RED, LOW);
    tft.fillCircle(centroX, centroY, radioFicha, ILI9341_BLUE);

    tft.setTextColor(ILI9341_BLUE, colorFondo);
    tft.setCursor(60, 170);
    tft.print("Jugador Azul");
    tft.setCursor(80, 200);
    tft.print("Comienza!!");
  }
  //un delay para que se pueda leer con calma
  delay(250);

  // Tocar una música de victoria al finalizar
  if (turnoInicial == 1) {
    // Tono para el jugador rojo
    tone(BUZZER, NOTE_C4, 200);
    delay(200);
    tone(BUZZER, NOTE_E4, 200);
    delay(200);
    tone(BUZZER, NOTE_G4, 400);
  } else {
    // Tono para el jugador azul
    tone(BUZZER, NOTE_G4, 200);
    delay(200);
    tone(BUZZER, NOTE_E4, 200);
    delay(200);
    tone(BUZZER, NOTE_C4, 400);
  }

  noTone(BUZZER);  // Apagar el tono

  return turnoInicial;
}

pintarTablero

Esta función simplemente se encarga de pintar en la pantalla las líneas necesarias para formar el tablero

void pintarTablero() {

  // Para pintar el tablero leemos el valor del LDR para saber si es necesario
  //poner el modo oscuro o el modo claro y dibujamos las líneas del tablero 7x7

  int val = analogRead(LDR);

  if (val < 350) {

    colorFondo = ILI9341_BLACK;
    tft.fillScreen(ILI9341_BLACK);

    for (int i = 0; i <= tft.width(); i += 40) {
      tft.drawLine(20 + i, 45, 20 + i, 225, ILI9341_WHITE);
    }
    for (int i = 30; i <= tft.height(); i += 30) {
      tft.drawLine(20, 15 + i, 300, 15 + i, ILI9341_WHITE);
    }

  } else {

    colorFondo = ILI9341_WHITE;
    tft.fillScreen(ILI9341_WHITE);

    for (int i = 0; i <= tft.width(); i += 40) {
      tft.drawLine(20 + i, 45, 20 + i, 225, ILI9341_BLACK);
    }
    for (int i = 30; i <= tft.height(); i += 30) {
      tft.drawLine(20, 15 + i, 300, 15 + i, ILI9341_BLACK);
    }
  }
}

comprobarVictoria

Esta función es llamada al final de cada turno, y se encarga de recorrer el tablero buscando combinaciones de 4 fichas consecutivas iguales. Para ello. recibe el turno actual, y, mediante 4 series de bucles anidados recorre el tablero buscando victorias horizontales, verticales y diagonales respectivamente.

En el caso de haber encontrado una victoria, se pinta una linea que parpadea indicando las 4 fichas que causan la victoria, y devuelve un valor que indica la victoria


int comprobar_victoria(int jugador) {
  int fila, columna, i;
  // Comprobar horizontal
  for (fila = 0; fila < FILAS; fila++) {
    for (columna = 0; columna < COLUMNAS - 3; columna++) {
      int ganador = 1;
      for (i = 0; i < 4; i++) {
        if (tablero[fila][columna + i] != jugador) {
          ganador = 0;
          break;
        }
      }
      if (ganador) {
        ganar(columna, fila, columna + 3, fila);
        return jugador;
      }
    }
  }

  // Comprobar vertical
  for (columna = 0; columna < COLUMNAS; columna++) {
    for (fila = 0; fila < FILAS - 3; fila++) {
      int ganador = 1;
      for (i = 0; i < 4; i++) {
        if (tablero[fila + i][columna] != jugador) {
          ganador = 0;
          break;
        }
      }
      if (ganador) {
        ganar(columna, fila, columna, fila + 3);
        return jugador;
      }
    }
  }

  // Comprobar diagonal (de izquierda a derecha)

  for (fila = 0; fila < FILAS - 3; fila++) {
    for (columna = 0; columna < COLUMNAS - 3; columna++) {
      int ganador = 1;
      for (i = 0; i < 4; i++) {
        if (tablero[fila + i][columna + i] != jugador) {
          ganador = 0;
          break;
        }
      }
      if (ganador) {
        ganar(columna, fila, columna + 3, fila + 3);
        return jugador;
      }
    }
  }

  // Comprobar diagonal (de derecha a izquierda)
  for (fila = 0; fila < FILAS - 3; fila++) {
    for (columna = 3; columna < COLUMNAS; columna++) {
      int ganador = 1;
      for (i = 0; i < 4; i++) {
        if (tablero[fila + i][columna - i] != jugador) {
          ganador = 0;
          break;
        }
      }
      if (ganador) {
        ganar(columna, fila, columna - 3, fila + 3);
        return jugador;
      }
    }
  }

  return 0;  // No hay ganador
}

mostrarGanador

Esta función es llamada cuando finaliza una partida, y se encarga de mostrar en la pantalla el ganador de la partida, o informar del empate caso de que se diera.

void mostrarGanador() {
  // para mostrar el ganador primero llenamos la pantalla con el color asignado
  // para el modo claro o oscuro

 int val = analogRead(LDR);

  if (val < 350) {
    colorFondo = ILI9341_BLACK;
  }else{
    colorFondo = ILI9341_WHITE;
  }

  tft.fillScreen(colorFondo);

  //colocamos el cursor desde donde queremos pintar y elegimos el tam de letra
  tft.setCursor(50, 90);
  tft.setTextSize(3);

  //si gana el 1 significa que ganó el jugador rojo y el texto estará de su color
  //si es el 2 sería el azúl y para el empate se elige un morado intermedio

  if (ganador == 1) {
    tft.setTextColor(ILI9341_RED, colorFondo);
    tft.print("Jugador Rojo");
    tft.setCursor(100, 140);
    tft.print("Gana!!!");
    digitalWrite(RED, HIGH);

  } else if (ganador == 2) {
    tft.setTextColor(ILI9341_BLUE, colorFondo);
    tft.print("Jugador Azul");
    tft.setCursor(100, 140);
    tft.print("Gana!!!");
    digitalWrite(BLUE, HIGH);

  } else {
    tft.setTextColor(ILI9341_PURPLE, colorFondo);
    tft.setCursor(80, 110);
    tft.print("EMPATE!!!");

    digitalWrite(RED, HIGH);
    digitalWrite(BLUE, HIGH);
  }

  sonidoVictoria();
}

Adicionalmente, se han utilizado algunas funciones auxiliares, que facilitan la lectura del código además de mejorar su semántica

drawX

Funcnión que dibuja una X cuando intentas colocar una ficha en una columna que ya está llena

void drawX(int x, int y, int size, uint16_t color) {
  // Dibuja la primera línea de la "x"
  tft.drawLine(x - size, y - size, x + size, y + size, color);
  // Dibuja la segunda línea de la "x"
  tft.drawLine(x - size, y + size, x + size, y - size, color);
}

ganar

Función que dibuja una línea que parpadea entre las 4 fichas que conforman la combinación ganadora

void ganar(int x0, int y0, int x1, int y1) {
  for (int i = 0; i < 4; i++) {
    tft.drawLine(x0 * 40 + 40, y0 * 30 + 60, x1 * 40 + 40, y1 * 30 + 60, ILI9341_BLACK);
    delay(500);
    tft.drawLine(x0 * 40 + 40, y0 * 30 + 60, x1 * 40 + 40, y1 * 30 + 60, ILI9341_WHITE);
    delay(500);
  }
}

sonidoVictoria

Función que toca una melodía de victoria en el buzzer cuando un jugador gana la partida

void sonidoVictoria() {

  //sonido original para la victoria de un jugador, con sus notas escritas

  int melody[] = {
    NOTE_G5, NOTE_G5, NOTE_G5, NOTE_G5,  // Sol
    NOTE_DS5,                            // RE #
    NOTE_F5,                             // Fa
    NOTE_G5, NOTE_F5,                    // Sol, Fa
    NOTE_G5                              // Sol
  };

  int noteDurations[] = {
    200, 200, 200, 500,  // Duraciones de las primeras notas
    400,                 // Duración de D#5
    400,                 // Duración de Fa
    300, 300,            // Duraciones de Sol y Fa
    500                  // Duración de la última nota
  };

  for (int i = 0; i < sizeof(melody) / sizeof(melody[0]); i++) {
    tone(BUZZER, melody[i], noteDurations[i]);
    delay(noteDurations[i] + 20);  // pequeño delay entre notas
  }
  delay(200);  // Pausa final
  noTone(BUZZER);
}

sonidoColocarFicha

Se llama a esta función cada vez que se coloca una ficha en el tablero. Toca una pequeña melodía en el zumbador indicando que la ficha se ha colocado.

void sonidoColocarFicha() {

  //sonido al colocar la ficha, 3 simples todos ascendentes agradables de escuchar

  int melody[] = { NOTE_A3, NOTE_D4 };
  int durations[] = { 200, 200 };  // Duración en milisegundos para cada nota

  for (int i = 0; i < sizeof(melody) / sizeof(melody[0]); i++) {

    tone(BUZZER, melody[i], durations[i]);
    delay(durations[i] + 50);  // Pausa pequeña entre cada nota
    noTone(BUZZER);
  }
}

Finalmente, añadir que el color de la pantalla cambia en base al valor de luminosidad que detecta el LDR, lo cual añade código en muchas de las funciones.

Problemas y soluciones encontradas

  • Pantalla: No lográbamos conectar adecuadamente la pantalla. Para solucionarlo, buscamos varias guías en YouTube para encontrar cómo conectarla adecuadamente, además de leer la documentación para poder utilizar sus funciones.
  • Carcasa: Nos fue difícil encontrar la manera más adecuada y óptima de empotrar el circuito en una carcasa hecha a medida. Tuvimos que imprimirla varias veces. Finalmente lo solucionamos tras mucho ensayo y error.
  • Cableado: Una vez teníamos la carcasa, tuvimos problemas para cablear todo el circuito, cortarlos y mantener un tamaño adecuado para el sistema.
  • Zumbador: Tuvimos un fallo con el zumbador pues un error de conexión hizo que lo quemáramos y tuvimos que conseguir otro
  • Concurrencia: Necesitamos que el Arduino haga más de una cosa a la vez en muchos casos, por lo que fue difícil simular una concurrencia. Para solucionarlo, utilizamos la función millis(), con sucesiones de condicionales.

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 *