Combine multiple millis() to control relais and neopixel simultanious

Given


I should have said



I only noticed because changing the define did not... change the pin. It's just gonna use 20 no matter, it was misleading. :wink:

a7

I have posted the complete sketch. Please refer to the below link for the Arduino simulator.

Thank you! I have reviewed your solution, and below is some code and a link to it nuder simulation.

I increased the min and max times because I thought it would finally make the hammers drop, slowly one by one and so forth.

I also had to remove

//      timeDelay = 500000UL;

the default time delay setting, as it bumped off any new delay time you set earlier.

We finally saw how your RunGame() functions. It does not block, rather every time it is called, it scans all N number of timers to see which, if any, have expired, and drops a hammer if it has. It then reschedules that hammer index.

Sry, that was so strange a solution to the OP's problem that I c0uld not read it as anything but a failed attemtp to capture the processor in RunGame() for the duration of one. Game.

I credit a member of the Umbrella Academy for figuring that out - we hooked up some instruments and found RunGame() being called many times a second, performing the entire for loop every time, usually doing nothing N times.

Your code does not block. Neither does it do anything like what the OP required:

  • Drop N hammers in a random order with a random time between them

Rather you are dropping hammer X every so often, and all the others every so often. As a result, a given hammer might be dropped again before all the others have been dropped. As a result, there may be a very short elapsed time during which several hammers are dropped.

Different game entirely. Different kind of fun.

Which is why the real RunGame() needs but one timer. How long to wait before dropping the next hammer. And why it has a mechanism for randomizing N hammers and doling them all out until it finishes. Which is another thing your sketch does not ever do. Finich.

This

                           4116 dropping 7 delay 3246 to next

4884 dropping 4 delay 2520 to next
5488 dropping 2 delay 1449 to next
6031 dropping 1 delay 4802 to next
6325 dropping 6 delay 1832 to next
6438 dropping 0 delay 4869 to next
6604 dropping 5 delay 2173 to next
6938 dropping 2 delay 5739 to next

                           7362 dropping 7 delay 4089 to next

shows hammer 7 being dropped, and scheduling its next drop, so at 4116 milliseconds into the run it drops, then again at 4116 + 3246 = 7362 into the run. No sign of hammer at indices 3 or 8 in between. But plenty of hammers dropping.

See it multitasking here in the wokwi simulator.

/*
  Solution For:
  Topics:   Combine multiple millis() to control relais and neopixel simultanious
  Category: Programming Questions
  Link:     https://forum.arduino.cc/t/combine-multiple-millis-to-control-relais-and-neopixel-simultanious/1099091

  Sketch:   AuCP_ RandomPipes.ino
  Created:  11-Mar-2023
  MicroBeaut (μB)

  changed for changes 10-Mar-2023?! @alto777

  https://wokwi.com/projects/358876197860297729
 
*/


# include <Adafruit_NeoPixel.h>
# define PIN 8  //Neopixel pin with PWM
# define NUMPIXELS 12     // number of LEDs on strip
Adafruit_NeoPixel strip(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);  // Declare our NeoPixel strip object

class TimerOn {
  private:
    bool prevInput;
    bool state;
    unsigned long startTime;
    unsigned long timeDelay;

  public:
    TimerOn() {
      prevInput = false;
      state = false;
//      timeDelay = 500000UL;
    }
    // Convert milliseconds to microseconds
    void setTimeDelay(long msTimeDelay) {
      timeDelay = msTimeDelay * 1000UL;
    }

    // Timer ON Condition
    bool timerOn(bool input = true) {
      if (input) {
        if (!prevInput) {
          startTime = micros();
        } else if (!state) {
          unsigned long elapsedTime = micros() - startTime;
          if (elapsedTime >= timeDelay) {
            elapsedTime = timeDelay;
            state = true;
          }
        }
      } else {
        state = false;
      }
      prevInput = input;
      return state;
    }

    bool done() {
      return state;
    }
};

#define RANDOM_MIN 1000L   // Random Min x milliseconds
#define RANDOM_MAX 7000L  // Random Mzx y milliseconds

const uint8_t numberOfPipes = 8;      // Number of Pipes
TimerOn pipeTimerOns[numberOfPipes];  // Timer On objects

TimerOn myTO;

void setup() {
  Serial.begin(115200);
  Serial.println(" timer focus \n");

  randomSeed(analogRead(0));

  strip.begin();

// strip check - are we talking or what?
  if (1) {
    strip.setPixelColor(7, 0xffffff);
    strip.show();
    delay(777);
  }

  for (uint8_t index = 0; index < numberOfPipes; index++) {
    long newDelay = random(RANDOM_MIN, RANDOM_MAX);   //Initial Random Time n milliseconds
    pipeTimerOns[index].setTimeDelay(newDelay);       // Set delay time to object
  }
}

unsigned long now;

void loop() {
  now = millis();

  RunGame();
  rainbow();
}

void RunGame() {
  for (uint8_t index = 0; index < numberOfPipes; index++) {
    pipeTimerOns[index].timerOn(true);                  // True = Enable or Start, False = Disable or Stop or Reset
    if (pipeTimerOns[index].done()) {                   // Timer On Done?

      pipeTimerOns[index].timerOn(false);               // Reset Timer On
//      randomSeed(analogRead(0));                        // Use an unconnected pin for randomSeed()
      long newDelay = random(RANDOM_MIN, RANDOM_MAX);   // New Random Time n milliseconds
      pipeTimerOns[index].setTimeDelay(newDelay);       // Set delay time to object

      Serial.print(millis()); Serial.print(" dropping ");
      Serial.print(index); Serial.print(" delay ");
      Serial.print(newDelay); Serial.print(" to next");
      
      Serial.println("");
    }
  }
}

// modified from Adafruit example to make it a state machine
void rainbow()
{
  static uint16_t j = 0;
  static unsigned long lastUpdate;
  if (now - lastUpdate < 40)
    return;

  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, Wheel((i + j) & 255));
  }
  strip.show();

  j++;
  if (j >= 256) j = 0;

  lastUpdate = now;  // time for next change to the display
}

uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

The TimerOn object seems like a long way to go for implementing a basic if-now-millis() paradigm, but hey! objects are fun, and yours works.

I do think, however, that anyone using it should know how it works, and that anyone who knows how it works probably wouldn't use it.

Thnks again for posting a complete sketch that compiles and functions. Sorry again for not seeing the cleverness which is your version, never mind it solves a different problem.

a7

1 Like

@hamarn

Please do not change code and wokwis that you have posted here.

When you post a wokwi, lock that project and move on. Or at least move on, that is to say stop modifying it.

You could also post the code here, cheap and easy. That gives it the best chances of being meaningful to anyone who arrives here.

It is very odd to see code changing in front of my eyes!

Are you sticking with your game idea, or is it your plan to address that? Why did you jettison the Neopixel Ring? The OP came here specifically to get "two things at once", I added that so we can all "see" that happening. Fun!

Antway, new post new wokwi and sketches when you think it is moving slow enough. Please do not edit your upthread contributions!

I hope I have a version of your old code before I changed it.

I like the TimerOn object, the way it turns on / initialiases. It wasn't immediately obvious, and did baffle at first in the original demo.

a7

1 Like

I cleaned up the code a bit and put 3 NeoPixel rings in the code (maybe not in the most efficient way, but it works).

Below the code that I'm going to put into the game. I will start construction soon and put the project when it's finished on instructables. At that time, I will post a link here so you can see the end-result.

A lot of thanks from me and my kids!

Danny

//Sketch to control falling pipes game, including 3 buttons; easy (long time), hard (short time) and reset. Including some NeoPixel rings to generate some attraction.
//used examples;
//Random pipe order from https://forum.arduino.cc/t/shuffle-numbers-when-button-is-pressed/675503/7
//NeoPixel; https://forum.arduino.cc/t/replacing-delay-with-millis-in-rainbowcycle-function/634554/7
//with many thanks to the help from the arduino forum and especially to alto777

//variabelen nodig voor timers
unsigned long prevTimeT1 = millis();                  // Deze is voor de knop LED te laten knipperen. Voor iedere actie met een tijdsfactor moet deze regel herhaald worden
unsigned long now;                                    // Deze is voor de timer van de buizen vallen (current time)
int fadeStep = 0;                                     // state variable for fade function
int counter;

//variabelen voor random generators
long nextTime;  //delay value number
long randPipe;  //Pipenummer uit random generator

//array buildup for pipe randomizer
int array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
const int arraySize = sizeof array / sizeof array[0];
int remainingNumbers = 0;

int Level = 0;  //moeilijkheidsgraad

//Input pins
#define Btn_Easy 20
#define Btn_Hard 21
//Resetbutton on Resetpin

//Output to relais
#define Pipe_1 2
#define Pipe_2 3
#define Pipe_3 4
#define Pipe_4 5
#define Pipe_5 6
#define Pipe_6 7
#define Pipe_7 8
#define Pipe_8 9
#define Pipe_9 10
#define Pipe_10 11
#define Btn_LED_Easy 14
#define Btn_LED_Hard 15
#define Btn_LED_Reset 16
int ledStateBtn = LOW;  // ledState used to set the LED van drukknop
int ledStateRst = LOW;  // ledState used to set the LED van resetknop

//NeoPixel setup
#define PINforControl1 12  //Neopixel pin with PWM
#define PINforControl2 13  //Neopixel pin with PWM
#define PINforControl3 17  //Neopixel pin with PWM
#define NUMPIXELS1 12     // number of LEDs on strip
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip1(NUMPIXELS1, PINforControl1, NEO_GRB + NEO_KHZ800);  // Declare our NeoPixel strip1 object
Adafruit_NeoPixel strip2(NUMPIXELS1, PINforControl2, NEO_GRB + NEO_KHZ800);  // Declare our NeoPixel strip2 object
Adafruit_NeoPixel strip3(NUMPIXELS1, PINforControl3, NEO_GRB + NEO_KHZ800);  // Declare our NeoPixel strip3 object

int getRandomEntry() {
  if (remainingNumbers == 0)
    remainingNumbers = arraySize;  // Start over with a full array

  int index = random(remainingNumbers);  // 0 to remainingNumbers - 1
  int returnValue = array[index];

  if (index < remainingNumbers - 1)  // Swap the chosen number with the number at the end of the current array (unless the chosen entry is already at the end)
  {
    array[index] = array[remainingNumbers - 1];  // Move the last entry into the chosen index
    array[remainingNumbers - 1] = returnValue;   // Move the value from the chosen index into the last entry
  }

  remainingNumbers--;  // Shorten the list so the picked number is not picked again
  return returnValue;
}

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

  randomSeed(analogRead(A0));            

  //Neopixel startup;
  strip1.begin();  // INITIALIZE NeoPixel strip object (REQUIRED)
  strip2.begin();  // INITIALIZE NeoPixel strip object (REQUIRED)
  strip3.begin();  // INITIALIZE NeoPixel strip object (REQUIRED)
  wipe();
  //strip1.setBrightness(50);  // Set BRIGHTNESS to about 1/5 (max = 255)

  pinMode(LED_BUILTIN, OUTPUT);  //onboard LED
  pinMode(20, INPUT_PULLUP);
  pinMode(21, INPUT_PULLUP);
  pinMode(Pipe_1, OUTPUT);
  pinMode(Pipe_2, OUTPUT);
  pinMode(Pipe_3, OUTPUT);
  pinMode(Pipe_4, OUTPUT);
  pinMode(Pipe_5, OUTPUT);
  pinMode(Pipe_6, OUTPUT);
  pinMode(Pipe_7, OUTPUT);
  pinMode(Pipe_8, OUTPUT);
  pinMode(Pipe_9, OUTPUT);
  pinMode(Pipe_10, OUTPUT);
  
  //prepare for the game
  colorWipeDelay(strip1.Color(255, 50, 0), 100);  // Orange
  colorWipeDelay(strip2.Color(255, 50, 0), 100);  // Orange
  colorWipeDelay(strip3.Color(255, 50, 0), 100);  // Orange
  AllMagnetsOn();                                //Alle magneten inschakelen
  colorWipeDelay(strip1.Color(0, 255, 0), 100);  // Green
  colorWipeDelay(strip2.Color(0, 255, 0), 100);  // Green
  colorWipeDelay(strip3.Color(0, 255, 0), 100);  // Green
  delay(2000);
  colorWipeDelay(strip1.Color(0, 0, 0), 100);  // Off
  colorWipeDelay(strip2.Color(0, 0, 0), 100);  // Off
  colorWipeDelay(strip3.Color(0, 0, 0), 100);  // Off
  wipe();  // off

  int counter;                                      // Used to count how many pipes have dropped, when all pipes have dropped, game is finished.

  Serial.println("Ready for first run");
}

void loop() {

  now = millis();
  rainbow();  // Flowing rainbow cycle along the whole strip with NeoPixel; This blocks the Button read loop because there is a delay within the function
  
if (digitalRead(Btn_Easy) == LOW) {  //als knop "Gemakkelijk" wordt ingedrukt; set moeilijkheid naar gemakkelijk en start spel
    delay(50); //debounce button
    Level = 10;
    Serial.println("Button Easy is pressed");
    counter = 0;

    while (counter < arraySize)   // when counter = arraySize; exit loop
      { 
      BtnLedEasyPuls();
      rainbowCycle();
      RunGame(); 
      }

    AllMagnetsOn(); // game is finished, reset for the next run
    delay(100);
    colorWipeDelay(strip1.Color(0, 255, 0), 100);  // Neopixel Green ring to show that pipes can be put back in place
    colorWipeDelay(strip2.Color(0, 255, 0), 100);  // Neopixel Green ring to show that pipes can be put back in place
    colorWipeDelay(strip3.Color(0, 255, 0), 100);  // Neopixel Green ring to show that pipes can be put back in place
    delay(500);
    colorWipeDelay(strip1.Color(0, 0, 0), 100);    // NeoPixel Off
    colorWipeDelay(strip2.Color(0, 0, 0), 100);    // NeoPixel Off
    colorWipeDelay(strip3.Color(0, 0, 0), 100);    // NeoPixel Off
    
  }

  if (digitalRead(Btn_Hard) == LOW) {  //als knop "Moeilijk" wordt ingedrukt; set moeilijkheid naar Moeilijk en start spel //this code must yet be corrected based on the Btn_Easy code
    delay(50); //debounce button
    Level = 3;
    Serial.println("Button Hard is pressed");
    counter = 0;

    while (counter < arraySize) // when counter = arraySize; exit loop
    {
      BtnLedHardPuls();
      theaterChaseRainbow();
      RunGame();
    }

    AllMagnetsOn(); // game is finished, reset for the next run
    delay(100);
    colorWipeDelay(strip1.Color(0, 255, 0), 100);  // Neopixel Green ring to show that pipes can be put back in place
    colorWipeDelay(strip2.Color(0, 255, 0), 100);  // Neopixel Green ring to show that pipes can be put back in place
    colorWipeDelay(strip3.Color(0, 255, 0), 100);  // Neopixel Green ring to show that pipes can be put back in place
    delay(500);
    colorWipeDelay(strip1.Color(0, 0, 0), 100);    // NeoPixel Off
    colorWipeDelay(strip2.Color(0, 0, 0), 100);    // NeoPixel Off
    colorWipeDelay(strip3.Color(0, 0, 0), 100);    // NeoPixel Off
  }
}

void AllMagnetsOn() {  //insert a small delay to give relay unit some time to initialize all relays one at a time to prevent current peak.
  digitalWrite(Pipe_1, LOW);
  delay(50);
  digitalWrite(Pipe_2, LOW);
  delay(50);
  digitalWrite(Pipe_3, LOW);
  delay(50);
  digitalWrite(Pipe_4, LOW);
  delay(50);
  digitalWrite(Pipe_5, LOW);
  delay(50);
  digitalWrite(Pipe_6, LOW);
  delay(50);
  digitalWrite(Pipe_7, LOW);
  delay(50);
  digitalWrite(Pipe_8, LOW);
  delay(50);
  digitalWrite(Pipe_9, LOW);
  delay(50);
  digitalWrite(Pipe_10, LOW);
  delay(500);
}

void RunGame() {
  static unsigned long lastTime = 3000;
  static int nextTime = random(100, 1000);
  now = millis();
 
  if (now - lastTime < (nextTime*Level))
    return;

    // select and drop random
    randPipe = getRandomEntry();
    digitalWrite(randPipe + 2, HIGH);  // laat de honden los!
 
    Serial.print("Random delay value: ");
    Serial.println(nextTime * Level);
    Serial.print("Pipe nr: ");
    Serial.print(randPipe);
    Serial.print(", Pin nr: ");
    Serial.println(randPipe + 2);
    Serial.print("Pipe counter value; ");
    Serial.print(counter);
    Serial.print(", array size; ");
    Serial.println(arraySize);
    Serial.println();

  // schedule
  nextTime  = random(100, 1000) ;
  lastTime = now;
  counter++; //counts every time a pipe has dropped
}

void BtnLedEasyPuls() {
  unsigned long now = millis();

  if (now - prevTimeT1 > 200) {

    prevTimeT1 = now;
    // if the LED is off turn it on and vice-versa:
    if (ledStateBtn == LOW) {
      ledStateBtn = HIGH;
      ledStateRst = LOW;
    } else {
      ledStateBtn = LOW;
      ledStateRst = HIGH;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(Btn_LED_Easy, ledStateBtn);
    digitalWrite(Btn_LED_Reset, ledStateRst);
  }
}

void BtnLedHardPuls() {
  unsigned long now = millis();

  if (now - prevTimeT1 > 200) {

    prevTimeT1 = now;
    // if the LED is off turn it on and vice-versa:
    if (ledStateBtn == LOW) {
      ledStateBtn = HIGH;
      ledStateRst = LOW;
    } else {
      ledStateBtn = LOW;
      ledStateRst = HIGH;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(Btn_LED_Easy, ledStateBtn);
    digitalWrite(Btn_LED_Reset, ledStateRst);
  }
}

void rainbow() {  // modified from Adafruit example to make it a state machine
  static uint16_t j = 0;
  static unsigned long lastUpdate;
  if (now - lastUpdate < 15)
    return;
  for (int i = 0; i < strip1.numPixels(); i++) {
    strip1.setPixelColor(i, Wheel((i + j) & 255));
    strip2.setPixelColor(i, Wheel((i + j) & 255));
    strip3.setPixelColor(i, Wheel((i + j) & 255));
  }
  strip1.show();
  strip2.show();
  strip3.show();
  j++;
  if (j >= 256) j = 0;
  lastUpdate = now;  // time for next change to the display
}

void rainbowCycle() {  // modified from Adafruit example to make it a state machine
  static uint16_t j = 0;
  static unsigned long lastUpdate2;

  if (now - lastUpdate2 < 20)
    return;

  for (int i = 0; i < strip1.numPixels(); i++) { //moet dit ook nog worden opgebouwd voor strip 2 en 3?
    strip1.setPixelColor(i, Wheel(((i * 256 / strip1.numPixels()) + j) & 255));
    strip2.setPixelColor(i, Wheel(((i * 256 / strip2.numPixels()) + j) & 255));
    strip3.setPixelColor(i, Wheel(((i * 256 / strip3.numPixels()) + j) & 255));
  }
  strip1.show();
  strip2.show();
  strip3.show();
  j++;
  if (j >= 256 * 5) j = 0;
  lastUpdate2 = now;  // time for next change to the display
}

void theaterChaseRainbow() {  // modified from Adafruit example to make it a state machine
  static int j = 0, q = 0;
  static boolean on = true;
  static unsigned long lastUpdate3;

  if (now - lastUpdate3 < 30) 
    return;

  if (on) {
    for (int i = 0; i < strip1.numPixels(); i = i + 3) { //moet dit ook nog worden opgebouwd voor strip 2 en 3?
      strip1.setPixelColor(i + q, Wheel((i + j) % 255));  //turn every third pixel on
      strip2.setPixelColor(i + q, Wheel((i + j) % 255));  //turn every third pixel on
      strip3.setPixelColor(i + q, Wheel((i + j) % 255));  //turn every third pixel on
    }
  } else {
    for (int i = 0; i < strip1.numPixels(); i = i + 3) {
      strip1.setPixelColor(i + q, 0);  //turn every third pixel off
      strip2.setPixelColor(i + q, 0);  //turn every third pixel off
      strip3.setPixelColor(i + q, 0);  //turn every third pixel off
    }
  }
  on = !on;      // toggel pixelse on or off for next time
  strip1.show();  // display
  strip2.show();  // display
  strip3.show();  // display
  q++;           // update the q variable
  if (q >= 3) {  // if it overflows reset it and update the J variable
    q = 0;
    j++;
    if (j >= 256) j = 0;
  }
  lastUpdate3 = now;  // time for next change to the display
}

void colorWipeDelay(uint32_t c, uint8_t wait) {  // Fill the dots one after the other with a color
  for (uint16_t i = 0; i < strip1.numPixels(); i++) {
    strip1.setPixelColor(i, c);
    strip2.setPixelColor(i, c);
    strip3.setPixelColor(i, c);
    strip1.show();
    strip2.show();
    strip3.show();
    delay(wait);
  }
}

void wipe() {  // clear all LEDs
  for (int k = 0; k < strip1.numPixels(); k++) {
    strip1.setPixelColor(k, strip1.Color(0, 0, 0));
    strip2.setPixelColor(k, strip2.Color(0, 0, 0));
    strip3.setPixelColor(k, strip3.Color(0, 0, 0));
  }
}

uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return strip1.Color(255 - WheelPos * 3, 0, WheelPos * 3);
    return strip2.Color(255 - WheelPos * 3, 0, WheelPos * 3);
    return strip3.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return strip1.Color(0, WheelPos * 3, 255 - WheelPos * 3);
    return strip2.Color(0, WheelPos * 3, 255 - WheelPos * 3);
    return strip3.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip1.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  return strip2.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  return strip3.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

If you bother with making constants manifest

Then you should use them, not like this

But like this

  pinMode(Btn_Easy, INPUT_PULLUP);
  pinMode(Btn_Hard, INPUT_PULLUP);

The idea being that moving these buttons to alternate I/O pins we have one place to make one change.

Same with any number used all over the place. I only noticed when changing the constants at the top did not fix the problem - I was running the cod on a different board...

I look forward to seeing the results. Did you ever describe how this game works, how it is played, scored and is there a winner?

a7

There's some parts of your code that doesn't makes sense.

This part is redundant:

void wipe() {  // clear all LEDs
  for (int k = 0; k < strip1.numPixels(); k++) {
    strip1.setPixelColor(k, strip1.Color(0, 0, 0));
    strip2.setPixelColor(k, strip2.Color(0, 0, 0));
    strip3.setPixelColor(k, strip3.Color(0, 0, 0));
  }
}

Can be replaced by:

strip1.clear();
strip2.clear();
strip3.clear();

strip1.show();
strip2.show();
strip3.show();

I think that this function has some errors.

void BtnLedHardPuls() {
    digitalWrite(Btn_LED_Easy, ledStateBtn);
    digitalWrite(Btn_LED_Reset, ledStateRst);
  }

You are checking the status of hard button but is changing the state of easy LED.

It should be:

void BtnLedHardPuls() {
    digitalWrite(Btn_LED_Hard, ledStateBtn);
    digitalWrite(Btn_LED_Reset, ledStateRst);
  }

I don't know why you are changing the status of reset LED since reset button is NOT checked in any part of your code.

void BtnLedEasyPuls() {
    digitalWrite(Btn_LED_Easy, ledStateBtn);
    digitalWrite(Btn_LED_Reset, ledStateRst);
}

void BtnLedHardPuls() {
    digitalWrite(Btn_LED_Easy, ledStateEasy);
    digitalWrite(Btn_LED_Reset, ledStateRst);
}

I believe that your functions should be like this:

void BtnLedEasyPuls() {
    digitalWrite(Btn_LED_Easy, ledStateEasy);
    digitalWrite(Btn_LED_Hard, ledStateHard);
}

void BtnLedHardPuls() {
    digitalWrite(Btn_LED_Easy, ledStateEasy);
    digitalWrite(Btn_LED_Hard, ledStateHard);
}

Also I can't see on code above the part where the state of the magnets is changed while playing the game.

Moreover the buttons doesn't change the difficult of the game just change the LED status.

Here your code with some improvements.

#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <Bounce2.h>

#define CONTROL_BUTTONS 3
#define NUM_PIPES 10

#define INITIAL_LED_STATE LOW
#define INITIAL_PIPE_STATE LOW

Bounce2::Button button[CONTROL_BUTTONS] = {Bounce2::Button()};

struct INFOS
{
  const byte buttonPin;
  bool ledState;
  const byte ledPin;
};

INFOS info[CONTROL_BUTTONS] =
{
  {20, INITIAL_LED_STATE, 14},
  {21, INITIAL_LED_STATE, 15},
  {18, INITIAL_LED_STATE, 16},
};

const byte pipePins[NUM_PIPES] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11};

// NeoPixel setup
#define PINforControl1 12
#define PINforControl2 13
#define PINforControl3 17
#define NUMPIXELS1 16                                                       // number of LEDs on strip

Adafruit_NeoPixel strip1(NUMPIXELS1, PINforControl1, NEO_GRB + NEO_KHZ800); // Declare our NeoPixel strip1 object
Adafruit_NeoPixel strip2(NUMPIXELS1, PINforControl2, NEO_GRB + NEO_KHZ800); // Declare our NeoPixel strip2 object
Adafruit_NeoPixel strip3(NUMPIXELS1, PINforControl3, NEO_GRB + NEO_KHZ800); // Declare our NeoPixel strip3 object

unsigned long prevTimeT1 = millis();                                        // Deze is voor de knop LED te laten knipperen. Voor iedere actie met een tijdsfactor moet deze regel herhaald worden                                                   // Deze is voor de timer van de buizen vallen (current time)
int fadeStep = 0;                                                           // state variable for fade function
int counter;
byte mode = 0;


// variabelen voor random generators
long nextTime;  // delay value number
long randPipe;  // Pipenummer uit random generator

// array buildup for pipe randomizer
int array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
const int arraySize = sizeof array / sizeof array[0];
int remainingNumbers = 0;

int Level = 0;  // moeilijkheidsgraad

int getRandomEntry();
uint32_t Wheel(byte WheelPos);
void colorWipeDelay(uint32_t c, uint8_t wait);
void rainbowCycle();
void rainbow();
void RunGame();
void AllMagnetsOff();
void AllMagnetsOn() ;
void theaterChaseRainbow();
void prepareGame();

int getRandomEntry()
{
  random(micros());

  if (remainingNumbers == 0)
  {
    remainingNumbers = arraySize;  // Start over with a full array

  }
  int index = random(remainingNumbers);  // 0 to remainingNumbers - 1
  int returnValue = array[index];

  if (index < remainingNumbers - 1)             // Swap the chosen number with the number at the end of the current array (unless the chosen entry is already at the end)
  {
    array[index] = array[remainingNumbers - 1]; // Move the last entry into the chosen index
    array[remainingNumbers - 1] = returnValue;  // Move the value from the chosen index into the last entry
  }

  remainingNumbers--;  // Shorten the list so the picked number is not picked again
  return returnValue;
}

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

  // Neopixel startup;
  strip1.begin();  // INITIALIZE NeoPixel strip object (REQUIRED)
  strip2.begin();  // INITIALIZE NeoPixel strip object (REQUIRED)
  strip3.begin();  // INITIALIZE NeoPixel strip object (REQUIRED)

  strip1.show();
  strip2.show();
  strip3.show();

  // strip1.setBrightness(50);  // Set BRIGHTNESS to about 1/5 (max = 255)

  for (byte i = 0; i < NUM_PIPES; i++)
  {
    pinMode(pipePins[i], OUTPUT);
    digitalWrite(pipePins[i], INITIAL_PIPE_STATE);
  }

  for (byte i = 0; i < CONTROL_BUTTONS; i++)
  {
    button[i].attach(info[i].buttonPin, INPUT_PULLUP);
    button[i].setPressedState(LOW);
    button[i].interval(5);

    pinMode(info[i].ledPin, OUTPUT);
    digitalWrite(info[i].ledPin, info[i].ledState);   
  }
  prepareGame();
  Serial.println("Ready for first run");
}

void loop()
{
  rainbow();               // Flowing rainbow cycle along the whole strip with NeoPixel; This blocks the Button read loop because there is a delay within the function

  for (byte i = 0; i < CONTROL_BUTTONS; i++)
  {
    button[i].update();

    if (button[0].pressed()) // als knop "Gemakkelijk" wordt ingedrukt; set moeilijkheid naar gemakkelijk en start spel
    {
      Level = 10;
      Serial.println("Button Easy is pressed");
      counter = 0;

      while (counter < arraySize) // when counter = arraySize; exit loop
      {
        button[0].update();

        if (button[0].pressed())
        {
          Serial.println("Easy mode selected");
          mode = 1;
          info[0].ledState = HIGH;
          info[1].ledState = LOW;
          digitalWrite(info[0].ledPin, info[0].ledState);
          digitalWrite(info[1].ledPin, info[1].ledState);
        }

        rainbowCycle();
        RunGame();
      }

      prepareGame();
    }
    else if (button[1].pressed())                   // als knop "Moeilijk" wordt ingedrukt; set moeilijkheid naar Moeilijk en start spel //this code must yet be corrected based on the Btn_Easy code
    {
      Level = 3;
      Serial.println("Button Hard is pressed");
      counter = 0;

      while (counter < arraySize) // when counter = arraySize; exit loop
      {
        button[1].update();

        if (button[1].pressed())
        {
          Serial.println("Hard mode selected");
          mode = 2;
          info[1].ledState = HIGH;
          info[0].ledState = LOW;

          digitalWrite(info[1].ledPin, info[1].ledState);
          digitalWrite(info[0].ledPin, info[0].ledState);
        }

        theaterChaseRainbow();
        RunGame();
      }

      prepareGame();
    }
    else if (button[2].pressed())                   // Reset button
    {
      Serial.println("Reset button pressed");
      info[2].ledState = !info[2].ledState;
      digitalWrite(info[2].ledPin, info[2].ledState);
    }
  }
}

void AllMagnetsOn()    // insert a small delay to give relay unit some time to initialize all relays one at a time to prevent current peak.
{
  for (byte i = 0; i < NUM_PIPES; i++)
  {
    digitalWrite(pipePins[i], !INITIAL_PIPE_STATE);
    delay(50);
  }
}

void AllMagnetsOff()
{
  for (byte i = 0; i < NUM_PIPES; i++)
  {
    digitalWrite(pipePins[i], INITIAL_PIPE_STATE);
  }
}

void RunGame()
{
  random(micros());

  static unsigned long lastTime = 3000;
  static unsigned int nextTime = random(100, 1000);

  if ((millis() - lastTime) < (nextTime * Level))
  {
    return;
  }

  // select and drop random
  randPipe = getRandomEntry();
  digitalWrite(randPipe + 2, HIGH);  // laat de honden los!

  Serial.print("Random delay value: ");
  Serial.println(nextTime * Level);
  Serial.print("Pipe nr: ");
  Serial.print(randPipe);
  Serial.print(", Pin nr: ");
  Serial.println(randPipe + 2);
  Serial.print("Pipe counter value; ");
  Serial.print(counter);
  Serial.print(", array size; ");
  Serial.println(arraySize);
  Serial.println();

  // schedule
  nextTime  = random(100, 1000);
  lastTime = millis();
  counter++; // counts every time a pipe has dropped
}

void prepareGame()
{
  // prepare for the game
  colorWipeDelay(strip1.Color(255, 50, 0), 100); // Orange
  colorWipeDelay(strip2.Color(255, 50, 0), 100); // Orange
  colorWipeDelay(strip3.Color(255, 50, 0), 100); // Orange
  AllMagnetsOn();                                // Alle magneten inschakelen
  colorWipeDelay(strip1.Color(0, 255, 0), 100);  // Green
  colorWipeDelay(strip2.Color(0, 255, 0), 100);  // Green
  colorWipeDelay(strip3.Color(0, 255, 0), 100);  // Green
  delay(2000);
  colorWipeDelay(strip1.Color(0, 0, 0), 100);    // Off
  colorWipeDelay(strip2.Color(0, 0, 0), 100);    // Off
  colorWipeDelay(strip3.Color(0, 0, 0), 100);    // Off
}

void rainbow()    // modified from Adafruit example to make it a state machine
{
  static uint16_t j = 0;
  static unsigned long lastUpdate;
  if (millis() - lastUpdate < 15)
  {
    return;
  }
  for (unsigned int i = 0; i < strip1.numPixels(); i++)
  {
    strip1.setPixelColor(i, Wheel((i + j) & 255));
    strip2.setPixelColor(i, Wheel((i + j) & 255));
    strip3.setPixelColor(i, Wheel((i + j) & 255));
  }
  strip1.show();
  strip2.show();
  strip3.show();
  j++;
  if (j >= 256)
  {
    j = 0;
  }
  lastUpdate = millis();  // time for next change to the display
}

void rainbowCycle()    // modified from Adafruit example to make it a state machine
{
  static uint16_t j = 0;
  static unsigned long lastUpdate2;

  if (millis() - lastUpdate2 < 20)
  {
    return;
  }

  for (unsigned int i = 0; i < strip1.numPixels(); i++)   // moet dit ook nog worden opgebouwd voor strip 2 en 3?
  {
    strip1.setPixelColor(i, Wheel(((i * 256 / strip1.numPixels()) + j) & 255));
    strip2.setPixelColor(i, Wheel(((i * 256 / strip2.numPixels()) + j) & 255));
    strip3.setPixelColor(i, Wheel(((i * 256 / strip3.numPixels()) + j) & 255));
  }
  strip1.show();
  strip2.show();
  strip3.show();
  j++;
  if (j >= 256 * 5)
  {
    j = 0;
  }
  lastUpdate2 = millis();  // time for next change to the display
}

void theaterChaseRainbow()    // modified from Adafruit example to make it a state machine
{
  static int j = 0, q = 0;
  static boolean on = true;
  static unsigned long lastUpdate3;

  if (millis() - lastUpdate3 < 30)
  {
    return;
  }

  if (on)
  {
    for (unsigned int i = 0; i < strip1.numPixels(); i = i + 3)   // moet dit ook nog worden opgebouwd voor strip 2 en 3?
    {
      strip1.setPixelColor(i + q, Wheel((i + j) % 255)); // turn every third pixel on
      strip2.setPixelColor(i + q, Wheel((i + j) % 255)); // turn every third pixel on
      strip3.setPixelColor(i + q, Wheel((i + j) % 255)); // turn every third pixel on
    }
  }
  else
  {
    for (unsigned int i = 0; i < strip1.numPixels(); i = i + 3)
    {
      strip1.setPixelColor(i + q, 0);  // turn every third pixel off
      strip2.setPixelColor(i + q, 0);  // turn every third pixel off
      strip3.setPixelColor(i + q, 0);  // turn every third pixel off
    }
  }
  on = !on;      // toggel pixelse on or off for next time
  strip1.show(); // display
  strip2.show(); // display
  strip3.show(); // display
  q++;           // update the q variable
  if (q >= 3)    // if it overflows reset it and update the J variable
  {
    q = 0;
    j++;
    if (j >= 256)
    {
      j = 0;
    }
  }
  lastUpdate3 = millis();  // time for next change to the display
}

void colorWipeDelay(uint32_t c, uint8_t wait)    // Fill the dots one after the other with a color
{
  for (uint16_t i = 0; i < strip1.numPixels(); i++)
  {
    strip1.setPixelColor(i, c);
    strip2.setPixelColor(i, c);
    strip3.setPixelColor(i, c);
    strip1.show();
    strip2.show();
    strip3.show();
    delay(wait);
  }
}

uint32_t Wheel(byte WheelPos)
{
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85)
  {
    return strip1.Color(255 - WheelPos * 3, 0, WheelPos * 3);
    return strip2.Color(255 - WheelPos * 3, 0, WheelPos * 3);
    return strip3.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170)
  {
    WheelPos -= 85;
    return strip1.Color(0, WheelPos * 3, 255 - WheelPos * 3);
    return strip2.Color(0, WheelPos * 3, 255 - WheelPos * 3);
    return strip3.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip1.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  return strip2.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  return strip3.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

There's so much job to be done.

But like this

  pinMode(Btn_Easy, INPUT_PULLUP);
  pinMode(Btn_Hard, INPUT_PULLUP);

I understand and changed this part of the code. I can imagine that you run into issues when using another board.

Did you ever describe how this game works, how it is played, scored and is there a winner?

My thought at this moment is to number the pipes (1 - 10) and sum the numbers from the pipes that are cought and put into a bucket. Pipes that touch the ground are discarded. I'm thinking of some variation in the pipe length (pipe with 10 points is shorter than the pipe for 1 point). Pipes will be PVC with something like a squashball in the bottom and a cap with a metal ring on top to attach to the magnets.
Maybe do 3 runs and add the score up. This also helps the kids to have some practice with math ;-).
Something like that, but maybe we change the rules when there is a more fun way of playing.

Hi FernandoGarcia,

Thank you for you in-depth check of the code.

This part is redundant:

void wipe() {  // clear all LEDs
  for (int k = 0; k < strip1.numPixels(); k++) {
    strip1.setPixelColor(k, strip1.Color(0, 0, 0));
    strip2.setPixelColor(k, strip2.Color(0, 0, 0));
    strip3.setPixelColor(k, strip3.Color(0, 0, 0));
  }
}

Can be replaced by:

strip1.clear();
strip2.clear();
strip3.clear();

strip1.show();
strip2.show();
strip3.show();

That will give another visual; wipe shuts off the LED's 1 at a time, clear just shuts all LED's off at the same time. I prefer the wipe function visually.

I think that this function has some errors.

void BtnLedHardPuls() {
    digitalWrite(Btn_LED_Easy, ledStateBtn);
    digitalWrite(Btn_LED_Reset, ledStateRst);
  }

You are correct. That's a copy-paste error. That one is fixed now. I didn't notice it yet because I have not yet installed all electronics.

I don't know why you are changing the status of reset LED since reset button is NOT checked in any part of your code.

void BtnLedEasyPuls() {
    digitalWrite(Btn_LED_Easy, ledStateBtn);
    digitalWrite(Btn_LED_Reset, ledStateRst);
}

void BtnLedHardPuls() {
    digitalWrite(Btn_LED_Easy, ledStateEasy);
    digitalWrite(Btn_LED_Reset, ledStateRst);
}

My idea here is that the LED's in the push buttons are flashing during the game; with the easy game, easy and reset flash alternating. With the hard game, the LED in the hard and reset will be flashing. This part of the code might change. Maybe I remove the "prepare for next game" from the code and led the Reset button LED flash after the game is finished. The reset button is connected to the reset pin, so the code ensures that everything is set for start. And kids love to push flashing buttons :wink:

Also I can't see on code above the part where the state of the magnets is changed while playing the game.
Moreover the buttons doesn't change the difficult of the game just change the LED status.

This part takes place at the loop function;

void loop() {

  now = millis();
  rainbow();  // Flowing rainbow cycle along the whole strip with NeoPixel; This blocks the Button read loop because there is a delay within the function
  
if (digitalRead(Btn_Easy) == LOW) {  //als knop "Gemakkelijk" wordt ingedrukt; set moeilijkheid naar gemakkelijk en start spel
    delay(50); //debounce button
    Level = 10;
    Serial.println("Button Easy is pressed");
    counter = 0;

    while (counter < arraySize)   // when counter = arraySize; exit loop
      { 
      BtnLedEasyPuls();
      rainbowCycle();
      RunGame(); 
      }

The first void in the wile loop (BtnLedEasyPuls) makes the LED from the pushbutton flash,
The 2nd void in the while loop (rainbowCycle) makes the NeoPixel give a light show
The 3rd void (RunGame) lets the magnets randomly shutt off.

The loop keeps running over these lines and checking a few conditions until all pipes have dropped.

I do like your simulator on wokwi. I wasn't aware that it is possible to build with other components. I think your last code is doing something else than I would like it to do, but I'm planning to also build a simulation of the game in there for testing.

I am a newbie to Arduino and may need time to learn a new device like Neopixel Ring :sweat_smile:.
Now, I understand how this game works. I have revised the code by,

  1. Randomize all pipes when starting the game at the same time.
  2. NeoPixel blinking when pipe drops.
    Please see the revised code in the Wokwi simulator.
/*
  Solution For:
  Topics:   Combine multiple millis() to control relais and neopixel simultanious
  Category: Programming Questions
  Link:     https://forum.arduino.cc/t/combine-multiple-millis-to-control-relais-and-neopixel-simultanious/1099091

  Sketch:   AuCP_RandomPipes.ino (https://wokwi.com/projects/358817800677315585)
  Created:  11-Mar-2023 (GMT+7)
  MicroBeaut (μB)

  10-Mar-2023:        changed for changes 10-Mar-2023?!
                      https://wokwi.com/projects/358876197860297729
                By:   @alto777

  12-Mar-2023:        Changed,
                      - Modified TimerOn by adding getTimeDelay() function and renaming "done" to "isDone"
                      - Defined Pipe color status
                      - Added RandomCollector() subroutine for pipes
                      - Added Dropping() subroutine
                      https://wokwi.com/projects/359013222335749121
                By:   @MicroBeaut

*/


# include <Adafruit_NeoPixel.h>
# define PIN 8            // Neopixel pin with PWM
# define PIPESTS_PIN 9    // NeoPixel Pipe Status pin

# define NUMPIXELS 12     // number of LEDs on strip
Adafruit_NeoPixel strip(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);              // Declare our NeoPixel strip object
Adafruit_NeoPixel pipeStatus(NUMPIXELS, PIPESTS_PIN, NEO_GRB + NEO_KHZ800); // Declare our NeoPixel strip object

# define PIPE_GREEN  pipeStatus.Color(0, 255, 0) // Pipe Available
# define PIPE_RED    pipeStatus.Color(255, 0, 0) // Pipe Dropping
# define PIPE_BLACK  pipeStatus.Color(0, 0, 0)   // Pipe Droped

class TimerOn {
  private:
    bool prevInput;
    bool state;
    unsigned long startTime;
    unsigned long timeDelay;

  public:
    TimerOn() {
      prevInput = false;
      state = false;
      //      timeDelay = 500000UL; 500 microseconds
    }
    // Convert milliseconds to microseconds
    void setTimeDelay(long msTimeDelay) {
      timeDelay = msTimeDelay * 1000UL;
    }

    // Timer ON Condition
    bool timerOn(bool input = true) {
      if (input) {
        if (!prevInput) {
          startTime = micros();
        } else if (!state) {
          unsigned long elapsedTime = micros() - startTime;
          if (elapsedTime >= timeDelay) {
            // elapsedTime = timeDelay;
            state = true;
          }
        }
      } else {
        state = false;
      }
      prevInput = input;
      return state;
    }

    uint16_t getTimeDelay() {
      return timeDelay / 1000UL;
    }

    bool isDone() {
      return state;
    }
};

#define RANDOM_MIN  1000L // Random Min x milliseconds
#define RANDOM_MAX  7000L // Random Max y milliseconds
#define BLINK_DELAY 500   // 500 microseconds

const uint8_t numberOfPipes = 12;       // Number of Pipes
// TimerOn pipeTimerOns[numberOfPipes]; // Timer On objects
TimerOn pipeTON;                        // Pipe Drop Timer On
uint8_t pipeCollector[numberOfPipes];   // Pipe Index Collector
uint8_t pipeIndex;                      // Pipe Index

TimerOn blinkTON;                       // Blinking Timer On
bool toggleState;

//TimerOn myTO;

void RunGame();
void rainbow();
uint32_t Wheel(byte WheelPos);
void RandomCollector();
void Dropping();
void PrintPipeInfo();

void setup() {
  Serial.begin(115200);
  Serial.println(" timer focus \n");

  randomSeed(analogRead(0));

  strip.begin();
  pipeStatus.begin();

  // strip check - are we talking or what?
  if (1) {
    strip.setPixelColor(7, 0xffffff);
    strip.show();
    delay(777);
  }

  for (uint8_t index = 0; index < numberOfPipes; index++) {
    // long newDelay = random(RANDOM_MIN, RANDOM_MAX);  // Initial Random Time n milliseconds
    // pipeTimerOns[index].setTimeDelay(newDelay);      // Set delay time to object
    pipeCollector[index] = index;
  }
  uint16_t newDelay = random(RANDOM_MIN, RANDOM_MAX); // Initial Random Time n milliseconds
  blinkTON.setTimeDelay(BLINK_DELAY);                 // Set Blink Time Delay
  pipeTON.setTimeDelay (newDelay);                    // Initial time delay for first pipe
  RandomCollector();                                  // Randomize Pipe
  pipeIndex = 0;                                      // Initil Pipe Index
  PrintPipeInfo();                                    // Print dropping info. for first pipe
  toggleState = true;
}

unsigned long now;

void loop() {
  now = millis();

  Dropping();
  RunGame();
  rainbow();
}

void RunGame() {
  if (pipeTON.timerOn(true)) {
    pipeTON.timerOn(false);                                         // Reset timer
    uint16_t newDelay = random(RANDOM_MIN, RANDOM_MAX);             // New Random Time n milliseconds
    pipeTON.setTimeDelay(newDelay);                                 // Set new Delay Time

    pipeStatus.setPixelColor(pipeCollector[pipeIndex], PIPE_BLACK); // Pipe dropped
    pipeIndex = (pipeIndex + 1) % numberOfPipes;                    // Increase Pipe Index modular with numberOfPipes
    if (pipeIndex == 0) RandomCollector();                          // Random for new game
    pipeStatus.setPixelColor(pipeCollector[pipeIndex], PIPE_RED);   // Set status for pipe
    pipeStatus.show();                                              // Update NeoPixel

    //   for (uint8_t index = 0; index < numberOfPipes; index++) {
    //     pipeTimerOns[index].timerOn(true);                  // True = Enable or Start, False = Disable or Stop or Reset
    //     if (pipeTimerOns[index].isDone()) {                 // Timer On Done?

    //       pipeTimerOns[index].timerOn(false);               // Reset Timer On
    //       randomSeed(analogRead(0));                        // Use an unconnected pin for randomSeed()
    //       long newDelay = random(RANDOM_MIN, RANDOM_MAX);   // New Random Time n milliseconds
    //       pipeTimerOns[index].setTimeDelay(newDelay);       // Set delay time to object

    PrintPipeInfo(); // Print dropping info.
    //    }
  }
}

// modified from Adafruit example to make it a state machine
void rainbow()
{
  static uint16_t j = 0;
  static unsigned long lastUpdate;
  if (now - lastUpdate < 40)
    return;

  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, Wheel((i + j) & 255));
  }
  strip.show();

  j++;
  if (j >= 256) j = 0;

  lastUpdate = now;  // time for next change to the display
}

uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

void Dropping() {
  if (blinkTON.timerOn(true)) {
    blinkTON.timerOn(false);
    toggleState = !toggleState;
    if (toggleState) {
      pipeStatus.setPixelColor(pipeCollector[pipeIndex], PIPE_RED);
    } else {
      pipeStatus.setPixelColor(pipeCollector[pipeIndex], PIPE_GREEN);
    }
    pipeStatus.show();
  }
}

// Random Collector for pipes
void RandomCollector() {
  for (uint8_t index = 0; index < numberOfPipes; index++) {
    uint8_t randomNumber = random(index, numberOfPipes);
    uint8_t temp = pipeCollector[index];
    pipeCollector[index] = pipeCollector[randomNumber];
    pipeCollector[randomNumber] = temp;
    pipeStatus.setPixelColor(index, PIPE_GREEN);
  }
  pipeStatus.show();
}

void PrintPipeInfo() {
  if (pipeIndex == 0) {
    Serial.println("\nNew Game...");
  }
  Serial.print(millis()); Serial.print(" dropping ");
  Serial.print(pipeCollector[pipeIndex]); Serial.print(" delay ");
  Serial.print(pipeTON.getTimeDelay()); Serial.print(" mS to next");
  Serial.println("");
}

And thank you for all of your feedback.
MicroBeaut (μB)

I did. The large difference to the OP's code is your freely running loop(). There really should not be any loops in the loop() function, certainly not ones that take any significant time.

That obvsly full speed loop allowed me to transport a hack I had developed elsewhere. See

Falling Hammers simulation

It was hard-geared for 10 hammers, so I turned that down in the *.ino.

Remains: anything to do with starting and stopping a game, and doing that at a level of difficulty.

Also remains is taking a closer look at your code - TBH I just looked until I could install the display hammers stuff.

L8R

a7

2 Likes

Please see the main topics that have been changed,

  • Added Start/Pause, Stop buttons (using RepeatButton Library)
  • Added the level simulator button (using RepeatButton Library)
  • Modified the TimerOn object and the moveHammers function by adding the `pause' parameter to support the Pause function. When the Pause function is added, the elapsed time will be paused.
  • Added the LimitObject for the level time delay

To increase the level, just put a Level simulator button :sweat_smile:.

Please click this link to run the Arduino emulator, and see the below code,

/*
  Solution For:
  Topics:   Combine multiple millis() to control relais and neopixel simultanious
  Category: Programming Questions
  Link:     https://forum.arduino.cc/t/combine-multiple-millis-to-control-relais-and-neopixel-simultanious/1099091

  Sketch:   AuCP_RandomPipes.ino (https://wokwi.com/projects/358817800677315585)
  Created:  11-Mar-2023 (GMT+7)
  MicroBeaut (μB)

  10-Mar-2023:        changed for changes 10-Mar-2023?!
                      https://wokwi.com/projects/358876197860297729
                By:   @alto777

  12-Mar-2023:        Changed,
                      - Modified TimerOn by adding getTimeDelay() function and renaming "done" to "isDone"
                      - Defined Pipe color status
                      - Added RandomCollector() subroutine for pipes
                      - Added Dropping() subroutine
                      https://wokwi.com/projects/359013222335749121
                By:   @MicroBeaut

  12-Mar-2023:        added hack hammer display machinery 23-Mar-2023
                      https://wokwi.com/projects/359050337059862529
                By:   @alto777

  13-Mar-2023:        - Added the start/pause and stop pushbutton
                      - Fixed the timerOn to support pressing pause.
                      - Added the LimitObject for the level time delay
                      - Added the 'pause' parameter to the "timerOn" function
                      - added the 'pause' parameter to the "moveHammers" function for actual velocity.
                      - Added the game level (still not define a role to increase the level)
                      - Added the level simulator button
                      https://wokwi.com/projects/359062656089169921
                By:   @MicroBeaut
*/


# include "hammerz.h"
# include "RepeatButton.h"


# include <Adafruit_NeoPixel.h>
# define PIN 8            // Neopixel pin with PWM
# define PIPESTS_PIN 9    // NeoPixel Pipe Status pin
# define START_PIN 7      // Star/Pause Game
# define STOP_PIN  6      // Stop Game
# define LEVEL_PIN 5      // Level simulator pin

# define GAME_STOP 0
# define GAME_START 1
# define GAME_PAUSE 2
# define GAME_MAX_LEVEL 6

# define NUMPIXELS 12     // number of LEDs on strip
Adafruit_NeoPixel strip(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);              // Declare our NeoPixel strip object
Adafruit_NeoPixel pipeStatus(NUMPIXELS, PIPESTS_PIN, NEO_GRB + NEO_KHZ800); // Declare our NeoPixel strip object

# define PIPE_GREEN  pipeStatus.Color(0, 255, 0) // Pipe Available
# define PIPE_RED    pipeStatus.Color(255, 0, 0) // Pipe Dropping
# define PIPE_BLACK  pipeStatus.Color(0, 0, 0)   // Pipe Droped

/*
  ===========================================================================
  Class:    TimerOn

  The rising edge of input "input" starts a timer of duration "time delay."
  The next falling edge of input "input" initializes the timer.
  When the elapsed time is greater than or equal to "time delay",
  the timer stops, and the output changes from FALSE to TRUE.
  When the "'pause" is FALSE, the elapsed time will be updated by summed with a delta time for each scan.
  When the TRUE, elapsed time will be paused.
  ===========================================================================
*/
class TimerOn {
  private:
    unsigned char prevInput: 1;
    unsigned char output: 1;
    unsigned long elapsedTime;
    unsigned long prevTime;
    unsigned long timeDelay;

  public:
    TimerOn() {
      prevInput = false;
      output = false;
      //      timeDelay = 500000UL; 500 microseconds
    }
    // Convert milliseconds to microseconds
    void setTimeDelay(long msTimeDelay) {
      timeDelay = msTimeDelay * 1000UL;
    }

    // Timer ON Condition
    bool timerOn(bool input = true, bool pause = false) {
      unsigned long currTime = micros();
      unsigned long deltaTime = currTime - prevTime;
      if (input) {
        if (!prevInput) {
          prevTime = currTime;
          elapsedTime = 0;
        } else if (!output) {
          if (!pause) elapsedTime += deltaTime;
          if (elapsedTime >= timeDelay) {
            elapsedTime = timeDelay;
            output = true;
          }
        }
      } else {
        output = false;
      }
      prevTime = currTime;
      prevInput = input;
      return output;
    }

    uint16_t getTimeDelay() {
      return timeDelay / 1000UL;
    }

    bool isDone() {
      return output;
    }
};

typedef struct {
  long minLimit;
  long maxLimit;
} LimitObject;

//#define RANDOM_MIN  1000L // Random Min x milliseconds
//#define RANDOM_MAX  7000L // Random Max y milliseconds
#define BLINK_DELAY 500   // 500 microseconds

const uint8_t numberOfPipes = 10;       // Number of Pipes
// TimerOn pipeTimerOns[numberOfPipes]; // Timer On objects
TimerOn pipeTON;                        // Pipe Drop Timer On
uint8_t pipeCollector[numberOfPipes];   // Pipe Index Collector
uint8_t pipeIndex;                      // Pipe Index

TimerOn blinkTON;                       // Blinking Timer On
bool toggleState;

LimitObject levelTimeLimit[] = {
  {1000L, 7000L},
  {750L, 5250L},
  {500, 3500L},
  {375, 2625},
  {250, 1750},
  {187, 1312}
};

RepeatButton startButton;
RepeatButton stopButton;
RepeatButton levelButton;

uint8_t gameLevel = 0;
uint8_t gameState;
bool startState;
bool pauseState;

//TimerOn myTO;

void OnStartKeyPressed(ButtonEvents e); // Declare the OnKeyPressed Callback Function
void OnStopKeyPressed(ButtonEvents e);  // Declare the OnKeyPressed Callback Function
void OnLevelKeyPressed(ButtonEvents e); // Declare the OnKeyPressed Callback Function

void RunGame();
void rainbow();
uint32_t Wheel(byte WheelPos);
void RandomCollector();
void SetTimeDelay();
void Dropping();
void PrintPipeInfo();

void setup() {
  Serial.begin(115200);
  Serial.println(" timer focus \n");

  setupHammers();
  resetHammers();

  randomSeed(analogRead(0));

  strip.begin();
  pipeStatus.begin();


  // strip check - are we talking or what?
  if (1) {
    strip.setPixelColor(7, 0xffffff);
    strip.show();
    delay(777);
  }

  for (uint8_t index = 0; index < numberOfPipes; index++) {
    // long newDelay = random(RANDOM_MIN, RANDOM_MAX);  // Initial Random Time n milliseconds
    // pipeTimerOns[index].setTimeDelay(newDelay);      // Set delay time to object
    pipeCollector[index] = index;
  }

  blinkTON.setTimeDelay(BLINK_DELAY);                 // Set Blink Time Delay
  //uint16_t newDelay = random(levelTimeLimit[gameLevel].minLimit, levelTimeLimit[gameLevel].maxLimit); // Initial Random Time n milliseconds
  //pipeTON.setTimeDelay (newDelay);                    // Initial time delay for first pipe
  RandomCollector();                                  // Randomize Pipe
  pipeIndex = 0;                                      // Initil Pipe Index
  //PrintPipeInfo();                                  // Print dropping info. for first pipe
  toggleState = true;

  startButton.buttonMode(START_PIN, INPUT_PULLUP);    // Pushbutton mode
  startButton.onKeyPressed(OnStartKeyPressed);        // Callback event on key press
  stopButton.buttonMode(STOP_PIN, INPUT_PULLUP);      // Pushbutton mode
  stopButton.onKeyPressed(OnStopKeyPressed);          // Callback event on key press
  levelButton.buttonMode(LEVEL_PIN, INPUT_PULLUP);    // Pushbutton mode
  levelButton.onKeyPressed(OnLevelKeyPressed);        // Callback event on key press
  gameLevel = 0;                                      // Initial level 0
}

unsigned long now;

void loop() {
  now = millis();

  startButton.repeatButton();
  stopButton.repeatButton();
  levelButton.repeatButton();

  Dropping();
  RunGame();
  moveHammers(pauseState, pipeTON.getTimeDelay());
  displayHammers();
  rainbow();
}

void OnStartKeyPressed(ButtonEvents e) {
  switch (e) {
    case keyPress:
      bool prevStartState = startState;
      gameState = gameState != GAME_START ? GAME_START : GAME_PAUSE;
      startState = true;
      pauseState = gameState == GAME_PAUSE;

      if (startState == !prevStartState) {
        pipeIndex = 0;
        SetTimeDelay();
        PrintPipeInfo();
      }
  }
}

void OnStopKeyPressed(ButtonEvents e) {
  switch (e) {
    case keyPress:
      bool prevStartState = startState;
      gameState = GAME_STOP;
      startState = false;
      if (!startState == prevStartState) {
        RandomCollector();
        resetHammers();
      }
      break;
  }
}

void OnLevelKeyPressed(ButtonEvents e) {
  switch (e) {
    case keyPress:
      gameLevel = (gameLevel + 1 ) % GAME_MAX_LEVEL;
      Serial.print("\nLevel:=");
      Serial.print(gameLevel + 1);
      Serial.println(", speed will be effective for the next hammer");
      break;
  }
}

void SetTimeDelay() {
  uint16_t newDelay = random(levelTimeLimit[gameLevel].minLimit, levelTimeLimit[gameLevel].maxLimit); // Initial Random Time n milliseconds
  pipeTON.setTimeDelay (newDelay);  // Set hammer time delay
}

void RunGame() {
  if (pipeTON.timerOn(startState, pauseState)) {
    pipeTON.timerOn(false);                                         // Reset timer
    //uint16_t newDelay = random(levelTimeLimit[gameLevel].minLimit, levelTimeLimit[gameLevel].maxLimit); // New Random Time n milliseconds
    //pipeTON.setTimeDelay(newDelay);                                 // Set new Delay Time
    SetTimeDelay();                                                 // Set hammer time delay
    pipeStatus.setPixelColor(pipeCollector[pipeIndex], PIPE_BLACK); // Pipe dropped
    pipeIndex = (pipeIndex + 1) % numberOfPipes;                    // Increase Pipe Index modular with numberOfPipes
    if (pipeIndex == 0) RandomCollector();                          // Random for new game
    pipeStatus.setPixelColor(pipeCollector[pipeIndex], PIPE_RED);   // Set status for pipe
    pipeStatus.show();                                              // Update NeoPixel

    //   for (uint8_t index = 0; index < numberOfPipes; index++) {
    //     pipeTimerOns[index].timerOn(true);                  // True = Enable or Start, False = Disable or Stop or Reset
    //     if (pipeTimerOns[index].isDone()) {                 // Timer On Done?

    //       pipeTimerOns[index].timerOn(false);               // Reset Timer On
    //       randomSeed(analogRead(0));                        // Use an unconnected pin for randomSeed()
    //       long newDelay = random(RANDOM_MIN, RANDOM_MAX);   // New Random Time n milliseconds
    //       pipeTimerOns[index].setTimeDelay(newDelay);       // Set delay time to object

    PrintPipeInfo(); // Print dropping info.
    //    }
  }
}

// modified from Adafruit example to make it a output machine
void rainbow()
{
  static uint16_t j = 0;
  static unsigned long lastUpdate;
  if (now - lastUpdate < 40)
    return;

  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, Wheel((i + j) & 255));
  }
  strip.show();

  j++;
  if (j >= 256) j = 0;

  lastUpdate = now;  // time for next change to the display
}

uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

void Dropping() {
  if (blinkTON.timerOn(startState, pauseState)) {
    blinkTON.timerOn(false);
    toggleState = !toggleState;
    if (toggleState) {
      pipeStatus.setPixelColor(pipeCollector[pipeIndex], PIPE_RED);
    } else {
      pipeStatus.setPixelColor(pipeCollector[pipeIndex], PIPE_GREEN);
    }
    pipeStatus.show();
  }
}


/*
  To initialize an array a of n elements to a randomly shuffled copy of source, both 0-based:
  for i from 0 to n − 1 do
      j ← random integer such that 0 ≤ j ≤ i
      if j ≠ i
          a[i] ← a[j]
      a[j] ← source[i]

*/


// Random Collector for pipes
void RandomCollector() {
  for (uint8_t index = 0; index < numberOfPipes; index++) {
    uint8_t randomNumber = random(index + 1);
    pipeCollector[index] = pipeCollector[randomNumber];
    pipeCollector[randomNumber] = index;
  }

  for (uint8_t index = 0; index < numberOfPipes; index++)
    pipeStatus.setPixelColor(index, PIPE_GREEN);

  pipeStatus.show();

  if (0) {
    for (uint8_t index = 0; index < numberOfPipes; index++) {
      Serial.print(pipeCollector[index]); Serial.print(" ");
      pipeStatus.setPixelColor(index, PIPE_GREEN);
    }
    Serial.println("");
  }
}



// // Random Collector for pipes
// void RandomCollector0() {
//   for (uint8_t index = 0; index < numberOfPipes; index++) {
//     uint8_t randomNumber = random(index, numberOfPipes);
//     uint8_t temp = pipeCollector[index];
//     pipeCollector[index] = pipeCollector[randomNumber];
//     pipeCollector[randomNumber] = temp;
//   }

//   for (uint8_t index = 0; index < numberOfPipes; index++) {
//     Serial.print(pipeCollector[index]); Serial.print(" ");
//     pipeStatus.setPixelColor(index, PIPE_GREEN);
//   }
//   Serial.println("");

//   pipeStatus.show();


// }

void PrintPipeInfo() {
  if (pipeIndex == 0) {
    Serial.print("\nNew Game Level:=");
    Serial.println(gameLevel + 1);
    resetHammers();
  }
  dropHammer(pipeCollector[pipeIndex]);

  Serial.print(millis()); Serial.print(" dropping ");
  Serial.print(pipeCollector[pipeIndex]); Serial.print(" delay ");
  Serial.print(pipeTON.getTimeDelay()); Serial.print(" mS to next");
  Serial.println("");
}

μB

Again, I did. I'm out the door soon, soon as my beach buddy swings by: she who must not be kept waiting...

I wrote the hammerz thing planning to eventually mimic gravity. That is to say, all hammers will drop with gravity, not just at a timed rate (like my original) or a selected rate based on the delay to next (I think that's what you are doing).

Also by doing that, you are preventing two hammers to be in the air at the same time. Which is good if they are to fall at different rates, but removes a "feature" which is being faced with multiple dropping hammers.

Not yet seen, nor written for that matter, is my rail gun and launch control system, a fully non-blocking hack to add in which will

  • afford a rail gun aiming bar, click '<' and '>' to position the gun, only moves so fast and

  • a missile launch button '=' which will shoot a 1 kg tungsten slug up at the falling hammer

Hit a hammer before it hits the floor and that columb is green a point.

Errors (firing at not moving or complete columns) or missing turns a columb red, no point.

Another 10 LED strip, three buttons and I am aiming (see what I did there?) for minimal involvement with the existing game code.

Don't hold your breath on that - we have a spectacular day and a forecast for several, time stolen from enjoying that will be time lost. :expressionless:

I may never.

a7

The Umbrella Academy hacked our way through adding a rail gun.

Sry, @hamarn, we should have added it to your progress but didn't want to undo anything.

The rail gun lets you aim and fire at the falling hammers. I do not recommend learning from the code, but the game play isn't entirely stupid. I think with some tuning and physics might end up being an amusing challenge.

The red "FIRE" button is also for the start game. The loop() uses a switch/case to be in one of three distinct game phases: resting and ready, two seconds set! and then go which runs through the N pipes or hammers or whatever.

Hammers, pipes and railgun.


a7
1 Like

I got off work late this week :smile:. I'll try this weekend.

μB

@alto777
It was done on firing. I have changed the dead color to green. And fire from yellow to red.
Hammers, pipes and railgun.

@hamarn Nice work!

Now there's a bunch of constants and defines, perhaps they could all be in one header file - the colors of the various actor-states, the timing of the drops and speed thereof &c.

If anyone builds this, they will need to fix all the colors as the wokwi isn't the best when it comes to simulated LEDs.

As it is now, I don't get to enjoy too much the firing up red traveller. :frowning: I'll turn it down to old man reflexes speed.

Also - perhaps firing at nothing should show a useless missile going up and off the hammer field.

Maybe no missile launches while that plays out which...

...might mean that making the missile move up significantly faster would be good.

There would be tunings that might make more than one missile in the air at a time be desirable. TBH I did not look too closely at the new firing logic.

The game is now at the point where real world switches would be nice. I've done some experiments with physics on this, and mostly it makes it hard to play on the wokwi.

Side project: Use the USB-HID on a Leonardo and make a little controller box. Make the sketch in the wokwi hear/listen to keystrokes and buttons. I wonder if some kind of game controller would already be a huge jumpt start on that.

I can only say life too short.

a7

@alto777; As promised, I uploaded a video to youtube to show you the end result of the game I made with your help;

Thank you a lot again for your help. Could not have done it without you!
The kids also enjoy it alot (and keep getting better at it).

1 Like

The sketch has changed a bit based on some testing. This is the latest and working sketch for anyone that wants to use it;

//Sketch to control falling pipes game, including 3 buttons; easy (long time), hard (short time) and reset. Including some NeoPixel rings to generate some attraction.
//used examples;
//Random pipe order from https://forum.arduino.cc/t/shuffle-numbers-when-button-is-pressed/675503/7
//NeoPixel; https://forum.arduino.cc/t/replacing-delay-with-millis-in-rainbowcycle-function/634554/7
//with many thanks to the help from the arduino forum and especially to alto777

//variabelen nodig voor timers
unsigned long prevTimeT1 = millis();                  // Deze is voor de knop LED te laten knipperen. Voor iedere actie met een tijdsfactor moet deze regel herhaald worden
unsigned long now;                                    // Deze is voor de timer van de buizen vallen (current time)
unsigned long CurrentTime; 
unsigned long prevTimeT2 = millis();                  // Deze is voor de timer van de buizen vallen (previous time)
unsigned long patternInterval = 20;                   // time between steps in the pattern
unsigned long lastUpdate = 0;                         // for millis() when last update occurred
unsigned long intervals[] = { 20, 20, 50, 100, 30 };  // speed for each pattern - add here when adding more cases
int fadeStep = 0;                                     // state variable for fade function
int counter;

//variabelen voor random generators
long nextTime;  //delay value number
long randPipe;  //Pipenummer uit random generator


//array buildup for pipe randomizer
int array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
const int arraySize = sizeof array / sizeof array[0];
int remainingNumbers = 0;

int Level = 0;  //moeilijkheidsgraad startwaarde

//Input pins
#define Btn_Easy 20
#define Btn_Hard 21
//Resetbutton on Resetpin

//Output to relais
#define Pipe_1 2
#define Pipe_2 3
#define Pipe_3 4
#define Pipe_4 5
#define Pipe_5 6
#define Pipe_6 7
#define Pipe_7 8
#define Pipe_8 9
#define Pipe_9 10
#define Pipe_10 11
#define Btn_LED_Easy 14
#define Btn_LED_Hard 15
#define Btn_LED_Reset 16
int ledStateBtn = LOW;  // ledState used to set the LED van drukknop
int ledStateRst = LOW;  // ledState used to set the LED van resetknop

//NeoPixel setup
#define PINforControl1 12  //Neopixel pin with PWM
#define PINforControl2 13  //Neopixel pin with PWM
#define PINforControl3 17  //Neopixel pin with PWM
#define NUMPIXELS1 12     // number of LEDs on strip
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip1(NUMPIXELS1, PINforControl1, NEO_GRB + NEO_KHZ800);  // Declare our NeoPixel strip object
Adafruit_NeoPixel strip2(NUMPIXELS1, PINforControl2, NEO_GRB + NEO_KHZ800);  // Declare our NeoPixel strip object
Adafruit_NeoPixel strip3(NUMPIXELS1, PINforControl3, NEO_GRB + NEO_KHZ800);  // Declare our NeoPixel strip object

int getRandomEntry() {
  if (remainingNumbers == 0)
    remainingNumbers = arraySize;  // Start over with a full array

  int index = random(remainingNumbers);  // 0 to remainingNumbers - 1
  int returnValue = array[index];

  if (index < remainingNumbers - 1)  // Swap the chosen number with the number at the end of the current array (unless the chosen entry is already at the end)
  {
    array[index] = array[remainingNumbers - 1];  // Move the last entry into the chosen index
    array[remainingNumbers - 1] = returnValue;   // Move the value from the chosen index into the last entry
  }

  remainingNumbers--;  // Shorten the list so the picked number is not picked again
  return returnValue;
}

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

  randomSeed(analogRead(A4 * A5));            

  //Neopixel startup;
  strip1.begin();  // INITIALIZE NeoPixel strip object (REQUIRED)
  strip2.begin();
  strip3.begin();
  wipe();
  //strip.setBrightness(50);  // Set BRIGHTNESS to about 1/5 (max = 255)

  pinMode(LED_BUILTIN, OUTPUT);  //onboard LED
  pinMode(Btn_LED_Easy, OUTPUT);
  pinMode(Btn_LED_Hard, OUTPUT);
  pinMode(Btn_LED_Reset, OUTPUT);
  pinMode(20, INPUT_PULLUP);
  pinMode(21, INPUT_PULLUP);
  pinMode(Pipe_1, OUTPUT);
  pinMode(Pipe_2, OUTPUT);
  pinMode(Pipe_3, OUTPUT);
  pinMode(Pipe_4, OUTPUT);
  pinMode(Pipe_5, OUTPUT);
  pinMode(Pipe_6, OUTPUT);
  pinMode(Pipe_7, OUTPUT);
  pinMode(Pipe_8, OUTPUT);
  pinMode(Pipe_9, OUTPUT);
  pinMode(Pipe_10, OUTPUT);

  //turn off all magnets
  digitalWrite(Pipe_1, HIGH);
  digitalWrite(Pipe_2, HIGH);
  digitalWrite(Pipe_3, HIGH);
  digitalWrite(Pipe_4, HIGH);
  digitalWrite(Pipe_5, HIGH);
  digitalWrite(Pipe_6, HIGH);
  digitalWrite(Pipe_7, HIGH);
  digitalWrite(Pipe_8, HIGH);
  digitalWrite(Pipe_9, HIGH);
  digitalWrite(Pipe_10, HIGH);
  
  //prepare for the game

  digitalWrite(Btn_LED_Reset, LOW);
  digitalWrite(Btn_LED_Easy, HIGH);
  digitalWrite(Btn_LED_Hard, HIGH);
  colorWipeDelay(strip1.Color(255, 50, 0), 100);  // Orange
  colorWipeDelay(strip2.Color(255, 50, 0), 100);  // Orange
  colorWipeDelay(strip3.Color(255, 50, 0), 100);  // Orange
  AllMagnetsOn();                                //Alle magneten inschakelen
  colorWipeDelay(strip1.Color(0, 255, 0), 100);  // Green
  colorWipeDelay(strip2.Color(0, 255, 0), 100);  // Green
  colorWipeDelay(strip3.Color(0, 255, 0), 100);  // Green
  delay(2000);
  colorWipeDelay(strip1.Color(0, 0, 0), 100);  // Off
  colorWipeDelay(strip2.Color(0, 0, 0), 100);  // Off
  colorWipeDelay(strip3.Color(0, 0, 0), 100);  // Off
  wipe();  // off
  digitalWrite(Btn_LED_Easy, LOW);
  digitalWrite(Btn_LED_Hard, LOW);

  int counter;                                      // Used to count how many pipes have dropped, when all pipes have dropped, game is finished.
}

void loop() {

  now = millis();
  rainbow();  // Flowing rainbow cycle along the whole strip with NeoPixel; This blocks the Button read loop because there is a delay within the function

if (digitalRead(Btn_Easy) == LOW) {  //als knop "Gemakkelijk" wordt ingedrukt; set moeilijkheid naar gemakkelijk en start spel
    delay(50); //debounce button
    Level = 10;
    Serial.println("Button Easy is pressed");
    counter = 0;

    while (counter < arraySize)   // when counter = arraySize; exit loop
      { 
      BtnLedEasyPuls();
      rainbowCycle();
      RunGame(); 
      }
  }

  if (digitalRead(Btn_Hard) == LOW) {  //als knop "Moeilijk" wordt ingedrukt; set moeilijkheid naar Moeilijk en start spel //this code must yet be corrected based on the Btn_Easy code
    delay(50); //debounce button
    Level = 4;
    Serial.println("Button Hard is pressed");
    counter = 0;

    while (counter < arraySize) // when counter = arraySize; exit loop
    {
      BtnLedHardPuls();
      theaterChaseRainbow();
      RunGame();
    }
  }
}

void AllMagnetsOn() {  //insert a small delay to give relay unit some time to initialize all relays one at a time to prevent current peak.
  digitalWrite(Pipe_1, LOW);
  delay(50);
  digitalWrite(Pipe_2, LOW);
  delay(50);
  digitalWrite(Pipe_3, LOW);
  delay(50);
  digitalWrite(Pipe_4, LOW);
  delay(50);
  digitalWrite(Pipe_5, LOW);
  delay(50);
  digitalWrite(Pipe_6, LOW);
  delay(50);
  digitalWrite(Pipe_7, LOW);
  delay(50);
  digitalWrite(Pipe_8, LOW);
  delay(50);
  digitalWrite(Pipe_9, LOW);
  delay(50);
  digitalWrite(Pipe_10, LOW);
  delay(500);
}

void RunGame() {
  static unsigned long lastTime = 3000;
  static int nextTime = random(100, 1000);
  now = millis();
 
  if (now - lastTime < (nextTime*Level))
    return;

    // select and drop random
    randPipe = getRandomEntry();
    digitalWrite(randPipe + 2, HIGH);  // laat de honden los!
 
    Serial.print("Random delay value: ");
    Serial.println(nextTime * Level);
    Serial.print("Pipe nr: ");
    Serial.print(randPipe);
    Serial.print(", Pin nr: ");
    Serial.println(randPipe + 2);
    Serial.print("Pipe counter value; ");
    Serial.print(counter);
    Serial.print(", array size; ");
    Serial.println(arraySize);
    Serial.println();

  // schedule
  nextTime  = random(100, 1000) ;
  lastTime = now;
  counter++; //counts every time a pipe has dropped
}

void BtnLedEasyPuls() {
  unsigned long now = millis();

  if (now - prevTimeT1 > 200) {

    prevTimeT1 = now;
    // if the LED is off turn it on and vice-versa:
    if (ledStateBtn == LOW) {
      ledStateBtn = HIGH;
      ledStateRst = LOW;
    } else {
      ledStateBtn = LOW;
      ledStateRst = HIGH;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(Btn_LED_Easy, ledStateBtn);
    digitalWrite(Btn_LED_Reset, ledStateRst);
  }
}

void BtnLedHardPuls() {
  unsigned long now = millis();

  if (now - prevTimeT1 > 200) {

    prevTimeT1 = now;
    // if the LED is off turn it on and vice-versa:
    if (ledStateBtn == LOW) {
      ledStateBtn = HIGH;
      ledStateRst = LOW;
    } else {
      ledStateBtn = LOW;
      ledStateRst = HIGH;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(Btn_LED_Hard, ledStateBtn);
    digitalWrite(Btn_LED_Reset, ledStateRst);
  }
}

void rainbow() {  // modified from Adafruit example to make it a state machine
  static uint16_t j = 0;
  static unsigned long lastUpdate;
  if (now - lastUpdate < 15)
    return;
  for (int i = 0; i < strip1.numPixels(); i++) {
    strip1.setPixelColor(i, Wheel((i + j) & 255));
    strip2.setPixelColor(i, Wheel((i + j) & 255));
    strip3.setPixelColor(i, Wheel((i + j) & 255));
  }
  strip1.show();
  strip2.show();
  strip3.show();
  j++;
  if (j >= 256) j = 0;
  lastUpdate = now;  // time for next change to the display
}

void rainbowCycle() {  // modified from Adafruit example to make it a state machine
  static uint16_t j = 0;
  static unsigned long lastUpdate2;

  if (now - lastUpdate2 < 20)
    return;

  for (int i = 0; i < strip1.numPixels(); i++) {
    strip1.setPixelColor(i, Wheel(((i * 256 / strip1.numPixels()) + j) & 255));
    strip2.setPixelColor(i, Wheel(((i * 256 / strip1.numPixels()) + j) & 255));
    strip3.setPixelColor(i, Wheel(((i * 256 / strip1.numPixels()) + j) & 255));
  }
  strip1.show();
  strip2.show();
  strip3.show();
  j++;
  if (j >= 256 * 5) j = 0;
  lastUpdate2 = now;  // time for next change to the display
}

void theaterChaseRainbow() {  // modified from Adafruit example to make it a state machine
  static int j = 0, q = 0;
  static boolean on = true;
  static unsigned long lastUpdate3;

  if (now - lastUpdate3 < 30) 
    return;

  if (on) {
    for (int i = 0; i < strip1.numPixels(); i = i + 3) {
      strip1.setPixelColor(i + q, Wheel((i + j) % 255));  //turn every third pixel on
      strip2.setPixelColor(i + q, Wheel((i + j) % 255));  //turn every third pixel on
      strip3.setPixelColor(i + q, Wheel((i + j) % 255));  //turn every third pixel on
    }
  } else {
    for (int i = 0; i < strip1.numPixels(); i = i + 3) {
      strip1.setPixelColor(i + q, 0);  //turn every third pixel off
      strip2.setPixelColor(i + q, 0);  //turn every third pixel off
      strip3.setPixelColor(i + q, 0);  //turn every third pixel off
    }
  }
  on = !on;      // toggel pixelse on or off for next time
  strip1.show();  // display
  strip2.show();  // display
  strip3.show();  // display
  q++;           // update the q variable
  if (q >= 3) {  // if it overflows reset it and update the J variable
    q = 0;
    j++;
    if (j >= 256) j = 0;
  }
  lastUpdate3 = now;  // time for next change to the display
}

void colorWipeDelay(uint32_t c, uint8_t wait) {  // Fill the dots one after the other with a color
  for (uint16_t i = 0; i < strip1.numPixels(); i++) {
    strip1.setPixelColor(i, c);
    strip2.setPixelColor(i, c);
    strip3.setPixelColor(i, c);
    strip1.show();
    strip2.show();
    strip3.show();
    delay(wait);
  }
}

void wipe() {  // clear all LEDs
  for (int k = 0; k < strip1.numPixels(); k++) {
    strip1.setPixelColor(k, strip1.Color(0, 0, 0));
    strip2.setPixelColor(k, strip2.Color(0, 0, 0));
    strip3.setPixelColor(k, strip3.Color(0, 0, 0));
  }
}

uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return strip1.Color(255 - WheelPos * 3, 0, WheelPos * 3);
    return strip2.Color(255 - WheelPos * 3, 0, WheelPos * 3);
    return strip3.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return strip1.Color(0, WheelPos * 3, 255 - WheelPos * 3);
    return strip2.Color(0, WheelPos * 3, 255 - WheelPos * 3);
    return strip3.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip1.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  return strip2.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  return strip3.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}