EMPOTRIVIAL
AUTORES: Alejandro Sisla Almorox, Álvaro Martín Martín y Miguel Ángel Moreno Campos
INTRODUCCION
Empotrivial consiste en la creación de un juego de preguntas 1vs1, estilo trivial/preguntados utilizando una placa Arduino UNO y otros componentes electrónicos, que se detallaran más adelante. El dispositivo portátil incluirá un conjunto de botones para que los jugadores respondan las preguntas y dos marcadores donde se mostrara la puntuación. Además, contendrá un altavoz en la parte superior de la caja, el cual aportará un grado de emoción y nos hará adentrarnos en el juego.
El juego contará con una amplia variedad de categorías de preguntas, en concreto historia, ciencia, deportes, geografía, entretenimiento y arte. El sistema de preguntas consistirá en un conjunto de cartas físicas que estarán dispuestas en unos compartimentos en el lateral de la caja.
Cada carta contendrá en su parte delantera la pregunta y las diferentes respuestas; en la parte trasera estará dispuesto un código de barras, el cual será leído por un lector una vez el usuario pase la carta sobre el lector.
El sistema de puntuación será automatizado y mostrará los resultados en los marcadores en todo momento. El juego tambien incluirá un temporizador para que los jugadores respondan las preguntas en un tiempo determinado, en nuestro caso de 30 segundos, lo que añadirá un elemento de emoción y competencia al juego. Además, se mostrará el turno de cada jugador en todo momento.
En resumen, este proyecto tiene como objetivo crear un dispositivo portátil de juego de preguntas que sea divertido, desafiante y educativo al mismo tiempo, ofreciendo una experiencia similar al famoso juego Trivial.
MECANICA DEL JUEGO
- En primer lugar, se iniciará el juego. Se iluminarán los leds correspondientes, en concreto los leds de la ruleta que girarán continuamente hasta que se pulse el botón de INICIO
- Una vez pulsado el botón de inicio, comenzará una partida. Se encenderá el led del jugador 1, puesto que en un inicio empieza siempre el jugador 1 por simplicidad. Los marcadores se inicializarán a 0 y el contador de tiempo a 30. En este momento se quedará esperando que se pulse el botón de JUGAR
- En el momento que se pulse este botón, empieza el ciclo de vida de la partida, donde se mantendrá hasta que alguno de los jugadores gane, es decir, llegue a la puntuación de 5
- Ya pulsado el botón de jugar, la ruleta comenzará a ‘girar’, es decir, los leds irán encendiéndose y apagándose continuamente de manera circular, y poco a poco irá disminuyendo la velocidad con la que va girando hasta el momento en que se pare en un determinado momento
- Una vez parado, el led encendido determinará el topic de la pregunta
- En este momento, el jugador deberá coger una carta del compartimento en que estén las preguntas del topic correspondiente. (Situadas en el lateral de la caja)
- A continuación, el jugador deberá pasar la parte posterior de la carta por encima del lector QR. El sistema comprobara si se ha elegido correctamente la carta, es decir, si el topic de la pregunta es el adecuado. En caso contrario, esperará hasta que se elija una carta del topic correcto
- Una vez leído el código QR de la carta, el tiempo de 30 segundos comenzará a correr, y el jugador deberá emplear este tiempo para leer y contestar la pregunta utilizando los botones dispuestos en la caja (Opciones A – B – C – D)
- Si se ha contestado correctamente sonará un sonido ganador y sumará en uno la puntuación dispuesta en el marcador del jugador. En caso de responder erróneamente, no se sumará puntuación al marcador y sonará una melodía perdedora
- Si el jugador no ha alcanzado la puntuación de 5, se vuelve al paso 3.1 donde jugara el jugador contrario
- Una vez alguno de los jugadores alcance la puntuación de 5, sonará una melodía de final de juego y posteriormente se volverá al paso 1, donde esperará que se pulse el botón de inicio en caso de querer jugar otra partida
MATERIALES Y COSTES
COMPONENTE | PRECIO |
Arduino UNO | – |
4 protoboard | – |
2 display | – |
1 buzzer | – |
Cables | 7€ |
6 pulsadores | – |
8 leds | – |
3 registros de desplazamiento | |
Lector de código de barras / QR | 32,67€ |
Material extra (cinta aislante, fundas, spray, impresiones, …) | 10€ |
TOTAL: | 49,67€ |
HARDWARE
Para el montaje del proyecto ha sido necesario en primer lugar, una placa Arduino suministrada por la universidad. Para la conexión de todos los componentes han sido necesarias tres protoboards adicionales a la que ya incluye el kit. Los componentes necesarios para la implementación de dicho proyecto han sido: seis botones, ocho leds, dos displays, un lector de código de barras/QR y un buzzer. A la hora de realizar las conexiones a las protoboards, lo hemos organizado de manera que quede bien distribuido todo y cada protoboard tenga un fin común:
- Dedicada a los cuatro botones de las respuestas, es decir los que se encuentran en la parte
inferior de la caja. Cada botón con sus conexiones a 5V, GND y el pin oportuno de la placa
Arduino. - Dedicada a los dos botones restantes, dedicados a inicio y girar, es decir los que se encuentran
en la zona media de la caja. Cada botón con sus conexiones a 5V, GND y el pin oportuno de la
placa Arduino. Además, tambien se encuentra conectado el buzzer, con sus respectivas
conexiones a 5V y al pin correspondiente de la placa Arduino. - Dedicada a los dos displays de los marcadores donde cada uno corresponde a un desplazador,
con sus respectivas conexiones a este último y donde cada pin de este controlará un segmento
del display - Dedicada a los ocho leds del proyecto, que están controlados por el restante desplazador de registros.
Por último, el lector de códigos de barras está conectado independientemente de las protoboards. Tiene conexiones a 5V, GND y por otro lado a los pines 3 y 4 de la placa Arduino, utilizados como receptor y transmisor del lector.
SOFTWARE
#include <SoftwareSerial.h>
//Notas buzzer
#define NOTE_C4 262
#define NOTE_D4 294
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_B4 494
#define NOTE_C5 523
//Melodías del buffer
//Partida iniciada
int mStart[] = { NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4, NOTE_C5 };
int bStart[] = { 125, 125, 125, 125, 125, 125, 125, 225 };
//Serial del lector QR
SoftwareSerial mySerial(3, 4); // RX, TX
char codigoBarras[1]; //Valores del codigo de barras
//Pines
int start = 2; // Pin del boton de inicio
int spin = 5; // Pin del boton de girar la ruleta
int buzzer = 9; // Pin del buzzer
int respuesta1 = 10;
int respuesta2 = 11;
int respuesta3 = 12;
int respuesta4 = 13;
//Valores de inicio
int startValor = 0; // Valor del boton start
int spinValor = 0; // Valor del boton spin
int respuesta1Valor = 0;
int respuesta2Valor = 0;
int respuesta3Valor = 0;
int respuesta4Valor = 0;
int finPartida = 0; //Boolean para saber cuándo ha acabado la partida
int puntos1 = 0, puntos2 = 0;
int turno = 1;
//Declaración de variables
int notaBuzzer; //Nota del buzzer
int n; //Para recorrer bucles
long randomNumber;
int topic; //Num del topic
int delaySpin;
int numPregunta;
//Struct de pregunta
struct pregunta {
int topic;
int respuesta;
};
//Array de preguntas
pregunta preguntas[60];
//Valores del desp
int pinDato = 6; // Pin conectado a DS
int pinClock = 8; // Pin conectado a SHCP
int pinRegistro = 7; // Pin conectado a STCP
byte datos;
//Numeros para los displays
byte numbers[] = {
0b0111111, //0
0b0000110, //1
0b1011011, //2
0b1001111, //3
0b1100110, //4
0b1101101, //5
0b1111101, //6
0b0000111, //7
0b1111111, //8
0b1100111 //9
};
void melodiaInicio() { //Melodías que suena al empezar
notaBuzzer = 0;
while (notaBuzzer < (sizeof(mStart) / sizeof(int))) {
tone(buzzer, mStart[notaBuzzer]);
delay(bStart[notaBuzzer]);
noTone(buzzer);
notaBuzzer++;
}
return;
}
void melodiaInversa() { //Melodías que suena al empezar
notaBuzzer = sizeof(mStart) / sizeof(int) - 1;
while (notaBuzzer >= 0) {
tone(buzzer, mStart[notaBuzzer]);
delay(bStart[notaBuzzer]);
noTone(buzzer);
notaBuzzer--;
}
return;
}
void encenderLuces(int lucesEmpezar, byte rueda){
digitalWrite(pinRegistro, LOW);
if (lucesEmpezar == 1){
shiftOut(pinDato, pinClock, MSBFIRST, 0b000000);
shiftOut(pinDato, pinClock, MSBFIRST, 0b000000);
} else {
shiftOut(pinDato, pinClock, MSBFIRST, numbers[puntos2]);
shiftOut(pinDato, pinClock, MSBFIRST, numbers[puntos1]);
}
shiftOut(pinDato, pinClock, MSBFIRST, rueda);
digitalWrite(pinRegistro, HIGH);
}
void lucesEmpezar() {
int antDatos;
int aux = 4;
int auxResta;
datos = 4;
n = 5;
startValor = digitalRead(start);
if (startValor == HIGH)
return;
while (n >= 0) {
startValor = digitalRead(start);
if (startValor == HIGH)
return;
encenderLuces(1, datos);
delay(200);
n--;
antDatos = datos;
datos = aux * 2;
aux = datos;
datos = datos + antDatos;
}
n = 5;
aux = 4;
startValor = digitalRead(start);
if (startValor == HIGH)
return;
while (n >= 0) {
startValor = digitalRead(start);
if (startValor == HIGH)
return;
encenderLuces(1, datos);
delay(200);
n--;
datos = datos - aux;
aux = aux * 2;
}
startValor = digitalRead(start);
if (startValor == HIGH)
return;
}
void declararPreguntas() { //Inicializar todas las preguntas
//Historia
preguntas[0].topic = 1;
preguntas[0].respuesta = 2;
preguntas[1].topic = 1;
preguntas[1].respuesta = 2;
preguntas[2].topic = 1;
preguntas[2].respuesta = 1;
preguntas[3].topic = 1;
preguntas[3].respuesta = 2;
preguntas[4].topic = 1;
preguntas[4].respuesta = 1;
preguntas[5].topic = 1;
preguntas[5].respuesta = 3;
preguntas[6].topic = 1;
preguntas[6].respuesta = 1;
preguntas[7].topic = 1;
preguntas[7].respuesta = 1;
preguntas[8].topic = 1;
preguntas[8].respuesta = 4;
preguntas[9].topic = 1;
preguntas[9].respuesta = 3;
//Ciencia
preguntas[10].topic = 2;
preguntas[10].respuesta = 2;
preguntas[11].topic = 2;
preguntas[11].respuesta = 1;
preguntas[12].topic = 2;
preguntas[12].respuesta = 3;
preguntas[13].topic = 2;
preguntas[13].respuesta = 3;
preguntas[14].topic = 2;
preguntas[14].respuesta = 2;
preguntas[15].topic = 2;
preguntas[15].respuesta = 3;
preguntas[16].topic = 2;
preguntas[16].respuesta = 1;
preguntas[17].topic = 2;
preguntas[17].respuesta = 1;
preguntas[18].topic = 2;
preguntas[18].respuesta = 3;
preguntas[19].topic = 2;
preguntas[19].respuesta = 2;
//Entretenimiento
preguntas[20].topic = 3;
preguntas[20].respuesta = 2;
preguntas[21].topic = 3;
preguntas[21].respuesta = 3;
preguntas[22].topic = 3;
preguntas[22].respuesta = 1;
preguntas[23].topic = 3;
preguntas[23].respuesta = 4;
preguntas[24].topic = 3;
preguntas[24].respuesta = 3;
preguntas[25].topic = 3;
preguntas[25].respuesta = 3;
preguntas[26].topic = 3;
preguntas[26].respuesta = 4;
preguntas[27].topic = 3;
preguntas[27].respuesta = 3;
preguntas[28].topic = 3;
preguntas[28].respuesta = 1;
preguntas[29].topic = 3;
preguntas[29].respuesta = 1;
//Arte
preguntas[30].topic = 4;
preguntas[30].respuesta = 1;
preguntas[31].topic = 4;
preguntas[31].respuesta = 2;
preguntas[32].topic = 4;
preguntas[32].respuesta = 1;
preguntas[33].topic = 4;
preguntas[33].respuesta = 4;
preguntas[34].topic = 4;
preguntas[34].respuesta = 3;
preguntas[35].topic = 4;
preguntas[35].respuesta = 1;
preguntas[36].topic = 4;
preguntas[36].respuesta = 3;
preguntas[37].topic = 4;
preguntas[37].respuesta = 4;
preguntas[38].topic = 4;
preguntas[38].respuesta = 2;
preguntas[39].topic = 4;
preguntas[39].respuesta = 1;
//Deportes
preguntas[40].topic = 5;
preguntas[40].respuesta = 1;
preguntas[41].topic = 5;
preguntas[41].respuesta = 1;
preguntas[42].topic = 5;
preguntas[42].respuesta = 3;
preguntas[43].topic = 5;
preguntas[43].respuesta = 3;
preguntas[44].topic = 5;
preguntas[44].respuesta = 2;
preguntas[45].topic = 5;
preguntas[45].respuesta = 3;
preguntas[46].topic = 5;
preguntas[46].respuesta = 3;
preguntas[47].topic = 5;
preguntas[47].respuesta = 2;
preguntas[48].topic = 5;
preguntas[48].respuesta = 1;
preguntas[49].topic = 5;
preguntas[49].respuesta = 4;
//Geografía
preguntas[50].topic = 6;
preguntas[50].respuesta = 4;
preguntas[51].topic = 6;
preguntas[51].respuesta = 1;
preguntas[52].topic = 6;
preguntas[52].respuesta = 4;
preguntas[53].topic = 6;
preguntas[53].respuesta = 3;
preguntas[54].topic = 6;
preguntas[54].respuesta = 2;
preguntas[55].topic = 6;
preguntas[55].respuesta = 4;
preguntas[56].topic = 6;
preguntas[56].respuesta = 2;
preguntas[57].topic = 6;
preguntas[57].respuesta = 1;
preguntas[58].topic = 6;
preguntas[58].respuesta = 3;
preguntas[59].topic = 6;
preguntas[59].respuesta = 1;
return;
}
int numCodigoBarras(){
//Variables del lector
int valorLeido = 0;
codigoBarras[0] = '\0';
codigoBarras[1] = '\0';
int index = 0;
while (mySerial.available()) // Lee byte a byte del buffer hasta que no quede nada
{
char input = mySerial.read(); // Lee 1 byte y lo guarda en la variable
if (index < 2)
codigoBarras[index++] = input;
delay(5);
}
for (int i = 0; i < index; i++)
if (codigoBarras[i] >= '0' && codigoBarras[i] <= '9')
valorLeido = valorLeido * 10 + (codigoBarras[i] - '0');
return(valorLeido);
}
void setup() {
Serial.begin(9600);
pinMode(start, INPUT);
pinMode(spin, INPUT);
pinMode(buzzer, OUTPUT);
pinMode(respuesta1, INPUT);
pinMode(respuesta2, INPUT);
pinMode(respuesta3, INPUT);
pinMode(respuesta4, INPUT);
//Desp
pinMode(pinRegistro, OUTPUT);
pinMode(pinClock, OUTPUT);
pinMode(pinDato, OUTPUT);
digitalWrite(pinRegistro, LOW);
//Lector
mySerial.begin(115200);
declararPreguntas();
}
void loop() {
int i = 0; //Contador
pregunta newPregunta; //Pregunta seleccionada
long startTiempo = 0;
long tiempoAux1;
long tiempoAux2;
int respuesta = 0;
finPartida = 0;
puntos1 = 0;
puntos2 = 0;
turno = 1;
// Mientras el boton no este pulsado
startValor = digitalRead(start);
while (startValor == LOW) {
startValor = digitalRead(start);
lucesEmpezar();
}
encenderLuces(1, 0); //Apagamos luces
//Melodía de inicio
melodiaInicio();
//Empieza la partida hasta que alguno acabe
while (finPartida == 0) {
encenderLuces(0, turno);
spinValor = digitalRead(spin);
while (spinValor == LOW) //Esperamos hasta pulsar el spin
spinValor = digitalRead(spin);
spinValor = LOW;
//Spin de la rueda
i = 0;
n = 2;
delaySpin = 80;
randomSeed(analogRead(0));
randomNumber = random(30, 54);
while ((long)i++ <= randomNumber) {
n *= 2;
if (n > 128) {
n = 4;
delaySpin += 40;
}
datos = n + turno;
encenderLuces(0, datos);
tone(buzzer, NOTE_C4);
delay(80);
noTone(buzzer);
delay(delaySpin);
}
if (n == 128) //Geografia
topic = 6;
else if (n == 64) //Ciencia
topic = 2;
else if (n == 32) //Deportes
topic = 5;
else if (n == 16) //Arte
topic = 4;
else if (n == 8) //Historia
topic = 1;
else if (n == 4) //Entretenimiento
topic = 3;
while(1)
if (mySerial.available()){ //Leer la pregunta
numPregunta = numCodigoBarras() - 1;
break;
}
Serial.println(numPregunta);
while (numPregunta < 0 || numPregunta > 59) //Comprobar que el numero de la pregunta esta en la lista
if (mySerial.available())
numPregunta = numCodigoBarras() - 1;
while (newPregunta.topic != topic){ //Comprobar que el topic es correcto
while(1)
if (mySerial.available()){ //Volvemos a leer la pregunta
numPregunta = numCodigoBarras() - 1;
break;
}
while (numPregunta < 0 || numPregunta > 59) //Volvemos a comprobar que el index este en la lista
if (mySerial.available())
numPregunta = numCodigoBarras() - 1;
newPregunta = preguntas[numPregunta];
}
Serial.print("Pregunta escaneada, respuesta: ");
Serial.println(newPregunta.respuesta);
delay(500);
startTiempo = millis();
tiempoAux2 = millis();
tone(buzzer, NOTE_E4);
delay(500);
noTone(buzzer);
while(millis() - startTiempo <= 30000 && respuesta == 0)
{
tiempoAux1 = millis();
if (millis() - startTiempo > 25000 && tiempoAux1 >= (tiempoAux2 + 250)){
tone(buzzer, NOTE_E4);
delay(100);
noTone(buzzer);
tiempoAux2 = millis();
} else if (millis() - startTiempo > 20000 && tiempoAux1 >= (tiempoAux2 + 500)){
tone(buzzer, NOTE_E4);
delay(100);
noTone(buzzer);
tiempoAux2 = millis();
} else if (millis() - startTiempo > 10000 && tiempoAux1 >= (tiempoAux2 + 1000)){
tone(buzzer, NOTE_E4);
delay(100);
noTone(buzzer);
tiempoAux2 = millis();
} else if (tiempoAux1 >= (tiempoAux2 + 2000)){
tone(buzzer, NOTE_E4);
delay(100);
noTone(buzzer);
tiempoAux2 = millis();
}
respuesta1Valor = digitalRead(respuesta1);
respuesta2Valor = digitalRead(respuesta2);
respuesta3Valor = digitalRead(respuesta3);
respuesta4Valor = digitalRead(respuesta4);
if (respuesta1Valor == HIGH)
respuesta = 1;
else if (respuesta2Valor == HIGH)
respuesta = 2;
else if (respuesta3Valor == HIGH)
respuesta = 3;
else if (respuesta4Valor == HIGH)
respuesta = 4;
}
Serial.println(respuesta);
if (respuesta == newPregunta.respuesta){
melodiaInicio();
if (turno == 1)
puntos1++;
else
puntos2++;
} else
melodiaInversa();
respuesta = 0;
encenderLuces(0, turno); // Para ver el 5 cuando vaya a acabar la partida
if (turno == 1)
turno++;
else
turno--;
if (puntos1 == 5 || puntos2 == 5){
finPartida = 1;
melodiaInicio();
delay(50);
melodiaInversa();
delay(50);
melodiaInicio();
delay(50);
melodiaInversa();
}
}
}
PROBLEMAS ENCONTRADOS
- Lector de código de barras / QR
- A lo largo de la práctica nos hemos encontrado con diferentes problemas que, con tiempo y esfuerzo hemos podido solucionar. Uno de los problemas que más nos ha dado dolores de cabeza ha sido la implementación del lector de código de barras. Dicha implementación no parecía muy costosa siguiendo la guía correspondiente, pero haciendo todo lo que la guía nos indicaba, no fuimos capaces de que el lector leyera la información de manera correcta. Cambiamos el código y conectamos los cables deinfinitas maneras, hasta llegar al punto de replantearnos sustituir el lector por un path numérico que compramos como sustituto en caso de que el lector no funcionara. Rendirse no estaba dentro de nuestros planes y continuamos intentándolo hasta que se probó a modificar la velocidad de transmisión de 9600 a 115200. Haciendo esto se leen y muestran correctamente los datos leídos del código de barras ya que al ser mayor la velocidad, el mensaje se leía al completo, al contrario de antes que la lectura se quedaba a medias
- Cartas de empotrivial
- Una parte fundamental de Empotrivial son las preguntas que se van a utilizar durante el juego. Pensamos en guardar el enunciado y sus posibles respuestas dentro de una memoria interna y mostrarlas por pantalla, pero nos pareció más accesible y familiar crear las preguntas en físico. Lo único que se va a guardar en el sistema va a ser el número de pregunta elegida (Número leído del código de barras) y su respuesta correcta, además de su topic para no escanear una tarjeta cuyo topic sea incorrecto. Por lo tanto, nos pusimos manos a la obra y pensamos cómo realizar el diseño de las preguntas. Pensamos en hacerlas a mano, sobre una cartulina y plastificarlas, pero al final decidimos hacerlas a ordenador sobre una plantilla de preguntas sencilla ya diseñada para posteriormente pasarlas a un Word y ver cual es la forma óptima de repartir el espacio para imprimir y cortarlas cómodamente.
- Por cada folio se imprimieron 6 preguntas, con la parte delantera y trasera de la carta totalmente juntas. Esto a la hora de recortar es clave ya que no se va a tener que recortar de manera independiente cada una de las caras de la carta, simplemente se recortará alrededor de ambas caras y se doblará para pegarlas con más facilidad.
- La elección de este método nos pareció mejor ya que no vimos necesario centrar al completo nuestras fuerzas y tiempo en la creación de tarjetas que, al fin y al cabo, no son tan importantes como la creación y diseño interno del sistema.
- Cableado
- Otro problema surgido durante el desarrollo que debemos remarcar es la implementación del hardware en el interior de la caja. Al principio no nos parecía una tarea muy difícil y, la verdad que no lo es, pero el gran número de componentes que hay que conectar desde la parte superior de la caja a las protoboards (botones, displays, buzzer o leds) han hecho que haya un gran número de cables. Este hecho supone que al mínimo movimiento de la caja (por ejemplo, a la hora de transportarla) las conexiones dejen de hacer contacto y se haría una odisea luego encontrar el error. La solución a este problema ha sido posible gracias a la cinta aislante, la cual nos ha servido para reforzar las conexiones y así estar seguro de que, a no ser que hagas un movimiento muy brusco, el contacto hecho por los cables debería serefectivo. También hemos pegado las protoboards al fondo de la caja para que estén totalmente inmóviles y evitar problemas.
- Pines insuficientes
- El último de los principales problemas que nos surgieron a la hora de ir conectando todo fue la falta de pines, debido a todos los leds y los displays que usamos necesitamos una gran cantidad, que, sumado a los 6 botones, necesitábamos muchísimos más pines. En un principio usamos el desplazador de registros que nos viene en el kit y decidimos comprar por internet un par de multiplexores. Sin embargo, al intentar usar los multiplexores nos surgieron varios problemas, ya que no recogía bien los valores de los botones y acababan abultando demasiado en las placas produciendo muchísimo lío de cables. Así que finalmente calculamos que con 2 desplazadores de registros más seríamos capaces de conectar todos los leds y los displays usando tan solo 3 pines, y también que seríamos capaces de conectar el resto (botones, lector, buzzer…) al Arduino. Así que utilizamos los desplazadores de otros kits de electrónica que teníamos y conseguimos optimizar al máximo los pines que utilizamos.