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
Componente | Detalles | Precio por unidad | Unidades | Total |
EELEGOO UNO R3 | 19,99€ | 1 | 19,99€ | |
Protoboard | 400 puntos | 3€ | 1 | 3€ |
Cableado | 1 metro de cable 21 cables macho-hembra | 1€/m 5€/pack de 40 | 1m 1 pack | 6€ |
Zumbador | 0,75€ | 1 | 0,75€ | |
LED RGB | 0,56€ | 1 | 0,56€ | |
LDR | 0,67€ | 1 | 0,67€ | |
Interruptor de botón | 0,10€ | 3 | 0,30€ | |
Pantalla ILI9341 | 240*320 5V/3.3V | 15,99€ | 1 | 15,99€ |
Carcasa | Hecha a medida e impresa en 3D | 25€ | 1 | 25€ |
Resistencias | 6 x 1KΩ, 100Ω, 5 x 5KΩ, 4 x 10KΩ | 0,02€ | 16 | 0,32€ |
Pila | 9V | 3,63€ | 1 | 3,63€ |
Total | 76,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 pin | Numero de pin en Arduino |
LDR | 1 (pin analogico) |
BOTON_ACCION | 2 |
BOTON_DER | 3 |
BOTON_IZQ | 4 |
BLUE(led) | 5 |
RED(led) | 6 |
BUZZER | 7 |
TFT_RESET | 8 |
TFT_DC | 9 |
TFT_CS | 10 |
TFT_MOSI | 11 |
TFT_MISO | 12 |
TFT_SCK | 13 |
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.