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 R36 €
3x LED Rojo 5mm0,51 €
7x LED Amarillo 5mm0,42 €
1x LED Blanco 5mm0,06 €
2x LED Verde 5mm0,12 €
1x Joystick Analogico 5 pines2,80 €
15x Resistores de100 Ohmios1,50 €
25x Jumpers Macho-Macho
5x Jumpers Macho-Hembra
3,50 €
1x Registro de Desplazamiento 74HC5950,46 €
1x Buzzer0,90 €
1x Full BreadBoard5,43 €
Impresion 3D 3 €
TOTAL24,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

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 *