// Simulation:
// https://wokwi.com/projects/368604124891332609
// A recipe for non-blocking arduino code can be found here:
// https://github.com/nosknut/arduino-course-v2023/blob/main/IELET1002/ArduinoExampleCode/FromBlockingToNonBlocking/FromBlockingToNonBlocking.ino
/////////////////////////////////////
///////////// LED Code //////////////
/////////////////////////////////////
const int LED_1_PIN = 6;
const int LED_2_PIN = 10;
void setupLeds()
{
pinMode(LED_1_PIN, OUTPUT);
pinMode(LED_2_PIN, OUTPUT);
}
/////////////////////////////////////
///////////// Rgb Code //////////////
/////////////////////////////////////
const int RGB_RED_PIN = 9;
const int RGB_GREEN_PIN = 8;
const int RGB_BLUE_PIN = 7;
void setupRgb()
{
pinMode(RGB_RED_PIN, OUTPUT);
pinMode(RGB_GREEN_PIN, OUTPUT);
pinMode(RGB_BLUE_PIN, OUTPUT);
}
/////////////////////////////////////
/////// Button 1 State Code /////////
/////////////////////////////////////
const int BUTTON_1_PIN = 2;
bool button1State = false;
// The time when the button state last changed
unsigned long button1DebounceTimer = 0;
unsigned long button1DebounceTimeout = 50;
// Updates the global variable button1State with
// the current debounced state of the button.
// Do not call this function directly. Use updateButton1 instead.
// https://docs.arduino.cc/built-in-examples/digital/Debounce
void updateButton1State()
{
if ((millis() - button1DebounceTimer) < button1DebounceTimeout)
{
// Exit the function if the timeout has not expired
return;
}
// Because the button is wired as a pullup circuit,
// the pin will detect a LOW signal when the button is pressed,
// and a HIGH signal when the button is not pressed.
bool newState = digitalRead(BUTTON_1_PIN) == LOW;
if (newState != button1State)
{
// Reset the debounce timer if the state has changed
button1DebounceTimer = millis();
}
// Update the global variable that
// the rest of the program uses
button1State = newState;
}
/////////////////////////////////////
///// Button 1 Edge Detection ///////
/////////////////////////////////////
/*
Becomes true for one loop when the button state has a rising edge.
A rising edge is when the button state changes from 0 to 1
Example:
if (button1RisingEdge)
{
// Do something once per button press
}
*/
bool button1RisingEdge = false;
// The oposite of button1RisingEdge
bool button1FallingEdge = false;
bool button1PreviousState = false;
// Updates the global variables button1RisingEdge and button1RisingEdge.
// Do not call this function directly. Use updateButton1 instead.
// https://docs.arduino.cc/built-in-examples/digital/StateChangeDetection
void updateButton1EdgeDetection()
{
// A rising edge is detected when the state is 1 and it used to be 0: 1 && !0
button1RisingEdge = button1State && !button1PreviousState;
// A falling edge is detected when the state is 0 and it used to be 1: !0 && 1
button1FallingEdge = !button1State && button1PreviousState;
// Update the previous state for the next time this function is called
button1PreviousState = button1State;
}
/////////////////////////////////////
//////////// Button 1 ///////////////
/////////////////////////////////////
void setupButton1()
{
pinMode(BUTTON_1_PIN, INPUT_PULLUP);
}
// Updates everything needed to use the global variables
// button1State, button1RisingEdge and button1FallingEdge.
// Call this function in void loop().
void updateButton1()
{
updateButton1State();
updateButton1EdgeDetection();
}
/////////////////////////////////////
/////// Button 2 State Code /////////
/////////////////////////////////////
const int BUTTON_2_PIN = 12;
bool button2State = false;
// The time when the button state last changed
unsigned long button2DebounceTimer = 0;
unsigned long button2DebounceTimeout = 50;
// Updates the global variable button2State with
// the current debounced state of the button.
// Do not call this function directly. Use updateButton2 instead.
// https://docs.arduino.cc/built-in-examples/digital/Debounce
void updateButton2State()
{
if ((millis() - button2DebounceTimer) < button2DebounceTimeout)
{
// Exit the function if the timeout has not expired
return;
}
// Because the button is wired as a pullup circuit,
// the pin will detect a LOW signal when the button is pressed,
// and a HIGH signal when the button is not pressed.
bool newState = digitalRead(BUTTON_2_PIN) == LOW;
if (newState != button2State)
{
// Reset the debounce timer if the state has changed
button2DebounceTimer = millis();
}
// Update the global variable that
// the rest of the program uses
button2State = newState;
}
/////////////////////////////////////
///// Button 2 Edge Detection ///////
/////////////////////////////////////
/*
Becomes true for one loop when the button state has a rising edge.
A rising edge is when the button state changes from 0 to 1
Example:
if (button2RisingEdge)
{
// Do something once per button press
}
*/
bool button2RisingEdge = false;
// The oposite of button2RisingEdge
bool button2FallingEdge = false;
bool button2PreviousState = false;
// Updates the global variables button2RisingEdge and button2RisingEdge.
// Do not call this function directly. Use updateButton2 instead.
// https://docs.arduino.cc/built-in-examples/digital/StateChangeDetection
void updateButton2EdgeDetection()
{
// A rising edge is detected when the state is 2 and it used to be 0: 2 && !0
button2RisingEdge = button2State && !button2PreviousState;
// A falling edge is detected when the state is 0 and it used to be 1: !0 && 1
button2FallingEdge = !button2State && button2PreviousState;
// Update the previous state for the next time this function is called
button2PreviousState = button2State;
}
/////////////////////////////////////
//////////// Button 2 ///////////////
/////////////////////////////////////
void setupButton2()
{
pinMode(BUTTON_2_PIN, INPUT_PULLUP);
}
// Updates everything needed to use the global variables
// button2State, button2RisingEdge and button2FallingEdge.
// Call this function in void loop().
void updateButton2()
{
updateButton2State();
updateButton2EdgeDetection();
}
/////////////////////////////////////
/////// Square Modulator Code ///////
/////////////////////////////////////
/**
* @param frequency The frequency of the square wave in Hz (Hertz).
* @param amplitude The maximum value of the wave.
* @param offset The offset of the square wave in ms (milliseconds).
* @return A square wave with the given frequency and offset.
*/
long getSquareWave(double frequency, long amplitude, long offset)
{
// The period of the square wave in ms (milliseconds)
// the period is the time in ms (milliseconds) it takes for the
// wave to complete one cycle (from 0 to 1 and back to 0)
unsigned long period = 1000 / frequency;
// The time since the program started + the offset
// that skews the function in time.
unsigned long time = millis() + offset;
// https://www.arduino.cc/reference/en/language/structure/arithmetic-operators/remainder/
// https://infosys.beckhoff.com/content/1033/tf5100_tc3_nc_i/Images/gif/3292507403__en-US__Web.gif
double periodicTime = time % period;
unsigned long halfPeriod = period / 2;
// The square wave is 1 for the first half of the
// period, and 0 for the second half of the period
bool currentSquareWaveValue = periodicTime < halfPeriod;
// Generate a value that is either equal to 0 or the amplitude
if (currentSquareWaveValue == 1)
{
return amplitude;
}
else
{
return 0;
}
}
/////////////////////////////////////
////// Triangle Modulator Code //////
/////////////////////////////////////
/**
* Generates the following type of signal:
* https://www.statisticshowto.com/wp-content/uploads/2019/12/triangle-wave-function.png
* @param frequency The frequency of the wave in Hz (Hertz).
* @param amplitude The maximum value of the wave.
* @param offset The offset of the wave in ms (milliseconds).
* @return A triangle wave with the given frequency and maximum value.
*/
unsigned long getTriangleWave(double frequency, long amplitude, long offset)
{
// The period of the square wave in ms (milliseconds)
// the period is the time in ms (milliseconds) it takes for the
// wave to complete one cycle (from 0 to max and back to 0)
unsigned long period = 1000 / frequency;
// The time since the program started + the offset
// that skews the function in time.
unsigned long time = millis() + offset;
// https://www.arduino.cc/reference/en/language/structure/arithmetic-operators/remainder/
// https://infosys.beckhoff.com/content/1033/tf5100_tc3_nc_i/Images/gif/3292507403__en-US__Web.gif
unsigned long periodicTime = time % period;
unsigned long halfPeriod = period / 2;
// The signal is 0 at the start of the period,
// and increases linearly until half way through the period
// where it is equal to the amplitude.
// Then it decreases linearly until the end of the period
// where it is equal to 0 again.
if (periodicTime < halfPeriod)
{
// https://www.arduino.cc/reference/en/language/functions/math/map/
return map(periodicTime, 0, halfPeriod, 0, amplitude);
}
else
{
// https://www.arduino.cc/reference/en/language/functions/math/map/
return map(periodicTime, halfPeriod, period, amplitude, 0);
}
}
/////////////////////////////////////
///// Update Winner Led Code ////////
/////////////////////////////////////
int winnerLedPin = 0;
bool runCelebrateWinnerLed = false;
void startWinnerCelebrationLed(int ledPin)
{
winnerLedPin = ledPin;
runCelebrateWinnerLed = true;
}
void stopWinnerCelebrationLed()
{
runCelebrateWinnerLed = false;
digitalWrite(winnerLedPin, LOW);
digitalWrite(RGB_GREEN_PIN, LOW);
}
// Rapidly fades the winner LED in and out
void updateWinnerCelebrationLed()
{
if (!runCelebrateWinnerLed)
{
// Exit the function
return;
}
analogWrite(winnerLedPin, getTriangleWave(1, 255, 0));
digitalWrite(RGB_RED_PIN, LOW);
digitalWrite(RGB_GREEN_PIN, getSquareWave(1, 1, 0));
digitalWrite(RGB_BLUE_PIN, LOW);
}
/////////////////////////////////////
///// Update Looser Led Code ////////
/////////////////////////////////////
int looserLedPin = 0;
bool runHumiliateLooserLed = false;
void startLooserHumiliationLed(int ledPin)
{
looserLedPin = ledPin;
runHumiliateLooserLed = true;
}
void stopLooserHumiliationLed()
{
runHumiliateLooserLed = false;
digitalWrite(looserLedPin, LOW);
digitalWrite(RGB_RED_PIN, LOW);
}
// Rapidly fades the winner LED in and out
void updateLooserHumiliationLed()
{
if (!runHumiliateLooserLed)
{
// Exit the function
return;
}
analogWrite(looserLedPin, getSquareWave(4, 255, 0));
digitalWrite(RGB_RED_PIN, getSquareWave(4, 1, 0));
digitalWrite(RGB_GREEN_PIN, LOW);
digitalWrite(RGB_BLUE_PIN, LOW);
}
/////////////////////////////////////
////////// Buzzer Code //////////////
/////////////////////////////////////
const int BUZZER_PIN = 11;
bool playWinnerCelebrationBuzzer = false;
bool playLooserHumiliationBuzzer = false;
void stopBuzzer()
{
playWinnerCelebrationBuzzer = false;
playLooserHumiliationBuzzer = false;
noTone(BUZZER_PIN);
}
void startWinnerCelebrationBuzz()
{
stopBuzzer();
playWinnerCelebrationBuzzer = true;
}
void startLooserHumiliationBuzz()
{
stopBuzzer();
playLooserHumiliationBuzzer = true;
}
// Plays the winner celebration buzz
// when enabled.
// Do not call this function directly.
// Use updateBuzzer() instead.
void updateWinnerCelebrationBuzz()
{
if (playWinnerCelebrationBuzzer)
{
// The code below uses the same logic as the LED code.
// The frequency of the tone
// changes from 500 Hz to (500 + 1500 = 2000) Hz and back to 500 Hz
// 2 times per second in a triangular pattern.
// https://www.statisticshowto.com/wp-content/uploads/2019/12/triangle-wave-function.png
tone(BUZZER_PIN, getTriangleWave(2, 1500, 0) + 500);
}
}
// Plays the looser humiliation buzz
// when enabled.
// Do not call this function directly.
// Use updateBuzzer() instead.
void updateLooserHumiliationBuzz()
{
if (playLooserHumiliationBuzzer)
{
// The code below uses the same logic as the LED code.
// The frequency of the tone
// switches between 100 Hz and (100 + 100 = 200) Hz
// 4 times per second in a square pattern.
// https://www.statisticshowto.com/wp-content/uploads/2019/12/square-wave-function.png
tone(BUZZER_PIN, getSquareWave(4, 100, 0) + 100);
}
}
// Call this function in void loop()
// to update the tones for the buzzer.
void updateBuzzer()
{
updateWinnerCelebrationBuzz();
updateLooserHumiliationBuzz();
}
void setupBuzzer()
{
pinMode(BUZZER_PIN, OUTPUT);
}
/////////////////////////////////////
////// Winner Celebration Code //////
/////////////////////////////////////
bool celebrateWinner = false;
unsigned long winnerCelebrationTimer = 0;
void startWinnerCelebration(int winnerLedPin)
{
// Start the winner celebration
celebrateWinner = true;
winnerCelebrationTimer = millis();
startWinnerCelebrationLed(winnerLedPin);
startWinnerCelebrationBuzz();
}
// Stops the winner celebration after 3 seconds
// Call this function in void loop()
void updateWinnerCelebration()
{
if (!celebrateWinner)
{
// Exit the function
return;
}
if ((millis() - winnerCelebrationTimer) < 3000)
{
// Exit the function until 3 seconds have passed
return;
}
// Stop the winner celebration
celebrateWinner = false;
stopWinnerCelebrationLed();
stopBuzzer();
}
/////////////////////////////////////
////// Looser Humiliation Code //////
/////////////////////////////////////
bool humiliateLooser = false;
unsigned long looserHumiliationTimer = 0;
void startLooserHumiliation(int looserLedPin)
{
// Start the looser humiliation
humiliateLooser = true;
looserHumiliationTimer = millis();
startLooserHumiliationLed(looserLedPin);
startLooserHumiliationBuzz();
}
// Stops the looser humiliation after 4 seconds
// Call this function in void loop()
void updateLooserHumiliation()
{
if (!humiliateLooser)
{
// Exit the function
return;
}
if ((millis() - looserHumiliationTimer) < 4000)
{
// Exit the function until 4 seconds have passed
return;
}
// Stop the looser humiliation
humiliateLooser = false;
stopLooserHumiliationLed();
stopBuzzer();
}
/////////////////////////////////////
/////// Game Sequence Code //////////
/////////////////////////////////////
int gameSequenceStep = 0;
unsigned long gameSequenceTimer = 0;
unsigned long gameSequenceRandomDuration = 0;
void updateGameSequence()
{
switch (gameSequenceStep)
{
case 0:
digitalWrite(RGB_RED_PIN, HIGH);
digitalWrite(RGB_GREEN_PIN, LOW);
digitalWrite(RGB_BLUE_PIN, LOW);
gameSequenceRandomDuration = random(1000, 3000);
gameSequenceTimer = millis();
gameSequenceStep += 1;
break;
case 1:
// During the random duration
if ((millis() - gameSequenceTimer) >= gameSequenceRandomDuration)
{
gameSequenceStep += 1;
}
if (button1RisingEdge)
{
startLooserHumiliation(LED_1_PIN);
gameSequenceStep += 2;
}
if (button2RisingEdge)
{
startLooserHumiliation(LED_2_PIN);
gameSequenceStep += 2;
}
break;
// After the random duration
case 2:
digitalWrite(RGB_RED_PIN, LOW);
digitalWrite(RGB_GREEN_PIN, HIGH);
digitalWrite(RGB_BLUE_PIN, LOW);
if (button1RisingEdge)
{
startWinnerCelebration(LED_1_PIN);
gameSequenceStep += 1;
}
if (button2RisingEdge)
{
startWinnerCelebration(LED_2_PIN);
gameSequenceStep += 1;
}
break;
case 3:
// Wait for celebration and
// humiliation to end
if (!celebrateWinner && !humiliateLooser)
{
gameSequenceStep = 0;
}
break;
}
}
/////////////////////////////////////
/////////// Main Code ///////////////
/////////////////////////////////////
void setup()
{
Serial.begin(9600);
randomSeed(analogRead(0));
setupRgb();
setupLeds();
setupButton1();
setupButton2();
setupBuzzer();
}
void loop()
{
updateButton1();
updateButton2();
updateGameSequence();
updateWinnerCelebration();
updateLooserHumiliation();
updateWinnerCelebrationLed();
updateLooserHumiliationLed();
updateBuzzer();
}