Project: Pinball redesign - Problem with triggering bumpers

Hello everyone. I am in desperate need for help.
I am upgrading a pinball that i bought a few months ago. (a rokr miniature pinbal). this game features bumpers (using solenoids) and several other ramp sensors. However its scoring logic was very basic and that brought me the idea to upgrade it (add an lcd monitor to show score) add my own more advanced scoring logic add a sensor etc) . I also saw a video with someone who did it.
I know about coding (sql, vb) but have no idea of electronics .

so far i have made huge progress, but i cannot make my bumpers work . and without bumpers / no pinball.

I will first explain how the bumpers worked (in the original game) . i want to use them the same way. The bumpers are 3 solenoids placed on the pinball floor.

To activate them , there is a conductive sticker that surrounds the 3 solenoids. this sticker is connected constantly to 5v (there was no GND connection)
On the other hand, the solenoids had a wire connected to their metal body (appart from the wires for connecting the actual solenoid)

So when the ball touched the sticker and the body of the solenoid, electricity reached the body and from there to the original platform of the game and triggered the solenoid.

Now where i have stuck. I did exactly the same logic, and connected the solenoids through relays . all good. i have written a simple code to test them and they work perfect.

but when i apply this logic to my big code for my whole pinball game, the solenoids fire only occasionally which makes the game unplayable.

I have tried many things using AI help (with blocking code non blocking code, with analogue and digital read but with no good results.
i would be grateful if someone could help
i will try to paste codes in different post.
Thanks

This is the simple code with which the solenoids behave perfectly!

// Solenoid 1
#define RELAY1_PIN 4
#define TRIGGER1_PIN 10

// Solenoid 2
#define RELAY2_PIN 3
#define TRIGGER2_PIN 11

// Solenoid 3
#define RELAY3_PIN 2
#define TRIGGER3_PIN 12

// Solenoid pulse duration in ms
#define SOLENOID_PULSE_MS 50

void setup() {
  // Set relay pins as outputs
  pinMode(RELAY1_PIN, OUTPUT);
  pinMode(RELAY2_PIN, OUTPUT);
  pinMode(RELAY3_PIN, OUTPUT);

  // Initialize all relays OFF (assuming active HIGH relays)
  digitalWrite(RELAY1_PIN, LOW);
  digitalWrite(RELAY2_PIN, LOW);
  digitalWrite(RELAY3_PIN, LOW);

  // Set trigger pins as inputs (or change to INPUT_PULLUP if your triggers pull to GND)
  pinMode(TRIGGER1_PIN, INPUT);
  pinMode(TRIGGER2_PIN, INPUT);
  pinMode(TRIGGER3_PIN, INPUT);
}

void loop() {
  // Check solenoid 1 trigger
  if (digitalRead(TRIGGER1_PIN) == HIGH) {
    fireSolenoid(RELAY1_PIN);
    waitForTriggerRelease(TRIGGER1_PIN);
  }

  // Check solenoid 2 trigger
  if (digitalRead(TRIGGER2_PIN) == HIGH) {
    fireSolenoid(RELAY2_PIN);
    waitForTriggerRelease(TRIGGER2_PIN);
  }

  // Check solenoid 3 trigger
  if (digitalRead(TRIGGER3_PIN) == HIGH) {
    fireSolenoid(RELAY3_PIN);
    waitForTriggerRelease(TRIGGER3_PIN);
  }
}

// Helper to fire a solenoid
void fireSolenoid(int relayPin) {
  digitalWrite(relayPin, HIGH);               // Activate relay (solenoid ON)
  delay(SOLENOID_PULSE_MS);                   // Short pulse
  digitalWrite(relayPin, LOW);                // Deactivate relay (solenoid OFF)
}

// Helper to wait for trigger release
void waitForTriggerRelease(int triggerPin) {
  while (digitalRead(triggerPin) == HIGH) {
    delay(10);
  }
}

Solenoids are inductive elements and cause a lot of electrical noise when activated/deactivated.
If you don't use some kind of snubbers when activating them, they make a big "mess" in the Arduino.
To better understand and be able to help you, post a schematic of your electrical project, even if it is done freehand.

and this is my main game code without adding anything for the bumpers

 // 1 = bumper4
  // 2 = Ramp6
  // 3 = Ramp 4
  // 4 = hit target3
  // 5 = looseBall
  // 6 = objective
  // 7= ramp 11
  // 8 = intro
  // 9= ramp9
  // 13 = intro space!
  // 16 = triple kill
  // 18 = Relaunch
  // 19 = upgrade complete
  // 20 = highscore
  // 21 = personal highscore
  // 22 = end of game
  // 23 = double kill
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include <Encoder.h>
#include <FastLED.h>
#include <DFRobotDFPlayerMini.h>

#define DF_RX 16   // to module TX
#define DF_TX 17   // to module RX

// Rotary Encoder Pins
#define ENCODER_PIN_A 18
#define ENCODER_PIN_B 19
#define ENCODER_BUTTON_PIN 24



// Game Buttons
#define BUTTON_SUB_BALL_PIN 52

// Objective Leds
#define OBJ_RAMP3A_LED_PIN 34
#define OBJ_RAMP3B_LED_PIN 35
#define OBJ_RAMP2A_LED_PIN 36
#define OBJ_RAMP2B_LED_PIN 37
#define OBJ_RAMP1A_LED_PIN 38
#define OBJ_RAMP1B_LED_PIN 39
#define OBJ_TUNNELA_LED_PIN 40
#define OBJ_TUNNELB_LED_PIN 41
#define OBJ_HITTARGET_LED_PIN 42

// Ramp and tunnel leds.
#define RAMP1_LED_PIN 44
#define RAMP2_LED_PIN 45
#define RAMP3_LED_PIN 46
#define TUNNEL_LED_PIN 49
#define HITTARGET_LED_PIN 48


//other leds
#define SAVEBALL_LED_PIN 33

// hit target
#define HIT_TARGET_PIN A0
const int   hitTargetThreshold  = 150;
bool lastHitState = false;

// Ramps
#define RAMP3_SENSOR_PIN A2
const int ramp3Threshold = 100;   // your chosen threshold
bool lastRamp3Hit = false;


#define RAMP2_SENSOR_PIN A3
const int ramp2Threshold = 100;   // your chosen threshold
bool lastRamp2Hit = false;


#define RAMP1_SENSOR_PIN A4
const int ramp1Threshold = 100;   // your chosen threshold
bool lastRamp1Hit = false;

#define TUNNEL_SENSOR_PIN A5
const int tunnelThreshold = 100;   // your chosen threshold
bool lastTunnelHit = false;

//objective counters
unsigned int ramp3Counter = 0;
unsigned int ramp2Counter = 0;
unsigned int ramp1Counter = 0;
unsigned int tunnelCounter = 0;
unsigned int bumperCounter = 0;
unsigned int hitTargetCounter = 0;

//Scoring Factors
float Ramp1Factor = 1;
float Ramp2Factor = 1;
float Ramp3Factor = 1;
float TunnelFactor = 1;
float BumperFactor = 1;
float HitTargetFactor = 1;    

//Save ball tracker
unsigned int SaveBallScore = 0;

LiquidCrystal_I2C lcd(0x27, 16, 2); // LCD address and dimensions
Encoder encoder(ENCODER_PIN_A, ENCODER_PIN_B); // Using Encoder library

#define EEPROM_VERSION 0x42
#define PLAYER_START_ADDR 10
#define MAX_PLAYERS 70

#define STRIP_TOP_PIN     6
#define STRIP_TOP_NUM_LEDS    12
#define STRIP_TOP_BRIGHTNESS  100
#define STRIP_TOP_TYPE    WS2812B
#define STRIP_TOP_ORDER   GRB

CRGB stripTopLeds[STRIP_TOP_NUM_LEDS];

bool lastSubBallButtonState = LOW;

DFRobotDFPlayerMini dfplayer;

//combo variables
bool     comboActive    = false;           // are we in a combo?
uint8_t  comboHits      = 0;               // how many bonus hits so far (0–3)
unsigned long comboStart= 0;               // timestamp of last ramp in combo
const unsigned long comboWindow = 10000;   // 10 s window

//structuring a player

struct Player {
  char name[4]; // 3 chars + null
  uint32_t score;
};

int playerCount = 0;
int encoderPos = 0;
int lastEncoderPos = -999;
int hsStatus = 1;

const char allowedChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ";
const int numChars = sizeof(allowedChars) - 1; // exclude terminating '\0'

bool ramp2EffectActive = false;
unsigned long ramp2EffectStartTime = 0;

void setup() {
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();

  pinMode(ENCODER_BUTTON_PIN, INPUT_PULLUP);
  pinMode(BUTTON_SUB_BALL_PIN, INPUT);

  initEEPROM();
  playerCount = getPlayerCount();

    Serial2.begin(9600);
  if (!dfplayer.begin(Serial2)) {
    while (1);
  }

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Button to Start");
  lcd.setCursor(0, 1);
  lcd.print("HS");
  printScoreRightAligned(getHighScore(), 1);
  lastRamp2Hit   = (analogRead(RAMP2_SENSOR_PIN) > ramp2Threshold);
  lastRamp3Hit   = (analogRead(RAMP3_SENSOR_PIN) > ramp3Threshold);
  lastRamp1Hit   = (analogRead(RAMP1_SENSOR_PIN) > ramp1Threshold);
  lastTunnelHit   = (analogRead(TUNNEL_SENSOR_PIN) > tunnelThreshold);
  lastHitState = (analogRead(HIT_TARGET_PIN) >= hitTargetThreshold);

  pinMode(OBJ_RAMP3A_LED_PIN, OUTPUT);
  pinMode(OBJ_RAMP3B_LED_PIN, OUTPUT);
  pinMode(OBJ_RAMP2A_LED_PIN, OUTPUT);
  pinMode(OBJ_RAMP2B_LED_PIN, OUTPUT);
  pinMode(OBJ_RAMP1A_LED_PIN, OUTPUT);
  pinMode(OBJ_RAMP1B_LED_PIN, OUTPUT);
  pinMode(OBJ_TUNNELA_LED_PIN, OUTPUT);
  pinMode(OBJ_TUNNELB_LED_PIN, OUTPUT);
  pinMode(OBJ_HITTARGET_LED_PIN, OUTPUT);

  pinMode(RAMP1_LED_PIN, OUTPUT);
  pinMode(RAMP2_LED_PIN, OUTPUT);
  pinMode(RAMP3_LED_PIN, OUTPUT);
  pinMode(TUNNEL_LED_PIN, OUTPUT);
  pinMode(HITTARGET_LED_PIN, OUTPUT);

  pinMode(SAVEBALL_LED_PIN, OUTPUT);
 
  pinMode(HIT_TARGET_PIN, INPUT);

  FastLED.addLeds<STRIP_TOP_TYPE, STRIP_TOP_PIN, STRIP_TOP_ORDER>(stripTopLeds, STRIP_TOP_NUM_LEDS);
  FastLED.setBrightness(STRIP_TOP_BRIGHTNESS);
}
 

void loop() {

  if (digitalRead(ENCODER_BUTTON_PIN) == LOW) {
    delay(200);
    while (digitalRead(ENCODER_BUTTON_PIN) == LOW);
    dfplayer.volume(25);
    dfplayer.play(13);
    showPlayerMenu();


  }
}



void showPlayerMenu() {
    fill_solid(stripTopLeds, STRIP_TOP_NUM_LEDS, CHSV(96, 200, 255)); // light blue hue
  FastLED.show();
  encoder.write(0);
  encoderPos = 0;
  lastEncoderPos = -999;

  while (true) {
    encoderPos = encoder.read() / 4; // Reduce sensitivity

    if (encoderPos < 0) encoderPos = playerCount;
    if (encoderPos > playerCount) encoderPos = 0;

    if (lastEncoderPos != encoderPos) {
      lastEncoderPos = encoderPos;
      lcd.clear();

      if (encoderPos == 0) {
        lcd.setCursor(0, 0);
        lcd.print("> New Player");
        if (playerCount > 0) {
          Player p = readPlayer(0);
          lcd.setCursor(0, 1);
          lcd.print("  ");
          lcd.print(p.name);
          lcd.print(" - ");
          lcd.print(p.score);
        }
      } else {
        int firstIndex = encoderPos - 1;
        Player p1 = readPlayer(firstIndex);
        lcd.setCursor(0, 0);
        lcd.print("> ");
        lcd.print(p1.name);
        lcd.print(" - ");
        lcd.print(p1.score);

        lcd.setCursor(0, 1);
        if (firstIndex + 1 < playerCount) {
          Player p2 = readPlayer(firstIndex + 1);
          lcd.print("  ");
          lcd.print(p2.name);
          lcd.print(" - ");
          lcd.print(p2.score);
        } else {
          lcd.print(" ");
        }
      }
    }

    if (digitalRead(ENCODER_BUTTON_PIN) == LOW) {
      delay(200);
      while (digitalRead(ENCODER_BUTTON_PIN) == LOW);

      if (encoderPos == 0) {
        enterNewPlayer();
        playerCount = getPlayerCount();
        encoder.write(0);
        lastEncoderPos = -999;
      } else {
        Player p = readPlayer(encoderPos - 1);
        bool start = confirmPlayerSelection(p);
        if (start) {
          dfplayer.volume(25);
          dfplayer.play(7);
          playStripTopParade();
          startGame(p);
          break; // Return to main menu after game screen
        } else {
          showCancelMessageAndReturn();
          encoder.write(0);
          lastEncoderPos = -999;
        }
      }
    }
  }
}

void showCancelMessageAndReturn() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Cancel, back to");
  lcd.setCursor(0, 1);
  lcd.print("player selection");
  delay(1500);
}

bool confirmPlayerSelection(Player p) {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Start game as:");
  lcd.setCursor(0, 1);
  lcd.print(p.name);

  unsigned long pressStart = 0;
  bool buttonPressed = false;

  while (true) {
    int buttonState = digitalRead(ENCODER_BUTTON_PIN);

    if (buttonState == LOW && !buttonPressed) {
      buttonPressed = true;
      pressStart = millis();
    } else if (buttonState == LOW && buttonPressed) {
      unsigned long pressDuration = millis() - pressStart;
      if (pressDuration >= 1000) {
        showCancelMessageAndReturn();
        while (digitalRead(ENCODER_BUTTON_PIN) == LOW) {
          delay(10);
        }
        delay(100);
        return false;
      }
    } else if (buttonState == HIGH && buttonPressed) {
      return true;
    }
    delay(10);
  }
}

void startGame(Player p) {
  lcd.clear();

  int RemainingBalls = 3;
  int CurrentScore = 0;
   
 

  // Show player name left aligned on second row
  lcd.setCursor(0, 1);
  lcd.print(p.name);
  // Show personal best right aligned on second row
  printScoreRightAligned(p.score, 1);

  updateScreen(RemainingBalls, CurrentScore, p);


while (RemainingBalls > 0) {

  // expire combo if time’s up
  if (comboActive && millis() - comboStart > comboWindow) {
    comboActive = false;
    comboHits   = 0;
    digitalWrite(RAMP1_LED_PIN, LOW);
    digitalWrite(RAMP2_LED_PIN, LOW);
    digitalWrite(RAMP3_LED_PIN, LOW);
    digitalWrite(TUNNEL_LED_PIN, LOW);
  }

  updateRamp2Effect();  // Run animation logic continuously
  Ramp1Trigger(RemainingBalls, CurrentScore, p);
  Ramp2Trigger(RemainingBalls, CurrentScore, p);
  Ramp3Trigger(RemainingBalls, CurrentScore, p);
 
  TunnelTrigger(RemainingBalls, CurrentScore, p);
  HitTargetTrigger(RemainingBalls, CurrentScore, p);
  SubtractBall (RemainingBalls, CurrentScore, p);
  CheckObjectives (RemainingBalls, CurrentScore, p);
  CheckSaveBall (CurrentScore);
 }
}

void CheckSaveBall(int &CurrentScore) {
if (CurrentScore == SaveBallScore) {
  digitalWrite(SAVEBALL_LED_PIN, HIGH);
  } else {
    digitalWrite(SAVEBALL_LED_PIN, LOW);

  }
}

void updateScreen(int RemainingBalls, int CurrentScore, Player p) {

  // Prevent LCD overflow
  if (RemainingBalls > 5) RemainingBalls = 5;

  lcd.setCursor(0, 0);
  lcd.print("                "); // Clear first row
  lcd.setCursor(0, 0);

  for (int i = 0; i < RemainingBalls; i++) {
    lcd.print('O');
  }

  String scoreStr = String(CurrentScore);
  int len = scoreStr.length();
  int startPos = 16 - len;

  lcd.setCursor(startPos, 0);
  lcd.print(scoreStr);

    if (CurrentScore >= p.score && hsStatus == 1) {
      hsStatus = 2;

    }
    if (hsStatus == 2) {
     
      lcd.setCursor(0, 1);
      lcd.print("                ");
     
      lcd.setCursor(0, 1);
      lcd.print("HS");
      printScoreRightAligned(getHighScore(), 1);
      hsStatus = 3;
      dfplayer.volume(25);
      dfplayer.play(21);
    }
   
    else if (CurrentScore > getHighScore() && hsStatus == 3) {

      lcd.setCursor(0, 1);
      lcd.print("                ");
      lcd.setCursor(0, 1);
      lcd.print(" NEW HIGH SCORE ");
      hsStatus = 4;
      dfplayer.volume(25);
      dfplayer.play(20);
    }

}


void enterNewPlayer() {
  char name[4] = {'A', 'A', 'A', '\0'};
  int letterIndex = 0;
  encoder.write(0);
  encoderPos = 0;
  lastEncoderPos = -999;

  unsigned long pressStart = 0;
  bool buttonPressed = false;

  while (letterIndex < 3) {
    encoderPos = encoder.read() / 4;
    if (encoderPos < 0) encoderPos = numChars - 1;
    if (encoderPos >= numChars) encoderPos = 0;

    if (lastEncoderPos != encoderPos) {
      lastEncoderPos = encoderPos;
      name[letterIndex] = allowedChars[encoderPos];

      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Enter Name:");
      lcd.setCursor(0, 1);

      for (int i = 0; i < 3; i++) {
        if (i == letterIndex) lcd.print('[');
        else lcd.print(' ');

        lcd.print(name[i]);

        if (i == letterIndex) lcd.print(']');
        else lcd.print(' ');
      }
    }

    int buttonState = digitalRead(ENCODER_BUTTON_PIN);

    if (buttonState == LOW && !buttonPressed) {
      buttonPressed = true;
      pressStart = millis();
    } else if (buttonState == LOW && buttonPressed) {
      unsigned long pressDuration = millis() - pressStart;
      if (pressDuration >= 1000) {
        showCancelMessageAndReturn();
        while (digitalRead(ENCODER_BUTTON_PIN) == LOW) {
          delay(10);
        }
        delay(100);
        return;
      }
    } else if (buttonState == HIGH && buttonPressed) {
      buttonPressed = false;
      letterIndex++;
      encoder.write(0);
      lastEncoderPos = -999;
    }

    delay(10);
  }

  bool exists = false;
  for (int i = 0; i < playerCount; i++) {
    Player p = readPlayer(i);
    if (strcmp(p.name, name) == 0) {
      exists = true;
      break;
    }
  }

  if (!exists && playerCount < MAX_PLAYERS) {
    Player newPlayer;
    strcpy(newPlayer.name, name);
    newPlayer.score = 0;
    savePlayer(playerCount, newPlayer);
    playerCount++;
  } else {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Name Exists!");
    delay(2000);
  }
}

void initEEPROM() {
  if (EEPROM.read(0) != EEPROM_VERSION) {
    EEPROM.write(0, EEPROM_VERSION);
    EEPROM.write(1, 0); // player count
  }
  playerCount = getPlayerCount();  // ensure global stays in sync
}

int getPlayerCount() {
  return EEPROM.read(1);
}

void savePlayer(int index, Player p) {
  int addr = PLAYER_START_ADDR + index * sizeof(Player);
  EEPROM.put(addr, p);

  // Only update count if adding a new player
  if (index >= EEPROM.read(1)) {
    EEPROM.write(1, index + 1);
  }

  sortPlayersAlphabetically();
}

Player readPlayer(int index) {
  Player p;
  int addr = PLAYER_START_ADDR + index * sizeof(Player);
  EEPROM.get(addr, p);
  return p;
}

void sortPlayersAlphabetically() {
  if (playerCount < 2) return;

  for (int i = 0; i < playerCount - 1; i++) {
    for (int j = 0; j < playerCount - i - 1; j++) {
      Player p1 = readPlayer(j);
      Player p2 = readPlayer(j + 1);
      if (strcmp(p1.name, p2.name) > 0) {
        savePlayerRaw(j, p2);
        savePlayerRaw(j + 1, p1);
      }
    }
  }
}

void savePlayerRaw(int index, Player p) {
  int addr = PLAYER_START_ADDR + index * sizeof(Player);
  EEPROM.put(addr, p);
}

int findPlayerIndex(const char *name) {
  for (int i = 0; i < playerCount; i++) {
    Player p = readPlayer(i);
    if (strcmp(p.name, name) == 0) return i;
  }
  return -1;
}

uint32_t getHighScore() {
  uint32_t hs = 0;
  for (int i = 0; i < playerCount; i++) {
    Player p = readPlayer(i);
    if (p.score > hs) hs = p.score;
  }
  return hs;
}

void printScoreRightAligned(uint32_t score, int row) {
  String scoreStr = String(score);
  int len = scoreStr.length();
  int startPos = 16 - len;
  lcd.setCursor(startPos, row);
  lcd.print(scoreStr);
  for (int i = 0; i < startPos; i++) {
    lcd.print(" ");
  }
}

void playStripTopParade() {
  unsigned long start = millis();
  uint8_t hue = 0;

  while (millis() - start < 3000) {
    for (int i = 0; i < STRIP_TOP_NUM_LEDS; i++) {
      stripTopLeds[i] = CHSV(hue + (i * 10), 255, 255);
    }
    FastLED.show();
    hue += 5;
    delay(50);
  }

  fill_solid(stripTopLeds, STRIP_TOP_NUM_LEDS, CHSV(192, 255, 255)); // Purple hue
  FastLED.show();
}

void HitTargetTrigger(int &RemainingBalls, int &CurrentScore, Player &p) {

  analogRead(HIT_TARGET_PIN);
  // 1) read sensor and compare to threshold
  int v     = analogRead(HIT_TARGET_PIN);
  bool isHit = (v >= hitTargetThreshold);
 
  // 2) rising‐edge: not hit → hit
  if (isHit && !lastHitState) {
    hitTargetCounter++;
   

    const uint16_t pts = 30;
    CurrentScore += pts;
   
    // play your “hit target” sound (track 4)
    dfplayer.volume(25);
    dfplayer.play(4);
   
    // update the display
    updateScreen(RemainingBalls, CurrentScore, p);
   
    // (optional) flash an LED
    //digitalWrite(HITTARGET_LED_PIN, HIGH);
    //delay(50);
    //digitalWrite(HITTARGET_LED_PIN, LOW);
    //delay(50);
    //digitalWrite(HITTARGET_LED_PIN, HIGH);
    //delay(50);
    //digitalWrite(HITTARGET_LED_PIN, LOW);
    //delay(50);
    //digitalWrite(HITTARGET_LED_PIN, HIGH);
    //delay(50);
   // digitalWrite(HITTARGET_LED_PIN, LOW);
  }

  // 3) remember state for next loop
  lastHitState = isHit;
}

void Ramp1Trigger(int &RemainingBalls, int &CurrentScore, Player &p) {

  int raw = analogRead(RAMP1_SENSOR_PIN);

  // Determine if object is close enough
  bool isClose = (raw > ramp1Threshold);

  // Rising-edge: far → close
 
    if (isClose && !lastRamp1Hit) {
      uint16_t points = round(150 * Ramp1Factor);
      CurrentScore += points;
      dfplayer.volume(25);
      dfplayer.play(7);
      updateScreen(RemainingBalls, CurrentScore, p);
      ramp1Counter++;
      handleComboTimer(RemainingBalls, CurrentScore, p, points);
    }
lastRamp1Hit = isClose;
}

void Ramp2Trigger(int &RemainingBalls, int &CurrentScore, Player &p) {
  int raw = analogRead(RAMP2_SENSOR_PIN);

  // Determine if object is close enough
  bool isClose = (raw > ramp2Threshold);

  // Rising-edge: far → close
 
    if (isClose && !lastRamp2Hit) {
    uint16_t points = round(250 * Ramp2Factor);
    CurrentScore += points;
    dfplayer.volume(25);
    dfplayer.play(2);
    updateScreen(RemainingBalls, CurrentScore, p);
    ramp2Counter++;
    handleComboTimer(RemainingBalls, CurrentScore, p, points);
   
    // Trigger the Ramp2 light effect
    ramp2EffectActive = true;
    ramp2EffectStartTime = millis();
  }

  lastRamp2Hit = isClose;
}

void Ramp3Trigger(int &RemainingBalls, int &CurrentScore, Player &p) {

  int raw = analogRead(RAMP3_SENSOR_PIN);

  // Determine if object is close enough
  bool isClose = (raw > ramp3Threshold);

  // Rising-edge: far → close
 
    if (isClose && !lastRamp3Hit) {
      uint16_t points = round(120 * Ramp3Factor);
      CurrentScore += points;
      dfplayer.volume(25);
      dfplayer.play(9);
      updateScreen(RemainingBalls, CurrentScore, p);
      ramp3Counter++;
      handleComboTimer(RemainingBalls, CurrentScore, p, points);
    }
lastRamp3Hit = isClose;

}

void TunnelTrigger(int &RemainingBalls, int &CurrentScore, Player &p) {

  int raw = analogRead(TUNNEL_SENSOR_PIN);

  // Determine if object is close enough
  bool isClose = (raw > tunnelThreshold);

  // Rising-edge: far → close
 
    if (isClose && !lastTunnelHit) {
      uint16_t points = round(200 * TunnelFactor);
      CurrentScore += points;
      dfplayer.volume(25);
      dfplayer.play(3);
      updateScreen(RemainingBalls, CurrentScore, p);
      tunnelCounter++;
      handleComboTimer(RemainingBalls, CurrentScore, p, points);
    }
lastTunnelHit = isClose;
}

void CheckObjectives (int &RemainingBalls, int &CurrentScore, Player &p) {
    if (ramp3Counter == 5) {
    digitalWrite(OBJ_RAMP3A_LED_PIN, HIGH);
    CurrentScore += 500;
    ramp3Counter++;
    Ramp3Factor = 1.2 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);

  }

  if (ramp3Counter == 11) {
    digitalWrite(OBJ_RAMP3B_LED_PIN, HIGH);
    CurrentScore += 1200;
    ramp3Counter++;
    Ramp3Factor = 1.5 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);

  }  

  if (ramp2Counter == 3) {
    digitalWrite(OBJ_RAMP2A_LED_PIN, HIGH);
    CurrentScore += 1000;
    ramp2Counter++;
    Ramp2Factor = 1.2 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);

  }  

  if (ramp2Counter == 7) {
    digitalWrite(OBJ_RAMP2B_LED_PIN, HIGH);
    CurrentScore += 2500;
    ramp2Counter++;
    Ramp2Factor = 1.5 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);

  }  

  if (ramp1Counter == 4) {
    digitalWrite(OBJ_RAMP1A_LED_PIN, HIGH);
    CurrentScore += 700;
    ramp1Counter++;
    Ramp1Factor = 1.2 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);

  }  

  if (ramp1Counter == 9) {
    digitalWrite(OBJ_RAMP1B_LED_PIN, HIGH);
    CurrentScore += 1800;
    ramp1Counter++;
    Ramp1Factor = 1.5 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);

  }  

  if (tunnelCounter == 3) {
    digitalWrite(OBJ_TUNNELA_LED_PIN, HIGH);
    CurrentScore += 900;
    tunnelCounter++;
    TunnelFactor = 1.2 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);
  }  

  if (tunnelCounter == 7) {
    digitalWrite(OBJ_TUNNELB_LED_PIN, HIGH);
    CurrentScore += 2000;
    tunnelCounter++;
    TunnelFactor = 1.5 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);
  }  

    if (hitTargetCounter == 20) {
    digitalWrite(OBJ_HITTARGET_LED_PIN, HIGH);
    CurrentScore += 2000;
    HitTargetFactor = 2 ;
    hitTargetCounter++;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);
  }
}

void handleComboTimer(int RemainingBalls, int &CurrentScore, Player p, uint16_t &points) {
  unsigned long now = millis();

  if (!comboActive) {
    comboActive = true;
    comboHits   = 1;
    comboStart  = now;
    digitalWrite(RAMP1_LED_PIN, HIGH);
    digitalWrite(RAMP2_LED_PIN, HIGH);
    digitalWrite(RAMP3_LED_PIN, HIGH);
    digitalWrite(TUNNEL_LED_PIN, HIGH);
  }
  else {
    if (now - comboStart <= comboWindow && comboHits < 3) {
      comboHits++;

      // Play different sounds based on double vs. triple
      if (comboHits == 2) {
        CurrentScore += points;
        dfplayer.play(23);  // double-hit sound
        updateScreen(RemainingBalls, CurrentScore, p);

      }
     
      if (comboHits == 3) {
        CurrentScore += 2 * points;
        dfplayer.play(16);  // triple-hit sound
        updateScreen(RemainingBalls, CurrentScore, p);
        digitalWrite(RAMP1_LED_PIN, LOW);
        digitalWrite(RAMP2_LED_PIN, LOW);
        digitalWrite(RAMP3_LED_PIN, LOW);
        digitalWrite(TUNNEL_LED_PIN, LOW);
      }

      // Continue the timer only if you might get a next hit
      if (comboHits < 3) {
        comboStart = now;
      } else {
        comboActive = false;  // combo completed
      }
    }
    else {
      // expired or already maxed → start fresh
      comboHits   = 1;
      comboStart  = now;
      digitalWrite(RAMP1_LED_PIN, HIGH);
      digitalWrite(RAMP2_LED_PIN, HIGH);
      digitalWrite(RAMP3_LED_PIN, HIGH);
      digitalWrite(TUNNEL_LED_PIN, HIGH);
    }
  }
}

void SubtractBall (int &RemainingBalls, int &CurrentScore, Player &p) {

  bool subBallButtonState = digitalRead(BUTTON_SUB_BALL_PIN);



    // Detect rising edge for Subtract Ball button
    if (subBallButtonState == HIGH && lastSubBallButtonState == LOW) {
   
      if (CurrentScore == SaveBallScore) {
        dfplayer.volume(25);
        dfplayer.play(18);
        lcd.setCursor(0, 0);
        lcd.print("                ");
        lcd.setCursor(0, 0);
        lcd.print("  BALL SAVED!  ");
        delay(3000);
        updateScreen(RemainingBalls, CurrentScore, p);
        return;
    }
      if (RemainingBalls > 0) {
        dfplayer.volume(28);
        dfplayer.play(5);
        RemainingBalls--;
        SaveBallScore = CurrentScore;
        updateScreen(RemainingBalls, CurrentScore, p);
        delay(2000);
      }
    }

   
    lastSubBallButtonState = subBallButtonState;

    if (RemainingBalls == 0) {
      dfplayer.volume(25);
      dfplayer.play(22);
      // Game Over Screen
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("   GAME OVER!   ");
      lcd.setCursor(0, 1);
     

      if (CurrentScore > p.score) {
        lcd.print(" Score: ");
        lcd.print(CurrentScore);
        p.score = CurrentScore;
        savePlayer(findPlayerIndex(p.name), p);
        playStripTopParade();
      } else {
        lcd.print(" Score: ");
        lcd.print(CurrentScore);
        playStripTopParade();
      }
   

      // Wait for rotary encoder button press to restart
      while (digitalRead(ENCODER_BUTTON_PIN) == HIGH) {
        delay(50);
      }
      delay(200); // debounce

      while (digitalRead(ENCODER_BUTTON_PIN) == LOW) {
        delay(50);
      }
      delay(200);
      hsStatus = 1;
      digitalWrite(OBJ_RAMP3A_LED_PIN, LOW);
      digitalWrite(OBJ_RAMP3B_LED_PIN, LOW);
      digitalWrite(OBJ_RAMP2A_LED_PIN, LOW);
      digitalWrite(OBJ_RAMP2B_LED_PIN, LOW);
      digitalWrite(OBJ_RAMP1A_LED_PIN, LOW);
      digitalWrite(OBJ_RAMP1B_LED_PIN, LOW);
      digitalWrite(OBJ_TUNNELA_LED_PIN, LOW);
      digitalWrite(OBJ_TUNNELB_LED_PIN, LOW);

      digitalWrite(RAMP1_LED_PIN, LOW);
      digitalWrite(RAMP2_LED_PIN, LOW);
      digitalWrite(RAMP3_LED_PIN, LOW);
      digitalWrite(TUNNEL_LED_PIN, LOW);

      digitalWrite(SAVEBALL_LED_PIN, LOW);

      ramp3Counter = 0;
      ramp2Counter = 0;
      ramp1Counter = 0;
      tunnelCounter = 0;
      tunnelCounter = 0;
      bumperCounter = 0;
      hitTargetCounter = 0;
      SaveBallScore = 0;

      Ramp1Factor = 1;
      Ramp2Factor = 1;
      Ramp3Factor = 1;
      TunnelFactor = 1;
      BumperFactor = 1;
      HitTargetFactor = 1;    

      return; // Back to player menu
    }
   
    delay(50); // debounce & CPU relief
 
}

void updateRamp2Effect() {
  if (!ramp2EffectActive) return;

  unsigned long elapsed = millis() - ramp2EffectStartTime;
  const unsigned long duration = 1000; // 1 second animation

  if (elapsed > duration) {
    ramp2EffectActive = false;
    fill_solid(stripTopLeds, STRIP_TOP_NUM_LEDS, CHSV(192, 255, 255)); // Default color
    FastLED.show();
    return;
  }

  // Simple pulsing effect
  uint8_t brightness = 100 + 100 * sin(PI * elapsed / (float)duration);
  fill_solid(stripTopLeds, STRIP_TOP_NUM_LEDS, CHSV(32, 255, brightness)); // Orange pulse
  FastLED.show();
}

Instead of closing the solenoid circuit using the 'sticker', use a MOSFET then have the MOSFET drive the solenoid. Use lot's of noise suppresion on the solenoid.

Without studying your code...

You may need to use interrupts because otherwise you are only reading/polling the sensors for an instant every time it goes through the loop. The more stuff you do in the loop the less frequently it's read and you can miss something.

Any delays pause everything and writing to a display usually slows things down too.

1 Like

You might want to take a look at
several things at the same time
and make your code more like a finite state machine. It will help simplify the design.

The use of a button library will also simplify things and get rid of all our delay(200) statements which are a extremely long debounce time

2 Likes

Does this mean code is missing from the "main game code?"

I see a lot of delays 100 or 200 ms and delays in loops.
It's going to miss a lot of events, plain to see.

I don't understand why the bumpers don't run themselves, aren't simply wired to their own contacts.

1 Like

In the original

the solenoids were not driven by current going through the ball, rather they were triggered by a signal from that event.

Now you are using a relay to provide current to the solenoid using, one day, programmed logic.

AmIRight?

You may want to switch the relay out for a MOSFET at some point. With the proper circuit, it should outlast any relay and probably outlast your solenoid bumber.

I agree with @DVDdoug, this may be a legitimate use case for interrupts. All pains should first be taken to make you loop() run free - no blocking, no delays. And then it can be tested to see if you can reliably detect the ball event. If not, interrupts will def be the way to go.

a7

I am willing to make a small wager that is exactly how it works. Occam's razor.

Since I can't see your project, what exactly does the armature, the moving part, actually do when it moves?

Check out https://www.reddit.com/r/interestingasfuck/comments/1ci6u7f/i_was_laying_awake_one_night_asking_myself_how_do/

Does this look familiar?

Invideo

1 Like

@ektorakos

We need to see a schematic, you have been asked more than once for this and yet you constantly refuse to supply one. Is this because you don't know how to make one? If so the first thing you need to know is how to read one, try this video:-
How to read a schematic

It is very likely that you do not have any decoupling capacitors anywhere in your circuits and have no diodes across your coils to kill fly-back voltages.

For the decoupling see:-
Decoupling tutorial

I think Mr. ektorakos has already left the building.

Wow, over 800 lines long!
A fairly complicated program for a newbie who has never done Arduino programming before.

You might consider starting with a much simpler task first and work your way up.

It seems to also be a combined program, showing unused remnant code.

Serial is configured, but never used. Serial2 is configured and used to set DFPlayerMini volume to "25" about 40 of 49 times.

The code "works" with some fiddling with Serial2 and the "ramps"... hard to start the game.

click for sketch.ino
// https://forum.arduino.cc/t/project-pinball-redesign-problem-with-triggering-bumpers/1389678/3

// 1 = bumper4
// 2 = Ramp6
// 3 = Ramp 4
// 4 = hit target3
// 5 = looseBall
// 6 = objective
// 7= ramp 11
// 8 = intro
// 9= ramp9
// 13 = intro space!
// 16 = triple kill
// 18 = Relaunch
// 19 = upgrade complete
// 20 = highscore
// 21 = personal highscore
// 22 = end of game
// 23 = double kill

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include <Encoder.h>
#include <FastLED.h>
#include <DFRobotDFPlayerMini.h>

#define DF_RX 16   // to module TX
#define DF_TX 17   // to module RX

// Rotary Encoder Pins
#define ENCODER_PIN_A 18
#define ENCODER_PIN_B 19
#define ENCODER_BUTTON_PIN 24

// Game Buttons
#define BUTTON_SUB_BALL_PIN 52

// Objective Leds
#define OBJ_RAMP3A_LED_PIN 34
#define OBJ_RAMP3B_LED_PIN 35
#define OBJ_RAMP2A_LED_PIN 36
#define OBJ_RAMP2B_LED_PIN 37
#define OBJ_RAMP1A_LED_PIN 38
#define OBJ_RAMP1B_LED_PIN 39
#define OBJ_TUNNELA_LED_PIN 40
#define OBJ_TUNNELB_LED_PIN 41
#define OBJ_HITTARGET_LED_PIN 42

// Ramp and tunnel leds.
#define RAMP1_LED_PIN 44
#define RAMP2_LED_PIN 45
#define RAMP3_LED_PIN 46
#define TUNNEL_LED_PIN 49
#define HITTARGET_LED_PIN 48

//other leds
#define SAVEBALL_LED_PIN 33

// hit target
#define HIT_TARGET_PIN A0
const int   hitTargetThreshold  = 150;
bool lastHitState = false;

// Ramps
#define RAMP3_SENSOR_PIN A2
const int ramp3Threshold = 100;   // your chosen threshold
bool lastRamp3Hit = false;

#define RAMP2_SENSOR_PIN A3
const int ramp2Threshold = 100;   // your chosen threshold
bool lastRamp2Hit = false;

#define RAMP1_SENSOR_PIN A4
const int ramp1Threshold = 100;   // your chosen threshold
bool lastRamp1Hit = false;

#define TUNNEL_SENSOR_PIN A5
const int tunnelThreshold = 100;   // your chosen threshold
bool lastTunnelHit = false;

//objective counters
unsigned int ramp3Counter = 0;
unsigned int ramp2Counter = 0;
unsigned int ramp1Counter = 0;
unsigned int tunnelCounter = 0;
unsigned int bumperCounter = 0;
unsigned int hitTargetCounter = 0;

//Scoring Factors
float Ramp1Factor = 1;
float Ramp2Factor = 1;
float Ramp3Factor = 1;
float TunnelFactor = 1;
float BumperFactor = 1;
float HitTargetFactor = 1;

//Save ball tracker
unsigned int SaveBallScore = 0;

LiquidCrystal_I2C lcd(0x27, 16, 2); // LCD address and dimensions

Encoder encoder(ENCODER_PIN_A, ENCODER_PIN_B); // Using Encoder library

#define EEPROM_VERSION 0x42
#define PLAYER_START_ADDR 10
#define MAX_PLAYERS 70

#define STRIP_TOP_PIN           6
#define STRIP_TOP_NUM_LEDS     12
#define STRIP_TOP_BRIGHTNESS  255 // 100
#define STRIP_TOP_TYPE    WS2812B
#define STRIP_TOP_ORDER       GRB
CRGB stripTopLeds[STRIP_TOP_NUM_LEDS];

bool lastSubBallButtonState = LOW;

DFRobotDFPlayerMini dfplayer;

//combo variables
bool     comboActive    = false;           // are we in a combo?
uint8_t  comboHits      = 0;               // how many bonus hits so far (0–3)
unsigned long comboStart = 0;              // timestamp of last ramp in combo
const unsigned long comboWindow = 10000;   // 10 s window

//structuring a player
struct Player {
  char name[4]; // 3 chars + null
  uint32_t score;
};

int playerCount = 0;
int encoderPos = 0;
int lastEncoderPos = -999;
int hsStatus = 1;
const char allowedChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ";
const int numChars = sizeof(allowedChars) - 1; // exclude terminating '\0'
bool ramp2EffectActive = false;
unsigned long ramp2EffectStartTime = 0;

void setup() {
  Serial.begin(9600);
  Serial2.begin(9600);

  lcd.init();
  lcd.backlight();

  pinMode(ENCODER_BUTTON_PIN, INPUT_PULLUP);
  pinMode(BUTTON_SUB_BALL_PIN, INPUT);

  initEEPROM();
  playerCount = getPlayerCount();

  // if (!dfplayer.begin(Serial2)) {
  //   while (1);
  // }
  dfplayer.begin(Serial2);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Button to Start");
  lcd.setCursor(0, 1);
  lcd.print("HS");

  printScoreRightAligned(getHighScore(), 1);

  lastRamp2Hit   = (analogRead(RAMP2_SENSOR_PIN) > ramp2Threshold);
  lastRamp3Hit   = (analogRead(RAMP3_SENSOR_PIN) > ramp3Threshold);
  lastRamp1Hit   = (analogRead(RAMP1_SENSOR_PIN) > ramp1Threshold);
  lastTunnelHit  = (analogRead(TUNNEL_SENSOR_PIN) > tunnelThreshold);
  lastHitState = (analogRead(HIT_TARGET_PIN) >= hitTargetThreshold);

  pinMode(OBJ_RAMP3A_LED_PIN, OUTPUT);
  pinMode(OBJ_RAMP3B_LED_PIN, OUTPUT);
  pinMode(OBJ_RAMP2A_LED_PIN, OUTPUT);
  pinMode(OBJ_RAMP2B_LED_PIN, OUTPUT);
  pinMode(OBJ_RAMP1A_LED_PIN, OUTPUT);
  pinMode(OBJ_RAMP1B_LED_PIN, OUTPUT);
  pinMode(OBJ_TUNNELA_LED_PIN, OUTPUT);
  pinMode(OBJ_TUNNELB_LED_PIN, OUTPUT);
  pinMode(OBJ_HITTARGET_LED_PIN, OUTPUT);
  pinMode(RAMP1_LED_PIN, OUTPUT);
  pinMode(RAMP2_LED_PIN, OUTPUT);
  pinMode(RAMP3_LED_PIN, OUTPUT);
  pinMode(TUNNEL_LED_PIN, OUTPUT);
  pinMode(HITTARGET_LED_PIN, OUTPUT);
  pinMode(SAVEBALL_LED_PIN, OUTPUT);

  pinMode(HIT_TARGET_PIN, INPUT);

  FastLED.addLeds<STRIP_TOP_TYPE, STRIP_TOP_PIN, STRIP_TOP_ORDER>(stripTopLeds, STRIP_TOP_NUM_LEDS);
  FastLED.setBrightness(STRIP_TOP_BRIGHTNESS);
}

void loop() {
  if (digitalRead(ENCODER_BUTTON_PIN) == LOW) {
    delay(200);
    while (digitalRead(ENCODER_BUTTON_PIN) == LOW);
    dfplayer.volume(25);
    dfplayer.play(13);
    showPlayerMenu();
  }
}

void showPlayerMenu() {
  fill_solid(stripTopLeds, STRIP_TOP_NUM_LEDS, CHSV(96, 200, 255)); // light blue hue
  FastLED.show();
  encoder.write(0);
  encoderPos = 0;
  lastEncoderPos = -999;
  while (true) {
    encoderPos = encoder.read() / 4; // Reduce sensitivity
    if (encoderPos < 0) encoderPos = playerCount;
    if (encoderPos > playerCount) encoderPos = 0;
    if (lastEncoderPos != encoderPos) {
      lastEncoderPos = encoderPos;
      lcd.clear();
      if (encoderPos == 0) {
        lcd.setCursor(0, 0);
        lcd.print("> New Player");
        if (playerCount > 0) {
          Player p = readPlayer(0);
          lcd.setCursor(0, 1);
          lcd.print("  ");
          lcd.print(p.name);
          lcd.print(" - ");
          lcd.print(p.score);
        }
      } else {
        int firstIndex = encoderPos - 1;
        Player p1 = readPlayer(firstIndex);
        lcd.setCursor(0, 0);
        lcd.print("> ");
        lcd.print(p1.name);
        lcd.print(" - ");
        lcd.print(p1.score);
        lcd.setCursor(0, 1);
        if (firstIndex + 1 < playerCount) {
          Player p2 = readPlayer(firstIndex + 1);
          lcd.print("  ");
          lcd.print(p2.name);
          lcd.print(" - ");
          lcd.print(p2.score);
        } else {
          lcd.print(" ");
        }
      }
    }

    if (digitalRead(ENCODER_BUTTON_PIN) == LOW) {
      delay(200);
      while (digitalRead(ENCODER_BUTTON_PIN) == LOW);
      if (encoderPos == 0) {
        enterNewPlayer();
        playerCount = getPlayerCount();
        encoder.write(0);
        lastEncoderPos = -999;
      } else {
        Player p = readPlayer(encoderPos - 1);
        bool start = confirmPlayerSelection(p);
        if (start) {
          dfplayer.volume(25);
          dfplayer.play(7);
          playStripTopParade();
          startGame(p);
          break; // Return to main menu after game screen
        } else {
          showCancelMessageAndReturn();
          encoder.write(0);
          lastEncoderPos = -999;
        }
      }
    }
  }
}

void showCancelMessageAndReturn() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Cancel, back to");
  lcd.setCursor(0, 1);
  lcd.print("player selection");
  delay(1500);
}

bool confirmPlayerSelection(Player p) {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Start game as:");
  lcd.setCursor(0, 1);
  lcd.print(p.name);
  unsigned long pressStart = 0;
  bool buttonPressed = false;
  while (true) {
    int buttonState = digitalRead(ENCODER_BUTTON_PIN);
    if (buttonState == LOW && !buttonPressed) {
      buttonPressed = true;
      pressStart = millis();
    } else if (buttonState == LOW && buttonPressed) {
      unsigned long pressDuration = millis() - pressStart;
      if (pressDuration >= 1000) {
        showCancelMessageAndReturn();
        while (digitalRead(ENCODER_BUTTON_PIN) == LOW) {
          delay(10);
        }
        delay(100);
        return false;
      }
    } else if (buttonState == HIGH && buttonPressed) {
      return true;
    }
    delay(10);
  }
}

void startGame(Player p) {
  lcd.clear();
  int RemainingBalls = 3;
  int CurrentScore = 0;

  // Show player name left aligned on second row
  lcd.setCursor(0, 1);
  lcd.print(p.name);
  // Show personal best right aligned on second row
  printScoreRightAligned(p.score, 1);
  updateScreen(RemainingBalls, CurrentScore, p);

  while (RemainingBalls > 0) {
    // expire combo if time’s up
    if (comboActive && millis() - comboStart > comboWindow) {
      comboActive = false;
      comboHits   = 0;
      digitalWrite(RAMP1_LED_PIN, LOW);
      digitalWrite(RAMP2_LED_PIN, LOW);
      digitalWrite(RAMP3_LED_PIN, LOW);
      digitalWrite(TUNNEL_LED_PIN, LOW);
    }
    updateRamp2Effect();  // Run animation logic continuously
    Ramp1Trigger(RemainingBalls, CurrentScore, p);
    Ramp2Trigger(RemainingBalls, CurrentScore, p);
    Ramp3Trigger(RemainingBalls, CurrentScore, p);
    TunnelTrigger(RemainingBalls, CurrentScore, p);
    HitTargetTrigger(RemainingBalls, CurrentScore, p);
    SubtractBall (RemainingBalls, CurrentScore, p);
    CheckObjectives (RemainingBalls, CurrentScore, p);
    CheckSaveBall (CurrentScore);
  }
}

void CheckSaveBall(int &CurrentScore) {
  if (CurrentScore == SaveBallScore) {
    digitalWrite(SAVEBALL_LED_PIN, HIGH);
  } else {
    digitalWrite(SAVEBALL_LED_PIN, LOW);
  }
}

void updateScreen(int RemainingBalls, int CurrentScore, Player p) {
  // Prevent LCD overflow
  if (RemainingBalls > 5) RemainingBalls = 5;
  lcd.setCursor(0, 0);
  lcd.print("                "); // Clear first row
  lcd.setCursor(0, 0);
  for (int i = 0; i < RemainingBalls; i++) {
    lcd.print('O');
  }
  String scoreStr = String(CurrentScore);
  int len = scoreStr.length();
  int startPos = 16 - len;
  lcd.setCursor(startPos, 0);
  lcd.print(scoreStr);
  if (CurrentScore >= p.score && hsStatus == 1) {
    hsStatus = 2;
  }

  if (hsStatus == 2) {
    lcd.setCursor(0, 1);
    lcd.print("                ");
    lcd.setCursor(0, 1);
    lcd.print("HS");
    printScoreRightAligned(getHighScore(), 1);
    hsStatus = 3;
    dfplayer.volume(25);
    dfplayer.play(21);
  }
  else if (CurrentScore > getHighScore() && hsStatus == 3) {
    lcd.setCursor(0, 1);
    lcd.print("                ");
    lcd.setCursor(0, 1);
    lcd.print(" NEW HIGH SCORE ");
    hsStatus = 4;
    dfplayer.volume(25);
    dfplayer.play(20);
  }
}

void enterNewPlayer() {
  char name[4] = {'A', 'A', 'A', '\0'};
  int letterIndex = 0;
  encoder.write(0);
  encoderPos = 0;
  lastEncoderPos = -999;
  unsigned long pressStart = 0;
  bool buttonPressed = false;
  while (letterIndex < 3) {
    encoderPos = encoder.read() / 4;
    if (encoderPos < 0) encoderPos = numChars - 1;
    if (encoderPos >= numChars) encoderPos = 0;
    if (lastEncoderPos != encoderPos) {
      lastEncoderPos = encoderPos;
      name[letterIndex] = allowedChars[encoderPos];
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Enter Name:");
      lcd.setCursor(0, 1);
      for (int i = 0; i < 3; i++) {
        if (i == letterIndex) lcd.print('[');
        else lcd.print(' ');
        lcd.print(name[i]);
        if (i == letterIndex) lcd.print(']');
        else lcd.print(' ');
      }
    }
    int buttonState = digitalRead(ENCODER_BUTTON_PIN);
    if (buttonState == LOW && !buttonPressed) {
      buttonPressed = true;
      pressStart = millis();
    } else if (buttonState == LOW && buttonPressed) {
      unsigned long pressDuration = millis() - pressStart;
      if (pressDuration >= 1000) {
        showCancelMessageAndReturn();
        while (digitalRead(ENCODER_BUTTON_PIN) == LOW) {
          delay(10);
        }
        delay(100);
        return;
      }
    } else if (buttonState == HIGH && buttonPressed) {
      buttonPressed = false;
      letterIndex++;
      encoder.write(0);
      lastEncoderPos = -999;
    }
    delay(10);
  }
  bool exists = false;
  for (int i = 0; i < playerCount; i++) {
    Player p = readPlayer(i);
    if (strcmp(p.name, name) == 0) {
      exists = true;
      break;
    }
  }

  if (!exists && playerCount < MAX_PLAYERS) {
    Player newPlayer;
    strcpy(newPlayer.name, name);
    newPlayer.score = 0;
    savePlayer(playerCount, newPlayer);
    playerCount++;
  } else {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Name Exists!");
    delay(2000);
  }
}

void initEEPROM() {
  if (EEPROM.read(0) != EEPROM_VERSION) {
    EEPROM.write(0, EEPROM_VERSION);
    EEPROM.write(1, 0); // player count
  }
  playerCount = getPlayerCount();  // ensure global stays in sync
}
int getPlayerCount() {
  return EEPROM.read(1);
}

void savePlayer(int index, Player p) {
  int addr = PLAYER_START_ADDR + index * sizeof(Player);
  EEPROM.put(addr, p);
  // Only update count if adding a new player
  if (index >= EEPROM.read(1)) {
    EEPROM.write(1, index + 1);
  }
  sortPlayersAlphabetically();
}
Player readPlayer(int index) {
  Player p;
  int addr = PLAYER_START_ADDR + index * sizeof(Player);
  EEPROM.get(addr, p);
  return p;
}

void sortPlayersAlphabetically() {
  if (playerCount < 2) return;
  for (int i = 0; i < playerCount - 1; i++) {
    for (int j = 0; j < playerCount - i - 1; j++) {
      Player p1 = readPlayer(j);
      Player p2 = readPlayer(j + 1);
      if (strcmp(p1.name, p2.name) > 0) {
        savePlayerRaw(j, p2);
        savePlayerRaw(j + 1, p1);
      }
    }
  }
}

void savePlayerRaw(int index, Player p) {
  int addr = PLAYER_START_ADDR + index * sizeof(Player);
  EEPROM.put(addr, p);
}
int findPlayerIndex(const char *name) {
  for (int i = 0; i < playerCount; i++) {
    Player p = readPlayer(i);
    if (strcmp(p.name, name) == 0) return i;
  }
  return -1;
}
uint32_t getHighScore() {
  uint32_t hs = 0;
  for (int i = 0; i < playerCount; i++) {
    Player p = readPlayer(i);
    if (p.score > hs) hs = p.score;
  }
  return hs;
}

void printScoreRightAligned(uint32_t score, int row) {
  String scoreStr = String(score);
  int len = scoreStr.length();
  int startPos = 16 - len;
  lcd.setCursor(startPos, row);
  lcd.print(scoreStr);
  for (int i = 0; i < startPos; i++) {
    lcd.print(" ");
  }
}

void playStripTopParade() {
  unsigned long start = millis();
  uint8_t hue = 0;
  while (millis() - start < 3000) {
    for (int i = 0; i < STRIP_TOP_NUM_LEDS; i++) {
      stripTopLeds[i] = CHSV(hue + (i * 10), 255, 255);
    }
    FastLED.show();
    hue += 5;
    delay(50);
  }
  fill_solid(stripTopLeds, STRIP_TOP_NUM_LEDS, CHSV(192, 255, 255)); // Purple hue
  FastLED.show();
}

void HitTargetTrigger(int &RemainingBalls, int &CurrentScore, Player &p) {
  analogRead(HIT_TARGET_PIN);
  // 1) read sensor and compare to threshold
  int v     = analogRead(HIT_TARGET_PIN);
  bool isHit = (v >= hitTargetThreshold);
  // 2) rising‐edge: not hit → hit
  if (isHit && !lastHitState) {
    hitTargetCounter++;

    const uint16_t pts = 30;
    CurrentScore += pts;
    // play your “hit target” sound (track 4)
    dfplayer.volume(25);
    dfplayer.play(4);
    // update the display
    updateScreen(RemainingBalls, CurrentScore, p);
    // (optional) flash an LED
    //digitalWrite(HITTARGET_LED_PIN, HIGH);
    //delay(50);
    //digitalWrite(HITTARGET_LED_PIN, LOW);
    //delay(50);
    //digitalWrite(HITTARGET_LED_PIN, HIGH);
    //delay(50);
    //digitalWrite(HITTARGET_LED_PIN, LOW);
    //delay(50);
    //digitalWrite(HITTARGET_LED_PIN, HIGH);
    //delay(50);
    // digitalWrite(HITTARGET_LED_PIN, LOW);
  }
  // 3) remember state for next loop
  lastHitState = isHit;
}

void Ramp1Trigger(int &RemainingBalls, int &CurrentScore, Player &p) {
  int raw = analogRead(RAMP1_SENSOR_PIN);
  // Determine if object is close enough
  bool isClose = (raw > ramp1Threshold);
  // Rising-edge: far → close
  if (isClose && !lastRamp1Hit) {
    uint16_t points = round(150 * Ramp1Factor);
    CurrentScore += points;
    dfplayer.volume(25);
    dfplayer.play(7);
    updateScreen(RemainingBalls, CurrentScore, p);
    ramp1Counter++;
    handleComboTimer(RemainingBalls, CurrentScore, p, points);
  }
  lastRamp1Hit = isClose;
}

void Ramp2Trigger(int &RemainingBalls, int &CurrentScore, Player &p) {
  int raw = analogRead(RAMP2_SENSOR_PIN);
  // Determine if object is close enough
  bool isClose = (raw > ramp2Threshold);
  // Rising-edge: far → close
  if (isClose && !lastRamp2Hit) {
    uint16_t points = round(250 * Ramp2Factor);
    CurrentScore += points;
    dfplayer.volume(25);
    dfplayer.play(2);
    updateScreen(RemainingBalls, CurrentScore, p);
    ramp2Counter++;
    handleComboTimer(RemainingBalls, CurrentScore, p, points);
    // Trigger the Ramp2 light effect
    ramp2EffectActive = true;
    ramp2EffectStartTime = millis();
  }
  lastRamp2Hit = isClose;
}

void Ramp3Trigger(int &RemainingBalls, int &CurrentScore, Player &p) {
  int raw = analogRead(RAMP3_SENSOR_PIN);
  // Determine if object is close enough
  bool isClose = (raw > ramp3Threshold);
  // Rising-edge: far → close
  if (isClose && !lastRamp3Hit) {
    uint16_t points = round(120 * Ramp3Factor);
    CurrentScore += points;
    dfplayer.volume(25);
    dfplayer.play(9);
    updateScreen(RemainingBalls, CurrentScore, p);
    ramp3Counter++;
    handleComboTimer(RemainingBalls, CurrentScore, p, points);
  }
  lastRamp3Hit = isClose;
}

void TunnelTrigger(int &RemainingBalls, int &CurrentScore, Player &p) {
  int raw = analogRead(TUNNEL_SENSOR_PIN);
  // Determine if object is close enough
  bool isClose = (raw > tunnelThreshold);
  // Rising-edge: far → close
  if (isClose && !lastTunnelHit) {
    uint16_t points = round(200 * TunnelFactor);
    CurrentScore += points;
    dfplayer.volume(25);
    dfplayer.play(3);
    updateScreen(RemainingBalls, CurrentScore, p);
    tunnelCounter++;
    handleComboTimer(RemainingBalls, CurrentScore, p, points);
  }
  lastTunnelHit = isClose;
}

void CheckObjectives (int &RemainingBalls, int &CurrentScore, Player &p) {
  if (ramp3Counter == 5) {
    digitalWrite(OBJ_RAMP3A_LED_PIN, HIGH);
    CurrentScore += 500;
    ramp3Counter++;
    Ramp3Factor = 1.2 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);
  }

  if (ramp3Counter == 11) {
    digitalWrite(OBJ_RAMP3B_LED_PIN, HIGH);
    CurrentScore += 1200;
    ramp3Counter++;
    Ramp3Factor = 1.5 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);
  }

  if (ramp2Counter == 3) {
    digitalWrite(OBJ_RAMP2A_LED_PIN, HIGH);
    CurrentScore += 1000;
    ramp2Counter++;
    Ramp2Factor = 1.2 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);
  }

  if (ramp2Counter == 7) {
    digitalWrite(OBJ_RAMP2B_LED_PIN, HIGH);
    CurrentScore += 2500;
    ramp2Counter++;
    Ramp2Factor = 1.5 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);
  }

  if (ramp1Counter == 4) {
    digitalWrite(OBJ_RAMP1A_LED_PIN, HIGH);
    CurrentScore += 700;
    ramp1Counter++;
    Ramp1Factor = 1.2 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);
  }

  if (ramp1Counter == 9) {
    digitalWrite(OBJ_RAMP1B_LED_PIN, HIGH);
    CurrentScore += 1800;
    ramp1Counter++;
    Ramp1Factor = 1.5 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);
  }

  if (tunnelCounter == 3) {
    digitalWrite(OBJ_TUNNELA_LED_PIN, HIGH);
    CurrentScore += 900;
    tunnelCounter++;
    TunnelFactor = 1.2 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);
  }

  if (tunnelCounter == 7) {
    digitalWrite(OBJ_TUNNELB_LED_PIN, HIGH);
    CurrentScore += 2000;
    tunnelCounter++;
    TunnelFactor = 1.5 ;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);
  }

  if (hitTargetCounter == 20) {
    digitalWrite(OBJ_HITTARGET_LED_PIN, HIGH);
    CurrentScore += 2000;
    HitTargetFactor = 2 ;
    hitTargetCounter++;
    dfplayer.volume(25);
    dfplayer.play(19);
    updateScreen(RemainingBalls, CurrentScore, p);
  }
}

void handleComboTimer(int RemainingBalls, int &CurrentScore, Player p, uint16_t &points) {
  unsigned long now = millis();
  if (!comboActive) {
    comboActive = true;
    comboHits   = 1;
    comboStart  = now;
    digitalWrite(RAMP1_LED_PIN, HIGH);
    digitalWrite(RAMP2_LED_PIN, HIGH);
    digitalWrite(RAMP3_LED_PIN, HIGH);
    digitalWrite(TUNNEL_LED_PIN, HIGH);
  }
  else {
    if (now - comboStart <= comboWindow && comboHits < 3) {
      comboHits++;
      // Play different sounds based on double vs. triple
      if (comboHits == 2) {
        CurrentScore += points;
        dfplayer.volume(25);
        dfplayer.play(23);  // double-hit sound
        updateScreen(RemainingBalls, CurrentScore, p);
      }
      if (comboHits == 3) {
        CurrentScore += 2 * points;
        dfplayer.volume(25);
        dfplayer.play(16);  // triple-hit sound
        updateScreen(RemainingBalls, CurrentScore, p);
        digitalWrite(RAMP1_LED_PIN, LOW);
        digitalWrite(RAMP2_LED_PIN, LOW);
        digitalWrite(RAMP3_LED_PIN, LOW);
        digitalWrite(TUNNEL_LED_PIN, LOW);
      }
      // Continue the timer only if you might get a next hit
      if (comboHits < 3) {
        comboStart = now;
      } else {
        comboActive = false;  // combo completed
      }
    }
    else {
      // expired or already maxed → start fresh
      comboHits   = 1;
      comboStart  = now;
      digitalWrite(RAMP1_LED_PIN, HIGH);
      digitalWrite(RAMP2_LED_PIN, HIGH);
      digitalWrite(RAMP3_LED_PIN, HIGH);
      digitalWrite(TUNNEL_LED_PIN, HIGH);
    }
  }
}

void SubtractBall (int &RemainingBalls, int &CurrentScore, Player &p) {
  bool subBallButtonState = digitalRead(BUTTON_SUB_BALL_PIN);

  // Detect rising edge for Subtract Ball button
  if (subBallButtonState == HIGH && lastSubBallButtonState == LOW) {
    if (CurrentScore == SaveBallScore) {
      dfplayer.volume(25);
      dfplayer.play(18);
      lcd.setCursor(0, 0);
      lcd.print("                ");
      lcd.setCursor(0, 0);
      lcd.print("  BALL SAVED!  ");
      delay(3000);
      updateScreen(RemainingBalls, CurrentScore, p);
      return;
    }
    if (RemainingBalls > 0) {
      dfplayer.volume(28);
      dfplayer.play(5);
      RemainingBalls--;
      SaveBallScore = CurrentScore;
      updateScreen(RemainingBalls, CurrentScore, p);
      delay(2000);
    }
  }

  lastSubBallButtonState = subBallButtonState;
  if (RemainingBalls == 0) {
    dfplayer.volume(25);
    dfplayer.play(22);
    // Game Over Screen
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("   GAME OVER!   ");
    lcd.setCursor(0, 1);

    if (CurrentScore > p.score) {
      lcd.print(" Score: ");
      lcd.print(CurrentScore);
      p.score = CurrentScore;
      savePlayer(findPlayerIndex(p.name), p);
      playStripTopParade();
    } else {
      lcd.print(" Score: ");
      lcd.print(CurrentScore);
      playStripTopParade();
    }

    // Wait for rotary encoder button press to restart
    while (digitalRead(ENCODER_BUTTON_PIN) == HIGH) {
      delay(50);
    }
    delay(200); // debounce
    while (digitalRead(ENCODER_BUTTON_PIN) == LOW) {
      delay(50);
    }
    delay(200);
    hsStatus = 1;
    digitalWrite(OBJ_RAMP3A_LED_PIN, LOW);
    digitalWrite(OBJ_RAMP3B_LED_PIN, LOW);
    digitalWrite(OBJ_RAMP2A_LED_PIN, LOW);
    digitalWrite(OBJ_RAMP2B_LED_PIN, LOW);
    digitalWrite(OBJ_RAMP1A_LED_PIN, LOW);
    digitalWrite(OBJ_RAMP1B_LED_PIN, LOW);
    digitalWrite(OBJ_TUNNELA_LED_PIN, LOW);
    digitalWrite(OBJ_TUNNELB_LED_PIN, LOW);
    digitalWrite(RAMP1_LED_PIN, LOW);
    digitalWrite(RAMP2_LED_PIN, LOW);
    digitalWrite(RAMP3_LED_PIN, LOW);
    digitalWrite(TUNNEL_LED_PIN, LOW);
    digitalWrite(SAVEBALL_LED_PIN, LOW);
    ramp3Counter = 0;
    ramp2Counter = 0;
    ramp1Counter = 0;
    tunnelCounter = 0;
    tunnelCounter = 0;
    bumperCounter = 0;
    hitTargetCounter = 0;
    SaveBallScore = 0;
    Ramp1Factor = 1;
    Ramp2Factor = 1;
    Ramp3Factor = 1;
    TunnelFactor = 1;
    BumperFactor = 1;
    HitTargetFactor = 1;
    return; // Back to player menu
  }
  delay(50); // debounce & CPU relief
}

void updateRamp2Effect() {
  if (!ramp2EffectActive) return;
  unsigned long elapsed = millis() - ramp2EffectStartTime;
  const unsigned long duration = 1000; // 1 second animation
  if (elapsed > duration) {
    ramp2EffectActive = false;
    fill_solid(stripTopLeds, STRIP_TOP_NUM_LEDS, CHSV(192, 255, 255)); // Default color
    FastLED.show();
    return;
  }
  // Simple pulsing effect
  uint8_t brightness = 100 + 100 * sin(PI * elapsed / (float)duration);
  fill_solid(stripTopLeds, STRIP_TOP_NUM_LEDS, CHSV(32, 255, brightness)); // Orange pulse
  FastLED.show();
}
click for diagram.json
{
  "version": 1,
  "author": "foreignpigdog x",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-arduino-mega", "id": "mega", "top": 0.6, "left": -3.6, "attrs": {} },
    {
      "type": "wokwi-led",
      "id": "led1",
      "top": -51.6,
      "left": 272.6,
      "attrs": { "color": "magenta" }
    },
    {
      "type": "wokwi-ky-040",
      "id": "encoder1",
      "top": -121.8,
      "left": 346.9,
      "rotate": 90,
      "attrs": {}
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn1",
      "top": -96.1,
      "left": 435.5,
      "rotate": 90,
      "attrs": { "color": "green", "xray": "1" }
    },
    {
      "type": "wokwi-led-bar-graph",
      "id": "bargraph1",
      "top": 33.6,
      "left": 484.8,
      "attrs": { "color": "lime" }
    },
    {
      "type": "wokwi-led-bar-graph",
      "id": "bargraph2",
      "top": 139.2,
      "left": 484.8,
      "attrs": { "color": "lime" }
    },
    {
      "type": "wokwi-led",
      "id": "led2",
      "top": -22.8,
      "left": 426.2,
      "attrs": { "color": "purple" }
    },
    {
      "type": "wokwi-text",
      "id": "text1",
      "top": 28.8,
      "left": 528,
      "attrs": { "text": "RAMP  3A\n.\n.\nHIT" }
    },
    {
      "type": "wokwi-text",
      "id": "text2",
      "top": 134.4,
      "left": 528,
      "attrs": { "text": "RAMP1\nHIT" }
    },
    { "type": "wokwi-text", "id": "text3", "top": -48, "left": 307.2, "attrs": { "text": "TX" } },
    {
      "type": "wokwi-text",
      "id": "text4",
      "top": -19.2,
      "left": 460.8,
      "attrs": { "text": "SAVE" }
    },
    {
      "type": "wokwi-text",
      "id": "text5",
      "top": -86.4,
      "left": 499.2,
      "attrs": { "text": "SUB" }
    },
    {
      "type": "wokwi-text",
      "id": "text6",
      "top": 355.2,
      "left": 364.8,
      "attrs": { "text": "RAMP2" }
    },
    { "type": "wokwi-text", "id": "text7", "top": 220.8, "left": 9.6, "attrs": { "text": "HIT" } },
    {
      "type": "wokwi-text",
      "id": "text8",
      "top": 355.2,
      "left": 460.8,
      "attrs": { "text": "RAMP1" }
    },
    {
      "type": "wokwi-text",
      "id": "text9",
      "top": 355.2,
      "left": 268.8,
      "attrs": { "text": "RAMP3" }
    },
    {
      "type": "wokwi-text",
      "id": "text11",
      "top": 316.8,
      "left": -19.2,
      "attrs": { "text": "TUNNEL" }
    },
    {
      "type": "wokwi-neopixel-matrix",
      "id": "ring1",
      "top": -54.96,
      "left": 54.08,
      "attrs": { "pixleate": "1", "rows": "2", "cols": "6" }
    },
    {
      "type": "wokwi-lcd1602",
      "id": "lcd1",
      "top": -195.2,
      "left": 34.4,
      "attrs": { "pins": "i2c" }
    },
    {
      "type": "wokwi-slide-potentiometer",
      "id": "pot1",
      "top": 259.6,
      "left": 229.2,
      "rotate": 90,
      "attrs": { "travelLength": "10" }
    },
    {
      "type": "wokwi-slide-potentiometer",
      "id": "pot2",
      "top": 259.6,
      "left": 325.2,
      "rotate": 90,
      "attrs": { "travelLength": "10" }
    },
    {
      "type": "wokwi-slide-potentiometer",
      "id": "pot3",
      "top": 259.6,
      "left": 421.2,
      "rotate": 90,
      "attrs": { "travelLength": "10" }
    },
    {
      "type": "wokwi-slide-potentiometer",
      "id": "pot4",
      "top": 182.6,
      "left": 22.6,
      "rotate": 180,
      "attrs": { "travelLength": "10" }
    },
    {
      "type": "wokwi-slide-potentiometer",
      "id": "pot5",
      "top": 278.6,
      "left": 22.6,
      "rotate": 180,
      "attrs": { "travelLength": "10" }
    }
  ],
  "connections": [
    [ "mega:17", "led1:A", "green", [ "v0" ] ],
    [ "mega:16", "led1:C", "green", [ "v0" ] ],
    [ "mega:5V.2", "encoder1:VCC", "red", [ "v1", "h26.2" ] ],
    [ "mega:GND.5", "encoder1:GND", "black", [ "v0.95", "h16.6" ] ],
    [ "mega:19", "encoder1:CLK", "green", [ "v-19.2", "h97.9" ] ],
    [ "mega:18", "encoder1:DT", "green", [ "v-28.8", "h107.9" ] ],
    [ "mega:GND.5", "btn1:1.r", "black", [ "v0.95", "h26.2", "v19.2", "h86.4" ] ],
    [ "mega:52", "btn1:2.r", "green", [ "v1.1", "h26.6" ] ],
    [ "bargraph1:C10", "bargraph1:C9", "green", [ "h0" ] ],
    [ "bargraph1:C9", "bargraph1:C8", "green", [ "h0" ] ],
    [ "bargraph1:C8", "bargraph1:C7", "green", [ "h0" ] ],
    [ "bargraph1:C7", "bargraph1:C6", "green", [ "h0" ] ],
    [ "bargraph1:C6", "bargraph1:C5", "green", [ "h0" ] ],
    [ "bargraph1:C5", "bargraph1:C4", "green", [ "h0" ] ],
    [ "bargraph1:C4", "bargraph1:C3", "green", [ "h0" ] ],
    [ "bargraph1:C3", "bargraph1:C2", "green", [ "h0" ] ],
    [ "bargraph1:C2", "bargraph1:C1", "green", [ "h0" ] ],
    [ "mega:34", "bargraph1:A1", "green", [ "v1.2", "h45.8", "v-19.2" ] ],
    [ "mega:35", "bargraph1:A2", "green", [ "v1.2", "h35.8", "v-9.6" ] ],
    [ "mega:37", "bargraph1:A4", "green", [ "v1.3", "h35.8" ] ],
    [ "mega:36", "bargraph1:A3", "green", [ "v1.3", "h45.8", "v-9.6" ] ],
    [ "mega:39", "bargraph1:A6", "green", [ "v1.15", "h35.8", "v9.6" ] ],
    [ "mega:38", "bargraph1:A5", "green", [ "v1.15", "h45.8" ] ],
    [ "mega:41", "bargraph1:A8", "green", [ "v1.25", "h35.8", "v19.2" ] ],
    [ "mega:40", "bargraph1:A7", "green", [ "v1.25", "h45.8", "v19.2" ] ],
    [ "mega:42", "bargraph1:A9", "green", [ "v1.1", "h45.8", "v19.2" ] ],
    [ "bargraph2:C10", "bargraph2:C9", "green", [ "h0" ] ],
    [ "bargraph2:C9", "bargraph2:C8", "green", [ "h0" ] ],
    [ "bargraph2:C8", "bargraph2:C7", "green", [ "h0" ] ],
    [ "bargraph2:C7", "bargraph2:C6", "green", [ "h0" ] ],
    [ "bargraph2:C6", "bargraph2:C5", "green", [ "h0" ] ],
    [ "bargraph2:C5", "bargraph2:C4", "green", [ "h0" ] ],
    [ "bargraph2:C4", "bargraph2:C3", "green", [ "h0" ] ],
    [ "bargraph2:C3", "bargraph2:C2", "green", [ "h0" ] ],
    [ "bargraph2:C2", "bargraph2:C1", "green", [ "h0" ] ],
    [
      "mega:GND.5",
      "bargraph2:C10",
      "black",
      [ "v0.95", "h26.2", "v19.2", "h91.6", "v36.5", "h32.98" ]
    ],
    [ "mega:45", "bargraph2:A2", "green", [ "v1.2", "h35.8", "v9.6" ] ],
    [ "mega:44", "bargraph2:A1", "green", [ "v1.2", "h45.8", "v9.6" ] ],
    [ "mega:46", "bargraph2:A3", "green", [ "v1.05", "h45.8", "v19.2" ] ],
    [ "mega:49", "bargraph2:A4", "green", [ "v1.15", "h35.8", "v19.2" ] ],
    [ "mega:48", "bargraph2:A5", "green", [ "v1.15", "h45.8", "v28.8" ] ],
    [ "mega:GND.5", "led2:C", "black", [ "v0.95", "h26.2", "v19.2", "h47.6" ] ],
    [ "mega:33", "led2:A", "green", [ "v1.1", "h83.8" ] ],
    [ "mega:GND.1", "ring1:GND", "black", [ "v0" ] ],
    [ "ring1:VCC", "mega:5V", "red", [ "v163.2", "h36.1" ] ],
    [ "mega:6", "ring1:DIN", "green", [ "v-9.6", "h-61.99" ] ],
    [ "lcd1:VCC", "mega:5V", "red", [ "h-19.2", "v307.3", "h124.8" ] ],
    [ "lcd1:SDA", "mega:20", "green", [ "h-38.4", "v192.2", "h326.4" ] ],
    [ "lcd1:SCL", "mega:21", "green", [ "h-48", "v192.3", "h336" ] ],
    [ "pot1:SIG", "mega:A2", "green", [ "v-9.6", "h-66.4" ] ],
    [ "pot2:SIG", "mega:A3", "green", [ "v-19.2", "h-143.2" ] ],
    [ "pot3:SIG", "mega:A4", "green", [ "v-28.8", "h-200.8" ] ],
    [ "mega:5V", "pot1:VCC", "red", [ "v16.5", "h2.3" ] ],
    [ "mega:5V", "pot2:VCC", "red", [ "v16.5", "h223.1" ] ],
    [ "mega:5V", "pot3:VCC", "red", [ "v16.5", "h261.5" ] ],
    [ "mega:GND.3", "pot1:GND", "black", [ "v198.9", "h127.05" ] ],
    [ "mega:GND.3", "pot2:GND", "black", [ "v198.9", "h223.05" ] ],
    [ "mega:GND.3", "pot3:GND", "black", [ "v198.9", "h319.05" ] ],
    [ "mega:A0", "pot4:SIG", "green", [ "v0" ] ],
    [ "mega:A5", "pot5:SIG", "green", [ "v0" ] ],
    [ "mega:5V", "pot4:VCC", "red", [ "v0" ] ],
    [ "mega:5V", "pot5:VCC", "red", [ "v0" ] ],
    [ "mega:GND.3", "pot4:GND", "black", [ "v102.9", "h-151.35" ] ],
    [ "mega:GND.3", "pot5:GND", "black", [ "v102.9", "h-160.95" ] ],
    [ "mega:GND.3", "lcd1:GND", "black", [ "v-50.7", "h-170.55", "v-297.6" ] ],
    [ "mega:24", "encoder1:SW", "green", [ "v0.95", "h55.4" ] ]
  ],
  "dependencies": {}
}

Hello everyone . Thanks for your replies! i decided to start over the programming part. I have understood that I need to create code that is fully non blocking so that things work independently and at the same time. I started with bumpers to make sure they work as intended and they seem to be working just fine. now i am adding the rest of components. so far so good. i am waiting for 5 beam sensors that will work on ramps and at the loose ball shaft . once these are mounted and working i will be confident that this project might actually work. When i am finished i will post photos and a video showing functionality of the pinball. Thanks again.

Hello guys. I have completed the project

You can check out the video to see the result

In the comment section of the video i describe changes and improvements

Thanks for helping me out

2 Likes