SUPER PACO BOMBASTIC SYSTEM

Descripción de proyecto
El proyecto que presento a continuación es un juego interactivo inspirado en el clásico “Escondite Inglés”, pero con una temática moderna y llena de tensión: la desactivación de una bomba de tiempo. El objetivo es simple: los jugadores deben avanzar con el joystick de la bomba mientras el semáforo se encuentra en la luz verde, pero deben quedarse completamente quietos cuando la luz roja se enciende. Si se detecta movimiento en el momento incorrecto, se contabilizaran strikes por cada error cometido y si se cometen 3 strikes, el juego terminara.
El juego tambien acabara por tiempo si no se ha logrado completar dentro del limite establecido, en este caso el jugador tambien perdera.
Si se logra completar la barra de progreso dentro del tiempo establecido, el jugador ganará el juego.
Este proyecto combina electrónica, programación, diseño 3D y creatividad.
Presupuesto total
Para construir este juguete se necesitan unos pocos componentes electrónicos y algunas piezas impresas en 3D.
A continuación se muestra una tabla con el presupuesto aproximado para orientar al lector sobre el coste total del montaje. Los precios pueden variar dependiendo de la tienda o país, pero sirven como referencia general:
| 1x Microcontrolador Elegoo Uno R3 | 6 € |
| 3x LED Rojo 5mm | 0,51 € |
| 7x LED Amarillo 5mm | 0,42 € |
| 1x LED Blanco 5mm | 0,06 € |
| 2x LED Verde 5mm | 0,12 € |
| 1x Joystick Analogico 5 pines | 2,80 € |
| 15x Resistores de100 Ohmios | 1,50 € |
| 25x Jumpers Macho-Macho 5x Jumpers Macho-Hembra | 3,50 € |
| 1x Registro de Desplazamiento 74HC595 | 0,46 € |
| 1x Buzzer | 0,90 € |
| 1x Full BreadBoard | 5,43 € |
| Impresion 3D | 3 € |
| TOTAL | 24,70 € |
Diagrama de diseño de proyecto
Para facilitar el montaje, hemos incluido un diagrama completo del circuito donde se muestra cómo debe conectarse cada LED y módulo al microcontrolador.
Gracias a este esquema, el proyecto puede montarse sin dificultades.
Solo tienes que consultar la imagen que aparece más abajo y replicar el cableado tal cual aparece indicado.
Diagrama de Hardware (Wokwi.com)

Ejemplo de hardware físico:

Recursos adicionales
Código
#include <Arduino.h> // si se trabaja en IDE de arduino , comentar esta linea
#include <limits.h>
#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978
#define REST 0
// MULTIPLEXORES
// Funciones
void actualizarShiftRegisters();
void setProgreso(int index, bool estado);
void setStrike(int index, bool estado);
void setMovimiento(bool verde, bool rojo);
// Pines para multiplexor 74HC595
const int PIN_DATA = 11; // DS
const int PIN_LATCH = 8; // ST_CP
const int PIN_CLOCK = 12; // SH_CP
byte estadoProgreso = 0b00000000; // 8 LEDs PROGRESO
// STRIKES + MOVIMIENTO
const int LED_STRIKE[2] = {4, 7}; // const int LED_STRIKE[3] = {9, 10, 11};
const int LED_MOV_VERDE = 3;
const int LED_MOV_ROJO = 5;
// ESTADO DE MOVIMIENTO
const int num_parpadeos = 5; // Número de parpadeos antes del cambio.
const int delay_parpadeos = 100; // Delay entre parpadeos, debe usar millis().
const int MIN_TIEMPO_RESTORE = 1000; // Tiempo mínimo antes de restaurar.
const int MIN_TIEMPO_CAMBIO = 3000; // Tiempo mínimo antes del cambio, máximo para restaurar.
const int MAX_TIEMPO_CAMBIO = 7000; // Tiempo máximo antes del cambio.
bool estadoMovimiento = true; // true = VERDE | false = ROJO
unsigned long tCambio = 0; // Próximo momento en el que se debe evaluar el cambio
bool enParpadeo = false; // Para saber si está parpadeando o no.
int contadorParpadeos = 0; // Número de parpadeos.
unsigned long tiempoParpadeo = 0; // Tiempo de parpadeo.
void actualizar_estado_movimiento(); // Cambiar entre VERDE y ROJO con transición (parpadeo).
// LECTURA DE JOYSTICK
const int JOYSTICK[2] = {A0, A1}; // Pines del joystick (lectura analógica).
const int PIN_RESET = 2; // Botón joystick, para reset.
// Reset por pulsación larga
const unsigned long RESET_HOLD_TIME = 3000; // 3 segundos
unsigned long tiempoInicioHold = 0;
bool holdEnProgreso = false;
void detectar_reset();
void resetearJuego();
const int VUELTAS_POR_LUZ = 20; // Vueltas necesarias para cada luz.
int vueltas = 0; // Vueltas dadas con el joystick.
// Para calcular según orientación joystick.
float angulo_anterior = 0, angulo_actual = 0;
int vueltasCompletas = 0, vueltasParciales = 0, vueltasPrevias = 0;
void leer_joystick_prueba();
void leer_joystick(); // Captar cuánto gira el joystick para iluminar LEDs.
// LEDS DE PROGRESO
unsigned long lastPWM = 0; // Controla cada cuánto se ejecuta un paso de PWM.
int pwmStep = 0; // Nivel actual dentro del ciclo PWM.
void actualizar_progreso(); // Actualizar los LEDS_PROGRESO.
// GESTIÓN DE STRIKES
const int MAX_STRIKES = 3; // Cantidad máxima de STRIKES.
const int COOLDOWN_STRIKE = 1000; // Cooldown para el STRIKE.
int strikesCount = 0; // Número actual de STRIKES.
bool puedeMover = true; // true - PUEDE MOVER | false - NO PUEDE MOVER.
unsigned long tiempoStrike = 0; // Tiempo hasta permitir el siguiente STRIKE.
void gestionar_strikes(); // Actualizar los LEDS_STRIKES cuando sea necesario.
// ACTUALIZACIÓN DE TIEMPO
const int LED_TIEMPO = 10; // Pin PWM
const unsigned long TIEMPO_TOTAL = 150000UL; // 2:30 minutos en milisegundos
unsigned long tiempoInicio; // Tiempo en el que se inicia la partida.
bool gameOver = false; // Para saber cuando se ha acabado el tiempo.
bool victoria = false; // Para saber cuando ha terminado el juego por victoria.
void actualizar_tiempo(); // Actualizar el display del tiempo.
// SONIDO
const int PIN_ZUMBADOR = 9; // Pin para el zumbador.
// VICTORIA
const int VICTORY_SOUND = 0; // Sonido de victoria.
int v_tempo = 85;
// Melodía completa
int v_melody[] = {
NOTE_D5,-4, NOTE_A5,8, NOTE_FS5,8, NOTE_D5,8,
NOTE_E5,-4, NOTE_FS5,8, NOTE_G5,4,
NOTE_FS5,-4, NOTE_E5,8, NOTE_FS5,4,
NOTE_D5,-2,
NOTE_D5,-4, NOTE_A5,8, NOTE_FS5,8, NOTE_D5,8,
NOTE_E5,-4, NOTE_FS5,8, NOTE_G5,4,
NOTE_FS5,-1,
NOTE_D5,-4, NOTE_A5,8, NOTE_FS5,8, NOTE_D5,8,
NOTE_E5,-4, NOTE_FS5,8, NOTE_G5,4,
NOTE_FS5,-4, NOTE_E5,8, NOTE_FS5,4,
NOTE_D5,-2,
NOTE_D5,-4, NOTE_A5,8, NOTE_FS5,8, NOTE_D5,8,
NOTE_E5,-4, NOTE_FS5,8, NOTE_G5,4,
NOTE_FS5,-1,
};
int v_notes = sizeof(v_melody) / sizeof(v_melody[0]) / 2;
int v_wholenote = (60000 * 4) / v_tempo;
// Variables para reproducción NO bloqueante
bool reproduciendoVictoria = false;
unsigned long v_notaInicio = 0;
int v_notaIndex = 0;
unsigned int v_notaDuracion = 0;
// DERROTA
const int FAILURE_SOUND = 1; // Sonido de derrota.
int f_tempo = 108;
// Melodía de Zelda’s Lullaby (versión derrota).
int f_melody[] = {
NOTE_E4,2, NOTE_G4,4,
NOTE_D4,2, NOTE_C4,8, NOTE_D4,8,
NOTE_E4,2, NOTE_G4,4,
NOTE_D4,-2,
NOTE_E4,2, NOTE_G4,4,
NOTE_D5,2, NOTE_C5,4,
NOTE_G4,2, NOTE_F4,8, NOTE_E4,8,
NOTE_D4,-2,
NOTE_E4,2, NOTE_G4,4,
NOTE_D4,2, NOTE_C4,8, NOTE_D4,8,
NOTE_E4,2, NOTE_G4,4,
NOTE_D4,-2,
NOTE_E4,2, NOTE_G4,4,
NOTE_D5,2, NOTE_C5,4,
NOTE_G4,2, NOTE_F4,8, NOTE_E4,8,
NOTE_F4,8, NOTE_E4,8, NOTE_C4,2,
NOTE_F4,2, NOTE_E4,8, NOTE_D4,8,
NOTE_E4,8, NOTE_D4,8, NOTE_A3,2,
NOTE_G4,2, NOTE_F4,8, NOTE_E4,8,
NOTE_F4,8, NOTE_E4,8, NOTE_C4,4, NOTE_F4,4,
NOTE_C5,-2
};
// Número total de notas.
int f_notes = sizeof(f_melody) / sizeof(f_melody[0]) / 2;
// Duración de nota completa en ms.
int f_wholenote = (60000 * 4) / f_tempo;
// Estado interno de reproducción.
bool reproduciendoDerrota = false;
unsigned long f_noteStart = 0;
int f_noteIndex = 0;
unsigned int f_noteDuration = 0;
void play_sound(int id); // Función para reproducir sonido.
void stop_sound(); // Parar sonido.
void actualizar_sonido(); // Para gestionar melodías complejas.
// GENERAL
void init_pins(); // Inicializar de PINES (mayor limpieza de SETUP).
// SETUP
void setup()
{
// Se inicializa el Serial.
Serial.begin(9600);
// Se inicializan los pines.
init_pins();
// Se prepara el primer tiempo de cambio aleatorio.
tCambio = millis() + random(MIN_TIEMPO_CAMBIO, MAX_TIEMPO_CAMBIO);
// Se prepara el tiempo de juego.
tiempoInicio = millis();
pinMode(LED_TIEMPO, OUTPUT);
analogWrite(LED_TIEMPO, 255); // Empieza encendido al máximo
}
// LOOP
void loop()
{
detectar_reset();
actualizar_sonido();
//leer_joystick_prueba();
leer_joystick();
actualizar_progreso();
actualizar_estado_movimiento();
gestionar_strikes();
actualizar_tiempo();
delay(20);
}
// IMPLEMENTACIÓN DE FUNCIONES
// MULTIPLEXORES
void actualizarShiftRegisters()
{
digitalWrite(PIN_LATCH, LOW);
shiftOut(PIN_DATA, PIN_CLOCK, MSBFIRST, estadoProgreso); // 1º registro
digitalWrite(PIN_LATCH, HIGH);
}
void setProgreso(int index, bool estado)
{
if(index < 0 || index > 7) return;
bitWrite(estadoProgreso, index, estado);
}
void setStrike(int index, bool estado)
{
digitalWrite(LED_STRIKE[index], estado);
}
void setMovimiento(bool verde, bool rojo)
{
digitalWrite(LED_MOV_VERDE, verde);
digitalWrite(LED_MOV_ROJO, rojo);
}
// Cambiar entre VERDE y ROJO con transición (parpadeo).
void actualizar_estado_movimiento()
{
// Si el juego terminó (tiempo == 0), apagar parpadeo y fijar en ROJO.
if (gameOver)
{
enParpadeo = false;
estadoMovimiento = (victoria) ? true : false; // VERDE O ROJO.
setMovimiento(estadoMovimiento, !estadoMovimiento);
puedeMover = false;
// Congelar completamente el sistema de semáforo
tCambio = ULONG_MAX; // Nunca más habrá cambio
contadorParpadeos = 0;
return; // IMPORTANTE: no seguir ejecutando la función
}
// Se guarda el tiempo actual para comparar sin bloquear.
unsigned long ahora = millis();
// Si aún no es momento de cambiar y no está parpadeando, se muestra el estado normal.
if (!enParpadeo && ahora < tCambio)
{
setMovimiento(estadoMovimiento, !estadoMovimiento);
return;
}
// Si NO está parpadeando y es momento de cambiar, se inicia el parpadeo.
if (!enParpadeo)
{
enParpadeo = true;
contadorParpadeos = 0;
tiempoParpadeo = ahora;
}
// Manejar el parpadeo.
if (ahora - tiempoParpadeo >= delay_parpadeos)
{
tiempoParpadeo = ahora;
contadorParpadeos++;
// Alternar LEDs en parpadeo
bool parp = contadorParpadeos % 2;
setMovimiento(parp, parp);
// Parpadeo terminado, se cambia el estado.
if (contadorParpadeos >= num_parpadeos * 2)
{
// Cambiar estado
estadoMovimiento = !estadoMovimiento;
enParpadeo = false;
// Nuevo tiempo antes del siguiente cambio
if (estadoMovimiento)
{
// si ahora está verde → espera más tiempo antes de volver a rojo
tCambio = ahora + random(MIN_TIEMPO_CAMBIO, MAX_TIEMPO_CAMBIO);
}
else
{
// si ahora está rojo → espera menos tiempo antes de volver a verde
tCambio = ahora + random(MIN_TIEMPO_RESTORE, MIN_TIEMPO_CAMBIO);
}
// Actualizar LED final tras cambio.
setMovimiento(estadoMovimiento, !estadoMovimiento);
}
}
// Evitar errores.
puedeMover = estadoMovimiento;
}
// Captar cuánto gira el joystick para iluminar LEDs.
void leer_joystick_prueba()
{
if (gameOver) return;
int y = analogRead(JOYSTICK[1]) - 512;
static unsigned long ultimaVuelta = 0;
const unsigned long DELAY_VUELTA = 150; // velocidad a la que simula vueltas
// Solo sumar vueltas si se mantiene hacia arriba
if (y < -200) // Ajustable según sensibilidad del joystick
{
unsigned long ahora = millis();
if (ahora - ultimaVuelta >= DELAY_VUELTA)
{
ultimaVuelta = ahora;
vueltas++;
vueltasCompletas = vueltas / VUELTAS_POR_LUZ;
vueltasParciales = vueltas % VUELTAS_POR_LUZ;
Serial.print("Vueltas simuladas: ");
Serial.print(vueltas);
Serial.print(" | completas: ");
Serial.print(vueltasCompletas);
Serial.print(" | parciales: ");
Serial.println(vueltasParciales);
}
}
}
void leer_joystick()
{
if (gameOver) return;
// Se lee el joystick en el eje X e Y, centrándolo con 512 (2^9).
int x = analogRead(JOYSTICK[0]) - 512;
int y = analogRead(JOYSTICK[1]) - 512;
/*
Serial.print("X: ");
Serial.print(x);
Serial.print(" Y: ");
Serial.println(y);
*/
// Si el joystick está cerca del centro, no se cuenta como giro.
if (abs(x) < 50 && abs(y) < 50) return;
// Calcular ángulo en grados (0° a 360°).
// En caso de valor negativo, se añade 360° (valores positivos).
angulo_actual = atan2(y, x) * 180.0 / PI;
if (angulo_actual < 0) angulo_actual += 360;
/*
Serial.print("Ángulo actual: ");
Serial.println(angulo_actual);
*/
// Detección de paso por cero → una vuelta
// Si antes estaba cerca de 350° y ahora está cerca de 10°, pasó por 0
if (angulo_anterior > 300 && angulo_actual < 60)
{
vueltas++; // Se incrementan vueltas completas
vueltasCompletas = vueltas / VUELTAS_POR_LUZ;
vueltasParciales = vueltas % VUELTAS_POR_LUZ;
/*
Serial.print("Vueltas: ");
Serial.println(vueltas);
Serial.print("Vueltas completas: ");
Serial.println(vueltasCompletas);
Serial.print("Vueltas parciales: ");
Serial.println(vueltasParciales);
*/
}
// Guardar para la próxima comparación
angulo_anterior = angulo_actual;
}
// Actualizar los LEDS_PROGRESO.
void actualizar_progreso()
{
if (gameOver)
{
// NO actualizar nada, mantener lo último mostrado
return;
}
// PWM suave, se repite rápido sin bloquear.
if (millis() - lastPWM >= 5)
{
lastPWM = millis();
pwmStep = (pwmStep + 1) % VUELTAS_POR_LUZ;
}
// Encender por completo los LEDs terminados.
for(int i = 0; i < vueltasCompletas && i < 8; i++)
setProgreso(i, true);
// Brillo progresivo del LED en curso.
if (vueltasCompletas < 8)
{
bool encender = (pwmStep < vueltasParciales);
setProgreso(vueltasCompletas, encender);
}
// Apagar los que todavía no deberían encenderse
for(int i = vueltasCompletas + 1; i < 8; i++)
{
setProgreso(i, false);
}
Serial.print("Estado de progreso: ");
Serial.println(estadoProgreso, BIN);
actualizarShiftRegisters();
// COMPROBAR SI SE HA COMPLETADO EL PROGRESO TOTAL PARA VICTORIA
if (vueltasCompletas >= 8 && !gameOver)
{
gameOver = true;
victoria = true;
vueltasPrevias = vueltas;
// Congelar tiempo
//analogWrite(LED_TIEMPO, 255);
// Congelar semáforo
estadoMovimiento = true;
enParpadeo = false;
tCambio = ULONG_MAX;
setMovimiento(true, false);
// No permitir más movimiento
puedeMover = false;
play_sound(VICTORY_SOUND);
return;
}
}
// Actualizar los LEDS_STRIKES cuando sea necesario.
void gestionar_strikes()
{
unsigned long ahora = millis();
// Si el juego terminó, NO revisar strikes ni castigos.
if (gameOver)
{
return;
}
// Detectar si se intentó mover estando en ROJO
if (!estadoMovimiento && vueltas > vueltasPrevias)
{
// Solo permitir un strike cada cierto tiempo
if (ahora - tiempoStrike >= COOLDOWN_STRIKE)
{
Serial.println("¡STRIKE!");
tiempoStrike = ahora;
strikesCount++;
// Encender LEDs de strikes
if (strikesCount >= 1) setStrike(0, true);
if (strikesCount >= 2) setStrike(1, true);
// if (strikesCount >= 3) setStrike(LED_STRIKE[2], true);
// Si llegó al máximo, castigo (GAME OVER).
if (strikesCount >= MAX_STRIKES)
{
Serial.println("¡GAME OVER POR STRIKE!");
gameOver = true;
victoria = false;
// Congelar semáforo
estadoMovimiento = false;
enParpadeo = false;
tCambio = ULONG_MAX;
setMovimiento(false, true);
// Congelar progreso
puedeMover = false;
// Mostrar strikes finales
setStrike(0, true);
setStrike(1, true);
vueltasPrevias = vueltas;
play_sound(FAILURE_SOUND);
return;
}
}
}
vueltasPrevias = vueltas; // Actualizar referencia
}
// Actualizar el display del tiempo.
void actualizar_tiempo()
{
unsigned long ahora = millis();
unsigned long transcurrido = ahora - tiempoInicio;
// Si el juego ya terminó, no hacer nada más.
if (gameOver)
{
return;
}
// Tiempo agotado resulta en GAME OVER.
if (transcurrido >= TIEMPO_TOTAL)
{
// Congelar progreso: no borrar LEDs y evitar que se actualicen.
pwmStep = vueltasParciales; // Mantiene el brillo final parcial
gameOver = true;
strikesCount = MAX_STRIKES;
vueltasPrevias = vueltas;
// Encender los 3 LEDs de STRIKE.
setStrike(0, true);
setStrike(1, true);
// setStrike(LED_STRIKE[2], true);
// Poner el semáforo en ROJO.
estadoMovimiento = false;
enParpadeo = false;
setMovimiento(false, true);
// Resetear progreso.
/*
vueltas = 0;
vueltasPrevias = 0;
vueltasCompletas = 0;
vueltasParciales = 0;
pwmStep = 0;
estadoProgreso = 0;
actualizarShiftRegisters();
*/
// Apagar LED de tiempo.
analogWrite(LED_TIEMPO, 0);
play_sound(FAILURE_SOUND);
return;
}
// Si aún hay tiempo, calcular brillo proporcional.
float progreso = 1.0 - (float)transcurrido / (float)TIEMPO_TOTAL;
int brillo = progreso * 255;
brillo = constrain(brillo, 0, 255);
analogWrite(LED_TIEMPO, brillo);
}
// Funciones para resetear el juego.
void detectar_reset()
{
bool pulsado = (digitalRead(PIN_RESET) == LOW);
unsigned long ahora = millis();
if (pulsado && !holdEnProgreso)
{
// Empezó a mantener pulsado.
holdEnProgreso = true;
tiempoInicioHold = ahora;
}
else if (!pulsado)
{
// Si lo suelta antes de llegar a los 3s, cancelar RESET.
holdEnProgreso = false;
}
else if (pulsado && holdEnProgreso)
{
// Está manteniendo el botón, comprobar tiempo.
if (ahora - tiempoInicioHold >= RESET_HOLD_TIME)
{
// Reset REAL aquí.
resetearJuego();
holdEnProgreso = false; // Evita múltiples resets.
}
}
}
void resetearJuego()
{
Serial.println("¡RESETEO EN PROGRESO!");
stop_sound();
// Reset estados principales.
gameOver = false;
enParpadeo = false;
// Reset semáforo (empieza en verde).
estadoMovimiento = true;
setMovimiento(true, false);
// Reset tiempos.
tiempoInicio = millis();
tCambio = tiempoInicio + random(MIN_TIEMPO_CAMBIO, MAX_TIEMPO_CAMBIO);
// Reset strikes.
strikesCount = 0;
setStrike(0, false);
setStrike(1, false);
// Reset progreso.
vueltas = 0;
vueltasPrevias = 0;
vueltasCompletas = 0;
vueltasParciales = 0;
pwmStep = 0;
estadoProgreso = 0;
actualizarShiftRegisters();
analogWrite(LED_TIEMPO, 255); // Tiempo al máximo.
// Permitir movimiento
puedeMover = true;
Serial.println("¡RESETEO COMPLETADO!");
}
// Funciones para SONIDO.
void play_sound(int id)
{
if (id == FAILURE_SOUND)
{
stop_sound(); // Asegura que no se solapen sonidos.
reproduciendoDerrota = true;
f_noteIndex = 0;
f_noteStart = millis();
return;
}
if (id == VICTORY_SOUND)
{
stop_sound(); // Asegura que no se solapen sonidos.
reproduciendoVictoria = true;
v_notaIndex = 0;
v_notaInicio = millis();
return;
}
}
void stop_sound()
{
noTone(PIN_ZUMBADOR);
reproduciendoVictoria = false;
reproduciendoDerrota = false;
}
void actualizar_sonido()
{
if (!reproduciendoVictoria && !reproduciendoDerrota) return;
if(reproduciendoVictoria)
{
unsigned long ahora = millis();
// Si ha terminado la nota actual → pasar a la siguiente
if (ahora - v_notaInicio >= v_notaDuracion)
{
// Avanzar al siguiente par (nota + duración)
v_notaIndex += 2;
// Si se acabó la melodía → detener
if (v_notaIndex >= v_notes * 2)
{
stop_sound();
return;
}
int nota = v_melody[v_notaIndex];
int divider = v_melody[v_notaIndex + 1];
if (divider > 0)
v_notaDuracion = v_wholenote / divider;
else
{
v_notaDuracion = (v_wholenote / abs(divider)) * 1.5;
}
tone(PIN_ZUMBADOR, nota, v_notaDuracion * 0.9);
v_notaInicio = ahora;
}
}
else if(reproduciendoDerrota)
{
unsigned long ahora = millis();
// ¿terminó la nota actual?
if (ahora - f_noteStart >= f_noteDuration)
{
f_noteIndex += 2;
// Si ya no quedan notas → parar
if (f_noteIndex >= f_notes * 2)
{
stop_sound();
return;
}
int nota = f_melody[f_noteIndex];
int divider = f_melody[f_noteIndex + 1];
if (divider > 0)
f_noteDuration = f_wholenote / divider;
else
f_noteDuration = (f_wholenote / abs(divider)) * 1.5;
tone(PIN_ZUMBADOR, nota, f_noteDuration * 0.9);
f_noteStart = ahora;
}
}
}
// Inicializar de PINES (mayor limpieza de SETUP).
void init_pins()
{
// Multiplexores.
pinMode(PIN_DATA, OUTPUT);
pinMode(PIN_LATCH, OUTPUT);
pinMode(PIN_CLOCK, OUTPUT);
digitalWrite(PIN_DATA, LOW);
digitalWrite(PIN_LATCH, LOW);
digitalWrite(PIN_CLOCK, LOW);
estadoProgreso = 0;
actualizarShiftRegisters();
pinMode(LED_STRIKE[0], OUTPUT);
pinMode(LED_STRIKE[1], OUTPUT);
// pinMode(LED_STRIKE[2], OUTPUT);
pinMode(LED_MOV_VERDE, OUTPUT);
pinMode(LED_MOV_ROJO, OUTPUT);
pinMode(LED_TIEMPO, OUTPUT);
analogWrite(LED_TIEMPO, 255);
// Setup para JOYSTICK.
for(int i = 0; i < 2; i++)
{
pinMode(JOYSTICK[i], INPUT);
}
pinMode(PIN_RESET, INPUT_PULLUP);
pinMode(PIN_ZUMBADOR, OUTPUT);
}
Recursos de sonido para código del zumbador:
https://github.com/robsoncouto/arduino-songs/tree/master
Caja para hardware Impresión 3D (Realizado en Blender):

Enlace de descarga de ficheros para impresión 3D:
https://drive.google.com/file/d/1pbpP2CmvOY0ibrxBsVmDKQd9et9kSnkw/view?usp=drive_link