Combine multiple millis() to control relais and neopixel simultanious

Hi all,

I'm looking for someone that can push me a bit in the right direction. My goal is to make a game with falling objects at random time and order so that my kids can catch them. The idea is to control 10 electromagnets trough a relais. With a push button, the game difficulty can be chosen. This part is working.
Next to that, I would like to use some NeoPixel rings to energize the game a bit.
I'm struggeling to replace the "delay" function to the millis() logic that I find everywhere so that the game and the NeoPixel can both run at the same time.

I'm especially struggeling with the "RunGame" void. It is possible that there is some dead code in my sketch. Still going trough it with trail and error and trying a lot of ideas from the forum.

Below my current code. Any help is appreciated!

//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

//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 CurrentTime = millis();                 // Deze is voor de timer van de buizen vallen (current time)
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 numberOfCases = 4;                                // how many case statements or patterns you have

//variabelen voor random generators
long RandTime;  //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 PINforControl 12  //Neopixel pin with PWM
#define NUMPIXELS1 12     // number of LEDs on strip
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip(NUMPIXELS1, PINforControl, NEO_GRB + NEO_KHZ800);  // Declare our NeoPixel strip object

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

  randomSeed(analogRead(A0));            // Pick a number from those remaining
  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;
  strip.begin();  // INITIALIZE NeoPixel strip object (REQUIRED)
  wipe();
  strip.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);

  //fade(0, 255, 0, 64, 0, 0, 400);  // fade from black to orange and back
  colorWipeDelay(strip.Color(255, 50, 0), 100);  // Orange
  AllMagnetsOn();                                //Alle magneten inschakelen
  //fade(0, 0, 0, 255, 0, 0, 400);  // fade from black to green and back
  colorWipeDelay(strip.Color(0, 255, 0), 100);  // Green
  delay(2000);
  wipe();  // off

  Serial.println("Ready");
}

void loop() {
  patternInterval = 20;
  if (millis() - lastUpdate > patternInterval)
    ;
  rainbow();  // Flowing rainbow cycle along the whole strip; 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
    Level = 10;
    Serial.println("Button Easy is pressed");
    BtnLedEasyPuls();
    rainbowCycle();  // Rainbow-enhanced theaterChase variant; this one blocks the rest of the programm until finished. I would like this one to continue running during the game.
    RunGame();
    AllMagnetsOn();
    colorWipeDelay(strip.Color(0, 255, 0), 100);  // Green
    colorWipeDelay(strip.Color(0, 0, 0), 100);    // Off
  }

  if (digitalRead(Btn_Hard) == LOW) {  //als knop "Moeilijk" wordt ingedrukt; set moeilijkheid naar Moeilijk en start spel
    Level = 3;
    Serial.println("Button Hard is pressed");
    BtnLedHardPuls();
    theaterChaseRainbow();
    RunGame();
    AllMagnetsOn();
    colorWipe(strip.Color(0, 255, 0));  // Green
    colorWipe(strip.Color(0, 0, 0));    // 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 RunGameWithSerialAndDelay() {  //works, but not together with NeoPixel routine, this void is now not in use, but remains in the code until the millis() void works 
  for (int i = 0; i < arraySize; i++) {
    randomSeed(analogRead(A0));
    RandTime = random(100, 1000);
    randPipe = getRandomEntry();
    delay(RandTime * Level);
    Serial.print("RandTime: ");
    Serial.println(RandTime);
    Serial.print("Delay value: ");
    Serial.println(RandTime * Level);
    Serial.print("Pipe nr: ");
    Serial.println(randPipe);
    Serial.print("Pin nr: ");
    Serial.println(randPipe + 2);

    Serial.println();
    digitalWrite(randPipe + 2, HIGH);
  }
  Serial.println("Finished");
}

void RunGame() {
  for (int i = 0; i < arraySize; i++) {
    CurrentTime = millis();
    randomSeed(analogRead(A0));
    RandTime = random(100, 1000);
    randPipe = getRandomEntry();
    if (CurrentTime - prevTimeT2 > RandTime) {
      digitalWrite(randPipe + 2, HIGH);
      prevTimeT2 = CurrentTime;
    }
  }
}

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

  if (currentTime - prevTimeT1 > 200) {

    prevTimeT1 = currentTime;
    // 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 currentTime = millis();

  if (currentTime - prevTimeT1 > 200) {

    prevTimeT1 = currentTime;
    // 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 fade(int redStartValue, int redEndValue, int greenStartValue, int greenEndValue, int blueStartValue, int blueEndValue, int totalSteps) {
  static float redIncrement, greenIncrement, blueIncrement;
  static float red, green, blue;
  static boolean fadeUp = false;

  if (fadeStep == 0) {  // first step is to initialise the initial colour and increments
    red = redStartValue;
    green = greenStartValue;
    blue = blueStartValue;
    fadeUp = false;

    redIncrement = (float)(redEndValue - redStartValue) / (float)totalSteps;
    greenIncrement = (float)(greenEndValue - greenStartValue) / (float)totalSteps;
    blueIncrement = (float)(blueEndValue - blueStartValue) / (float)totalSteps;
    fadeStep = 1;  // next time the function is called start the fade
  } else {         // all other steps make a new colour and display it
    // make new colour
    red += redIncrement;
    green += greenIncrement;
    blue += blueIncrement;

    // set up the pixel buffer
    for (int i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, strip.Color((int)red, (int)green, (int)blue));
    }
    // now display it
    strip.show();
    fadeStep += 1;                 // go on to next step
    if (fadeStep >= totalSteps) {  // finished fade
      if (fadeUp) {                // finished fade up and back
        fadeStep = 0;
        return;  // so next call re-calabrates the increments
      }
      // now fade back
      fadeUp = true;
      redIncrement = -redIncrement;
      greenIncrement = -greenIncrement;
      blueIncrement = -blueIncrement;
      fadeStep = 1;  // don't calculate the increments again but start at first change
    }
  }
}

void rainbow() {  // modified from Adafruit example to make it a state machine
  static uint16_t j = 0;
  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, Wheel((i + j) & 255));
  }
  strip.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;
  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
  }
  strip.show();
  j++;
  if (j >= 256 * 5) j = 0;
  lastUpdate = 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;
  if (on) {
    for (int i = 0; i < strip.numPixels(); i = i + 3) {
      strip.setPixelColor(i + q, Wheel((i + j) % 255));  //turn every third pixel on
    }
  } else {
    for (int i = 0; i < strip.numPixels(); i = i + 3) {
      strip.setPixelColor(i + q, 0);  //turn every third pixel off
    }
  }
  on = !on;      // toggel pixelse on or off for next time
  strip.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;
  }
  lastUpdate = millis();  // time for next change to the display
}

void colorWipe(uint32_t color) {  // modified from Adafruit example to make it a state machine
  static int i = 0;
  strip.setPixelColor(i, color);
  strip.show();
  i++;
  if (i >= strip.numPixels()) {
    i = 0;
    wipe();  // blank out strip
  }
  lastUpdate = 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 < strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

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

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);
}

Please describe the difficulties: explain what did you expect to happen, and what happens instead.

Hint: the keyword "void" in front of a function name means that the function does not return a value.

This, from RunGame() is a problem:

    if (CurrentTime - prevTimeT2 > RandTime) {
      digitalWrite(randPipe + 2, HIGH);
      prevTimeT2 = CurrentTime;
    }

When that condition isn't met, the for loop will just skitter ahead, not doing what you seem to want, that is it doesn't wait around testing until it is time.

You can see this if you carefully step yourself through the loop, pretend executing the code. If the if is false, step right along with the loop.

I'm looking through the tiny window. A quick test, but something you'd want to fix later, is to place a random delay() in the loop instead of aiming for a sophisticated timer mechanism.

That would let you see RinGame() do something like I think you want it to.

If this is still up when I get to the lab I'll show a quick test, see if you can try what I think might work as a patch for the moment.

HTH and L8R

a7

Please describe the difficulties: explain what did you expect to happen, and what happens instead.

The part that is not working is that when I use the source code for the neopixel (url to the source forum site is in the code), the "rainbow" void shows a slow changing pattern of colors. I copy-pasted this code to my code and then it's flashing, like it's missing the timer.

A quick test, but something you'd want to fix later, is to place a random delay() in the loop instead of aiming for a sophisticated timer mechanism.

I started with the random delay. That one is coded in the "RunGameWithSerialAndDelay()" void and that is working correctly. But as soon as the program enters this void, the neopixel stops it's movement. Both the game and the neopixel need a timer. Seperatly they work well with the delay, but when 1 is in the delay wait, the other one also waits. That's why I'm looking to the more sophisticated timer with the millis(). And that's where I got stuck.

OK, I understand.

And here's where the road takes a hard turn to a new direction.

You've started to attempt to exploit some common patterns. There are as many ways to explain and grasp this as there are ppl on these fora who want to help.

I'm still with the tiny window under the umbrella, but the google trifecta goes something like

 Arduino blink without delay

and

 Arduino two things at once

and

 Arduino finite state machine

Settle in and without trying to master this in one go, poke around reading on those three searches.

This

has recently impressed some of the heavies here.

Expect to be over your head until an Aha! moment, similar perhaps to the first time you actually balanced a bike and rode it across the empty parking lot at the mall until you crashed into the wall, but hey! you were doing it!

We here.

a7

1 Like

OK, I took a fast flight over your code to get to RunGame().

Call randomSeed() once, in setup(). Eliminate any other calls to randomSeed();

It appears your code is already doing all of the necessary things, so either you forgot how to or just plain to write RunGame() in the same paradigm, or you never truly understood the code you stole borrowed.

Or somewhere in between. :wink:

So with some hope that you compare, side by side my version of your function with yours.

void RunGame() {
  static unsigned long prevTimeT2;
  static int RandTime = 316;
  static int i;           ///...  for (int i = 0; i < arraySize; i++) {

// time to?
  CurrentTime = millis();
  if (CurrentTime - prevTimeT2 < RandTime)
    return;

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

// cycle
  i++; if (i >= arraySize) i = 0; 
  
// schedule 
  RandTime = random(100, 1000);
  prevTimeT2 = CurrentTime;
}

It compiles, i did not test it. Throw it on in there and say what happens.

google the language features you do not understand.

a7

Thank you for your quick reply.

I will study the info in the link you send (multi-tasking the arduino).

or you never truly understood the code you stole borrowed. Or somewhere in between. :wink:
You may have a point. I try to understand what the code is doing (and I think i understand most of it), but its hard for me to see how it works together at some points.

When I compare your code to my last one, I see that you determine if the randomTime is smaller then randomTime (so it is not yet time to release the next pipe), it returns to the Loop, when the elapsed time is equal or larger then the RandTime, the code continues to drop a pipe and set the timer to currentTime. That sound logical.

digitalWrite(randPipe + 2, HIGH); // laat de honden los!

I think we are in the same area of the world :slight_smile:

I put some serial.print lines within the code to monitor the actions and this part works :slight_smile:

The only part I see now not working is the part I "borrowed" to control the NeoPixel; With the original code from https://forum.arduino.cc/t/replacing-delay-with-millis-in-rainbowcycle-function/634554/7 the NeoPixel moves slowly trough a rainbow of colors.
When I call the "rainbow" void in my own code during the wait time before a button is pressed, and move trough the "theaterChsseRainbow" during the game, the neopixel is flashing trought the rainbow way to fast, so i expect it misses wait time in/before the neopixel code.

I tried to put the millis() loop within the loop function to correct this, but that is not working.

So here the current code;

//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

//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 CurrentTime = millis();                 // Deze is voor de timer van de buizen vallen (current time)
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 numberOfCases = 4;                                // how many case statements or patterns you have

//variabelen voor random generators
long RandTime;  //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 PINforControl 12  //Neopixel pin with PWM
#define NUMPIXELS1 12     // number of LEDs on strip
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip(NUMPIXELS1, PINforControl, NEO_GRB + NEO_KHZ800);  // Declare our NeoPixel strip object

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

  randomSeed(analogRead(A0));            // Pick a number from those remaining
  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;
  strip.begin();  // INITIALIZE NeoPixel strip object (REQUIRED)
  wipe();
  strip.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);

  //fade(0, 255, 0, 64, 0, 0, 400);  // fade from black to orange and back
  colorWipeDelay(strip.Color(255, 50, 0), 100);  // Orange
  AllMagnetsOn();                                //Alle magneten inschakelen
  //fade(0, 0, 0, 255, 0, 0, 400);  // fade from black to green and back
  colorWipeDelay(strip.Color(0, 255, 0), 100);  // Green
  delay(2000);
  wipe();  // off

  Serial.println("Ready");
}

void loop() {
  //patternInterval = 20;
  //if (millis() - lastUpdate > patternInterval);
  rainbow();  // Flowing rainbow cycle along the whole strip; 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
    Level = 10;
    Serial.println("Button Easy is pressed");
    BtnLedEasyPuls();
    rainbowCycle();  // Rainbow-enhanced theaterChase variant; this one blocks the rest of the programm until finished. I would like this one to continue running during the game.
    RunGameWithSerialAndDelay();
    AllMagnetsOn();
    colorWipeDelay(strip.Color(0, 255, 0), 100);  // Green
    colorWipeDelay(strip.Color(0, 0, 0), 100);    // Off
  }

  if (digitalRead(Btn_Hard) == LOW) {  //als knop "Moeilijk" wordt ingedrukt; set moeilijkheid naar Moeilijk en start spel
    Level = 3;
    Serial.println("Button Hard is pressed");
    BtnLedHardPuls();
    theaterChaseRainbow();
    RunGame();
    AllMagnetsOn();
    colorWipe(strip.Color(0, 255, 0));  // Green
    colorWipe(strip.Color(0, 0, 0));    // 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 RunGameWithSerialAndDelay() {  //works, but not together with NeoPixel routine, this void is now not in use, but remains in the code until the millis() void works 
  for (int i = 0; i < arraySize; i++) {
    randomSeed(analogRead(A0));
    RandTime = random(100, 1000);
    randPipe = getRandomEntry();
    delay(RandTime * Level);
    Serial.print("RandTime: ");
    Serial.println(RandTime);
    Serial.print("Delay value: ");
    Serial.println(RandTime * Level);
    Serial.print("Pipe nr: ");
    Serial.println(randPipe);
    Serial.print("Pin nr: ");
    Serial.println(randPipe + 2);
    Serial.println();
    digitalWrite(randPipe + 2, HIGH);
  }
  Serial.println("Finished");
}

void RunGameVs2NoDelay() {  //not working, but left in code to make comparisson & study easier
  for (int i = 0; i < arraySize; i++) {
    CurrentTime = millis();
    randomSeed(analogRead(A0));
    RandTime = random(100, 1000);
    randPipe = getRandomEntry();
    if (CurrentTime - prevTimeT2 > RandTime) {
      digitalWrite(randPipe + 2, HIGH);
      prevTimeT2 = CurrentTime;
    }
  }
}

void RunGame() {
  static unsigned long prevTimeT2;
  static int RandTime = 316;
  static int i;           ///...  for (int i = 0; i < arraySize; i++) {

// time to?
  CurrentTime = millis();
  if (CurrentTime - prevTimeT2 < RandTime)
    return;

// select and drop random
  randPipe = getRandomEntry();
  digitalWrite(randPipe + 2, HIGH);         // laat de honden los!
    Serial.print("RandTime: ");
    Serial.println(RandTime);
    Serial.print("Delay value: ");
    Serial.println(RandTime * Level);
    Serial.print("Pipe nr: ");
    Serial.println(randPipe);
    Serial.print("Pin nr: ");
    Serial.println(randPipe + 2);
    Serial.println();

// cycle
  i++; if (i >= arraySize) i = 0; 
  
// schedule 
  RandTime = random(100, 1000);
  prevTimeT2 = CurrentTime;
}

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

  if (currentTime - prevTimeT1 > 200) {

    prevTimeT1 = currentTime;
    // 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 currentTime = millis();

  if (currentTime - prevTimeT1 > 200) {

    prevTimeT1 = currentTime;
    // 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 fade(int redStartValue, int redEndValue, int greenStartValue, int greenEndValue, int blueStartValue, int blueEndValue, int totalSteps) {
  static float redIncrement, greenIncrement, blueIncrement;
  static float red, green, blue;
  static boolean fadeUp = false;

  if (fadeStep == 0) {  // first step is to initialise the initial colour and increments
    red = redStartValue;
    green = greenStartValue;
    blue = blueStartValue;
    fadeUp = false;

    redIncrement = (float)(redEndValue - redStartValue) / (float)totalSteps;
    greenIncrement = (float)(greenEndValue - greenStartValue) / (float)totalSteps;
    blueIncrement = (float)(blueEndValue - blueStartValue) / (float)totalSteps;
    fadeStep = 1;  // next time the function is called start the fade
  } else {         // all other steps make a new colour and display it
    // make new colour
    red += redIncrement;
    green += greenIncrement;
    blue += blueIncrement;

    // set up the pixel buffer
    for (int i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, strip.Color((int)red, (int)green, (int)blue));
    }
    // now display it
    strip.show();
    fadeStep += 1;                 // go on to next step
    if (fadeStep >= totalSteps) {  // finished fade
      if (fadeUp) {                // finished fade up and back
        fadeStep = 0;
        return;  // so next call re-calabrates the increments
      }
      // now fade back
      fadeUp = true;
      redIncrement = -redIncrement;
      greenIncrement = -greenIncrement;
      blueIncrement = -blueIncrement;
      fadeStep = 1;  // don't calculate the increments again but start at first change
    }
  }
}

void rainbow() {  // modified from Adafruit example to make it a state machine
  static uint16_t j = 0;
  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, Wheel((i + j) & 255));
  }
  strip.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;
  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
  }
  strip.show();
  j++;
  if (j >= 256 * 5) j = 0;
  lastUpdate = 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;
  if (on) {
    for (int i = 0; i < strip.numPixels(); i = i + 3) {
      strip.setPixelColor(i + q, Wheel((i + j) % 255));  //turn every third pixel on
    }
  } else {
    for (int i = 0; i < strip.numPixels(); i = i + 3) {
      strip.setPixelColor(i + q, 0);  //turn every third pixel off
    }
  }
  on = !on;      // toggel pixelse on or off for next time
  strip.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;
  }
  lastUpdate = millis();  // time for next change to the display
}

void colorWipe(uint32_t color) {  // modified from Adafruit example to make it a state machine
  static int i = 0;
  strip.setPixelColor(i, color);
  strip.show();
  i++;
  if (i >= strip.numPixels()) {
    i = 0;
    wipe();  // blank out strip
  }
  lastUpdate = 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 < strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

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

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);
}

After a careful baking, the RunGame() can be further modified.

prevTime2 and RandTime do not to be global. As local variables, they can be given different names.

CurrentTime, a global, should be set nowhere except in the loop() function. The code elow assumes the existence of a global variable now, set to millis() once per loop.

arraySize, and in fact the entire mechanism of the for loop, is no longer relevant. Elements supported that can be removed.

void RunGame()
{
  static unsigned long lastTime;
  static int nextTime = 316;

  if (now - lastTime < nextTime)
    return;

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

  nextTime = random(100, 1000);
  lastTime = now;
}

The only remaining global is randPipe, this might or might not be important. I left it be.

There is an aesthetic issue, without knowing how the whole game functions it is impossible to say if it must be fixed.

a7

In theatreChaseRainbow(), you do not have the all the parts of the timing idiom, that means it is not taking care of its own timing in the same manner as, say, RunGame() now does.

All I see is

      lastUpdate = millis();  // time for next change to the display`

The complete framework can be seen in RunGame().

void theaterChaseRainbow() {  // modified from Adafruit example to make it a state machine
  static unsigned long lastUpdate;  // our own time stamp

// check to see if it is time to run - if not, just return
  if (now - lastUpdate < INTERVAL)
    return;


// OK, INTERVAL has elapsed since last time so
// here you do whatever to caculate the new strip pixels and then

  strip.show();


// move the time marker up. Use the  sketch-wide 'now' time. 
  lastUpdate = now;  // time for next change to the display
}

It's the idiomatic pattern, just with a reversed test.


void theaterChaseRainbow() { 
  static unsigned long lastUpdate; 

  if (now - lastUpdate >= INTERVAL) {

    // OK, INTERVAL has elapsed since last time so
    // here you do whatever to caculate the new strip pixels and then

    strip.show();

// move the time marker up:
    lastUpdate = now;  // time for next change to the display
  }
}

Makes no difference. I like the obvious instant return in version one, "is it time? no - get out of here".

Ppl dignify the global time variable by calling it currentMillis, and assigning it the value of millis() once at the top of the loop() function, every time through.

I don't answer to anybody and have no need to impress myself with fancy variable names, so I've always just called it now.

a7

1 Like

Hi a7,

I took some time to study the code and your remarks further. The NeoPixel part is now running as it should, but the game part is not; when I press the button to start the game, only 1 random pipe is dropped and then the program falls back into the loop without dropping the other 9 pipes.

arraySize, and in fact the entire mechanism of the for loop, is no longer relevant. Elements supported that can be removed.

I think this is where the code arraySize is being used; as long as there are pipes left hanging, the arraySize is not equal to the "i" indicator, so there is still something to do. With every dropping pipe, the i-indicator is increased by 1 unit the number meets the array size (if I understand the code correctly).

I tried various combinations within the loop and/or within the game void, but I get either the correct random wait time, but only 1 dropping pipe, or I get all the pipes, but they all drop at the same time. I think this part of the code should be in the game void. When it is in the loop, it gets triggered every cycle and therefor it's done in 10 cycles.

The function should be; when the button is pressed, all pipes fall at random intervals and in a random order. Maybe for some inspiration; when you search on google images for "stokvangen" (i'm sorry it's in Dutch for all folks out here, but I couldn't find a proper English name for this game).

The code that I expected to work is the following, but that removes the Timer logic;

void RunGame() {
  static unsigned long lastTime = 3000;
  static int nextTime = random(100, 1000);
  static int i;           ///...  for (int i = 0; i < arraySize; i++) {

  if (now - lastTime < nextTime)
    return;

  for (int i = 0; i < arraySize; i++) { // logic to update the array after each drop

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

    // cycle
    //i++; if (i >= arraySize) i = 0;
  
  // schedule
  nextTime = random(100, 1000);
  lastTime = now;
  }

    AllMagnetsOn(); // game is finished, reset for the next run
    colorWipeDelay(strip.Color(0, 255, 0), 100);  // Neopixel Green ring to show that pipes can be put back in place
    colorWipeDelay(strip.Color(0, 0, 0), 100);    // Off
    Serial.println("Ready for next run");
}

I feel that we are nearly there... I appreciate the help I'm getting so far!

NP, that's what we here for.

I'm under the umbrella, so it will be a minute, but wanted to catch you and ask that you (always) post the complete sketch.

Editing your old version is work, and it is fraught. We might get it wrong, you might forget one or more other things you've changed.

I believe I once had your code dropping hammers randomly timed.

It may well not have stopped doing after arraySize number of hammers, but that easy enough and yes, would involve counting and stopping.

It would not involve a for loop, you are trying to get it to work in a magical fashion which it will not. Every call is a brand new for loop…

a7

Ah, yes, I'm sorry for that.
Hereby the full code (there needs to be some cleaning up to be done when I'm finished);

//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

//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 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 numberOfCases = 4;                                // how many case statements or patterns you have

//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 PINforControl 12  //Neopixel pin with PWM
#define NUMPIXELS1 12     // number of LEDs on strip
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip(NUMPIXELS1, PINforControl, NEO_GRB + NEO_KHZ800);  // Declare our NeoPixel strip object

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

  randomSeed(analogRead(A0));            // Pick a number from those remaining
  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;
  strip.begin();  // INITIALIZE NeoPixel strip object (REQUIRED)
  wipe();
  strip.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);

  //fade(0, 255, 0, 64, 0, 0, 400);  // fade from black to orange and back
  colorWipeDelay(strip.Color(255, 50, 0), 100);  // Orange
  AllMagnetsOn();                                //Alle magneten inschakelen
  //fade(0, 0, 0, 255, 0, 0, 400);  // fade from black to green and back
  colorWipeDelay(strip.Color(0, 255, 0), 100);  // Green
  delay(2000);
  wipe();  // off

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

void loop() {
  //patternInterval = 20;
  //if (millis() - lastUpdate > patternInterval);
  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
    Level = 10;
    static int i;
    Serial.println("Button Easy is pressed");
    //for (int i = 0; i < arraySize; i++) {
      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
    Level = 3;
    Serial.println("Button Hard is pressed");
    BtnLedHardPuls();
    theaterChaseRainbow();
    RunGame();
    AllMagnetsOn();
    colorWipe(strip.Color(0, 255, 0));  // Green
    colorWipe(strip.Color(0, 0, 0));    // Off
    Serial.println("Ready for next run");
  }

}

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 RunGameWithSerialAndDelay() {  //works, but not together with NeoPixel routine, this void is now not in use, but remains in the code until the millis() void works
  for (int i = 0; i < arraySize; i++) {
    randomSeed(analogRead(A0));
    nextTime = random(100, 1000);
    randPipe = getRandomEntry();
    delay(nextTime * Level);
    Serial.print("nextTime: ");
    Serial.println(nextTime);
    Serial.print("Delay value: ");
    Serial.println(nextTime * Level);
    Serial.print("Pipe nr: ");
    Serial.println(randPipe);
    Serial.print("Pin nr: ");
    Serial.println(randPipe + 2);
    Serial.println();
    digitalWrite(randPipe + 2, HIGH);
  }
  Serial.println("Finished");
}

void RunGameVs2NoDelay() {  //not working, but left in code to make comparisson & study easier
  for (int i = 0; i < arraySize; i++) {
    now = millis();
    randomSeed(analogRead(A0));
    nextTime = random(100, 1000);
    randPipe = getRandomEntry();
    if (now - prevTimeT2 > nextTime) {
      digitalWrite(randPipe + 2, HIGH);
      prevTimeT2 = now;
    }
  }
}

void RunGame() {
  static unsigned long lastTime = 3000;
  static int nextTime = random(100, 1000);
  static int i;           ///...  for (int i = 0; i < arraySize; i++) {

  if (now - lastTime < nextTime)
    return;

  for (int i = 0; i < arraySize; i++) { // logic to update the array after each drop

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

    // cycle
    //i++; if (i >= arraySize) i = 0;
  
  // schedule
  nextTime = random(100, 1000);
  lastTime = now;
  }

    AllMagnetsOn(); // game is finished, reset for the next run
    colorWipeDelay(strip.Color(0, 255, 0), 100);  // Neopixel Green ring to show that pipes can be put back in place
    colorWipeDelay(strip.Color(0, 0, 0), 100);    // NeoPixel Off
    Serial.println("Ready for next run");
}

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 fade(int redStartValue, int redEndValue, int greenStartValue, int greenEndValue, int blueStartValue, int blueEndValue, int totalSteps) {
  static float redIncrement, greenIncrement, blueIncrement;
  static float red, green, blue;
  static boolean fadeUp = false;

  if (fadeStep == 0) {  // first step is to initialise the initial colour and increments
    red = redStartValue;
    green = greenStartValue;
    blue = blueStartValue;
    fadeUp = false;

    redIncrement = (float)(redEndValue - redStartValue) / (float)totalSteps;
    greenIncrement = (float)(greenEndValue - greenStartValue) / (float)totalSteps;
    blueIncrement = (float)(blueEndValue - blueStartValue) / (float)totalSteps;
    fadeStep = 1;  // next time the function is called start the fade
  } else {         // all other steps make a new colour and display it
    // make new colour
    red += redIncrement;
    green += greenIncrement;
    blue += blueIncrement;

    // set up the pixel buffer
    for (int i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, strip.Color((int)red, (int)green, (int)blue));
    }
    // now display it
    strip.show();
    fadeStep += 1;                 // go on to next step
    if (fadeStep >= totalSteps) {  // finished fade
      if (fadeUp) {                // finished fade up and back
        fadeStep = 0;
        return;  // so next call re-calabrates the increments
      }
      // now fade back
      fadeUp = true;
      redIncrement = -redIncrement;
      greenIncrement = -greenIncrement;
      blueIncrement = -blueIncrement;
      fadeStep = 1;  // don't calculate the increments again but start at first change
    }
  }
}

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 < 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
}

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 < strip.numPixels(); i++) {
    strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
  }
  strip.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 < 50)
    return;

  if (on) {
    for (int i = 0; i < strip.numPixels(); i = i + 3) {
      strip.setPixelColor(i + q, Wheel((i + j) % 255));  //turn every third pixel on
    }
  } else {
    for (int i = 0; i < strip.numPixels(); i = i + 3) {
      strip.setPixelColor(i + q, 0);  //turn every third pixel off
    }
  }
  on = !on;      // toggel pixelse on or off for next time
  strip.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 colorWipe(uint32_t color) {  // modified from Adafruit example to make it a state machine
  static int i = 0;
  static unsigned long lastUpdate4;
    if (now - lastUpdate4 < 100)
    return;
  strip.setPixelColor(i, color);
  strip.show();
  i++;
  if (i >= strip.numPixels()) {
    i = 0;
    wipe();  // blank out strip
  }
  lastUpdate4 = 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 < strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

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

uint32_t Wheel(byte WheelPos) {

OK, you left off a few lines but I grabbed them from earlier.

You seem to not yet understand. Either

you can have RunGame() "own" the processor for the entire sequence of hammer drops, using a for loop to step through them and a millis() timing mechanism (which would be no more useful than a delay) to dole them out

OR

you can use a RunGame() like I wrote, which is meant to be called extremely frequently and which will drop a hammer after a certain random delay since the last one. Dropped.

With the first, you cannot give any attention to your other state machinery.

With the second, you have to handle starting and finishing the game differently, this

    Level = 3;
    Serial.println("Button Hard is pressed");
    BtnLedHardPuls();
    theaterChaseRainbow();
    RunGame();
    AllMagnetsOn();
    colorWipe(strip.Color(0, 255, 0));  // Green
    colorWipe(strip.Color(0, 0, 0));    // Off
    Serial.println("Ready for next run");

only makes sense if RunGame() runs once and drops N hammers. "Ready for next run" only after it finishes.

When RunGame() is a state machine that (might) drop the next hammer, it will be called thousands of times, usually doing nothing. But you can also call your other state machines and give them a chance to do something.

I'm not going to keep changing RunGame() back for you. It is time for you to start understanding how these things are meant to work. Put your finger on the code and step through it like you are a dumb computer - you may see why the code fails your expectations.

Also I note

  pinMode(20, INPUT_PULLUP);  //... why have constants if you don't use them

and you still haven't removed randomSeed() from everywhere and placed it once in setup(). You can trust me or google it. randomSeed() is usually called once, early on, to make each game sequence have the possibility to not be exactly the same.

  randomSeed(analogRead(A0));            // no. remove this line
  int index = random(remainingNumbers);  // 0 to remainingNumbers - 1

BTW - nice random getRandomEntry().

When you are getting hammer drops but RunGame has no means to stop dropping hammers, post that code and we can fix that.

Or try yourself:

intialise a counter

  count the hammers as they drop when RunGame decides it is time to

  see if N number of hammers have dropped and decide

whether to loop on this and do more or be finished.

L8R

a7

1 Like

Please see the below code and result for your reference,

class TimerOn {
  private:
    bool prevInput = false;
    bool state = false;
    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 100L   // Random Min x milliseconds
#define RANDOM_MAX 1000L  // Random Mzx y milliseconds

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

void setup() {
  Serial.begin(115200);
  for (uint8_t index = 0; index < numberOfPipes; index++) {
    randomSeed(analogRead(0));                        // Use an unconnected pin for randomSeed()
    long newDelay = random(RANDOM_MIN, RANDOM_MAX);   // Initial Random Time n milliseconds
    pipeTimerOns[index].setTimeDelay(newDelay);       // Set delay time to object
  }
}

void loop() {
  RunGame();
}

void RunGame() {
  for (uint8_t index = 0; index < numberOfPipes; index++) {
    pipeTimerOns[index].timerOn(true);                  // True = Enable, 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

      // START PRINT - NEW PIPE AND TIMER INFO.
      char charArray[10];
      unsigned long currTime = micros();
      sprintf(charArray, "T%06lu", currTime / 1000UL);
      Serial.print(charArray);
      Serial.print(" - New Pipe ");
      Serial.print(index + 1);
      Serial.print(", Next Time: ≈");
      sprintf(charArray, "T%06lu", (currTime + newDelay * 1000UL) / 1000UL + 1UL);
      Serial.println(charArray);
      // END PRINT - NEW PIPE AND TIMER INFO.
    }
  }
}

Result:

T000228 - New Pipe 2, Next Time: ≈T000742
T000260 - New Pipe 7, Next Time: ≈T000819
T000272 - New Pipe 3, Next Time: ≈T000607
T000456 - New Pipe 1, Next Time: ≈T000644
T000488 - New Pipe 6, Next Time: ≈T000604
T000604 - New Pipe 6, Next Time: ≈T001437
T000607 - New Pipe 3, Next Time: ≈T000756
T000644 - New Pipe 1, Next Time: ≈T001602
T000716 - New Pipe 5, Next Time: ≈T001134
T000742 - New Pipe 2, Next Time: ≈T000860
T000756 - New Pipe 3, Next Time: ≈T000946
T000819 - New Pipe 7, Next Time: ≈T001358
T000860 - New Pipe 2, Next Time: ≈T001615
T000932 - New Pipe 8, Next Time: ≈T001456
T000944 - New Pipe 4, Next Time: ≈T001328
T000946 - New Pipe 3, Next Time: ≈T001638
T001134 - New Pipe 5, Next Time: ≈T002051
T001328 - New Pipe 4, Next Time: ≈T001839
T001358 - New Pipe 7, Next Time: ≈T002333
T001437 - New Pipe 6, Next Time: ≈T001853
T001456 - New Pipe 8, Next Time: ≈T001712
T001602 - New Pipe 1, Next Time: ≈T002351
T001615 - New Pipe 2, Next Time: ≈T001908
T001638 - New Pipe 3, Next Time: ≈T002407
T001712 - New Pipe 8, Next Time: ≈T002478
T001839 - New Pipe 4, Next Time: ≈T002638
T001853 - New Pipe 6, Next Time: ≈T002492
T001908 - New Pipe 2, Next Time: ≈T002043
...
...
...

Arduino Simulator: RandomPipes.ino

μB

OK, will that allow the LED effect to run in parallel?

Please post the complete sketch you used to produce your output. It would be nice to know what variables you used, the setup() and... the name of the library you explot.

Then it might be tested. By not you.

We are trying to make RunGame() compatible with rainbowCycle().

Both must be non blocking.

Besides from under the umbrella it does not look like your for loop will drop the hammers at random times.

There is no need for any timer library at this time. This is code anyone should be able to understand, whether or not they go on to choose to outsource the functionality whe they need it.

And please, stop coding nonsense about randomSeed(). The call to randomSeed() is out of place there.

a7

@alto777,
Thank you for your feedback.

OK, I got it running now. It took me a few beers :-).

The trick was indeed the counter; every time a pipe drops, the counter increases by 1. As long as the counter is not equal to the arraysize, there are still pipes to be dropped and the program must remain within the sub-loop.

Also 1 small mistake found; the timer did not take into account the set level (easy or hard), thats why i noticed the pipes almost dropping simultatious.

pinMode(20, INPUT_PULLUP); //... why have constants if you don't use them

The pin is used to detect a button push. When I leave this line out, it imidiatly detects a button signal. But I keep an open mind for code improvement. All but 1 randomSeed are gone now.

BTW - nice random getRandomEntry().

Unfortunatly not my own design. I also borrowed this code, but I put the source in the top of the code to give credits to the owner.

Again, many thanks for your help!

So, for future reference and others interested, hereby the code that's working;

//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

//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 PINforControl 12  //Neopixel pin with PWM
#define NUMPIXELS1 12     // number of LEDs on strip
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip(NUMPIXELS1, PINforControl, 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(A0));            

  //Neopixel startup;
  strip.begin();  // INITIALIZE NeoPixel strip object (REQUIRED)
  wipe();
  //strip.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(strip.Color(255, 50, 0), 100);  // Orange
  AllMagnetsOn();                                //Alle magneten inschakelen
  colorWipeDelay(strip.Color(0, 255, 0), 100);  // Green
  delay(2000);
  colorWipeDelay(strip.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(strip.Color(0, 255, 0), 100);  // Neopixel Green ring to show that pipes can be put back in place
    delay(500);
    colorWipeDelay(strip.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(strip.Color(0, 255, 0), 100);  // Neopixel Green ring to show that pipes can be put back in place
    delay(500);
    colorWipeDelay(strip.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);
  //static int counter;           //counts every time a pipe has dropped
  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++;
}

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 fade(int redStartValue, int redEndValue, int greenStartValue, int greenEndValue, int blueStartValue, int blueEndValue, int totalSteps) {
  static float redIncrement, greenIncrement, blueIncrement;
  static float red, green, blue;
  static boolean fadeUp = false;

  if (fadeStep == 0) {  // first step is to initialise the initial colour and increments
    red = redStartValue;
    green = greenStartValue;
    blue = blueStartValue;
    fadeUp = false;

    redIncrement = (float)(redEndValue - redStartValue) / (float)totalSteps;
    greenIncrement = (float)(greenEndValue - greenStartValue) / (float)totalSteps;
    blueIncrement = (float)(blueEndValue - blueStartValue) / (float)totalSteps;
    fadeStep = 1;  // next time the function is called start the fade
  } else {         // all other steps make a new colour and display it
    // make new colour
    red += redIncrement;
    green += greenIncrement;
    blue += blueIncrement;

    // set up the pixel buffer
    for (int i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, strip.Color((int)red, (int)green, (int)blue));
    }
    // now display it
    strip.show();
    fadeStep += 1;                 // go on to next step
    if (fadeStep >= totalSteps) {  // finished fade
      if (fadeUp) {                // finished fade up and back
        fadeStep = 0;
        return;  // so next call re-calabrates the increments
      }
      // now fade back
      fadeUp = true;
      redIncrement = -redIncrement;
      greenIncrement = -greenIncrement;
      blueIncrement = -blueIncrement;
      fadeStep = 1;  // don't calculate the increments again but start at first change
    }
  }
}

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 < 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
}

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 < strip.numPixels(); i++) {
    strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
  }
  strip.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 < 50)
    return;

  if (on) {
    for (int i = 0; i < strip.numPixels(); i = i + 3) {
      strip.setPixelColor(i + q, Wheel((i + j) % 255));  //turn every third pixel on
    }
  } else {
    for (int i = 0; i < strip.numPixels(); i = i + 3) {
      strip.setPixelColor(i + q, 0);  //turn every third pixel off
    }
  }
  on = !on;      // toggel pixelse on or off for next time
  strip.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 < strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

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

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);
}
1 Like

Hi Hamarn,

Thank you for your input and your attempt to help me.
Fortunatly, I just finished (and posted) a working code for my game.

With kind regards,
Danny

1 Like

@hamarn !

I tried working with your code, and only turned up a struct TimerOn somewhere, which looked plausible but didn't have a certain method.

Can you post a complete sketch including the TimerOn object? I can't make sense of how you were doing the timing by the method names or the structure of the logic. It would be easier to read and run for myself.

TIA

a7