/*
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 // Game 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("");
}