diff --git a/maboul2.ino b/maboul2.ino new file mode 100644 index 0000000000000000000000000000000000000000..ba24d25b5433643da782f1ca87aded41536b7d49 --- /dev/null +++ b/maboul2.ino @@ -0,0 +1,427 @@ +#include <TM1637.h> +#include <Arduino.h> + + + +// Electric shock duration +const int SHOCK_DURATION = 100; + +// Minimum time duration between 2 electric shocks +const int IDLE_AFTER_SHOCK_DURATION = 1000; + +//Duration for the electrical transformer to switch state +const int DELAY_BETWEEN_SWITCH = 10; + +//Duration for flashing the timer +const int TIMER_FLASH_SWITCH_DURATION = 1000; + +//Timer brightness +const int TIMER_BRIGHTNESS = 7; + +//Ending stage duration +const int STAGE_ENDING_DURATION = 5000; + + +//Pin to deliver a shock +const int SHOCK_PIN = 3; + +//Pin to detect a touch +const int SHOCK_SENSOR_PIN = 4; + +//Clock pins +const int DISPLAY_CLK = 8; +const int DISPLAY_DIO = 9; + +//Min duration to sync both buttons +const int BUTTON_SYNC_MAX_DURATION = 50; +//Buttons pins +const int LEFT_BUTTON_PIN = 7; +const int RIGHT_BUTTON_PIN = 6; + + + +unsigned long leftButtonLastTime = 0; +unsigned long rightButtonLastTime = 0; + +int leftButtonPreviousState = HIGH; +int rightButtonPreviousState = HIGH; + +int leftButtonCurrentState = HIGH; +int rightButtonCurrentState = HIGH; + +unsigned long endingTime = 0; +/* STAGE + * STAGE_IDLE : Display best score + * STAGE_READY : Waiting for the player to release both buttons (and then start the game) + * STAGE_PLAYING: Playing + * STAGE_ENDING : Display player score (flashing) +*/ + +enum Stage { STAGE_IDLE, STAGE_READY, STAGE_PLAYING, STAGE_ENDING}; + +enum Stage currentStage = STAGE_IDLE; + +unsigned long bestTime = 0; + +TM1637 timer(DISPLAY_CLK,DISPLAY_DIO); + + +int currentState = LOW; // the current reading from the input pin +int prevState = LOW; + +unsigned long shock_time = 0; +unsigned long currentTime; +unsigned long lastSwitchShockState = 0; +int previousShockState = LOW; + + +int shock_on = 0; + +int isTimerRunning = 0; +unsigned long timerStartTime = 0; +unsigned long lastTimerUpdate = 0; +unsigned long timerDuration = 0; +int prevTimerDisplayType = 0; + +unsigned long lastTimerFlashSwitch = 0; +int timerFlashValue = 0; + +void setup() { + pinMode(SHOCK_PIN, OUTPUT); // set arduino pin to output mode + + pinMode(LEFT_BUTTON_PIN, INPUT_PULLUP); + pinMode(RIGHT_BUTTON_PIN, INPUT_PULLUP); + pinMode(SHOCK_SENSOR_PIN, INPUT_PULLUP); + + timer.init(); + timer.set(TIMER_BRIGHTNESS); +} + +/** + * Return true if the a given time has expired (with a given duration) + */ +int hasExpired(unsigned long lastTime, int duration){ + return (currentTime - lastTime) > duration; +} + +/** + * Send a electrical shock if needed + */ +void shocking(){ + if(hasExpired(lastSwitchShockState, DELAY_BETWEEN_SWITCH)){ + int nextState; + if(previousShockState == LOW){ + nextState = HIGH; + }else{ + nextState = LOW; + } + digitalWrite(SHOCK_PIN, nextState); + previousShockState = nextState; + lastSwitchShockState = currentTime; + } +} + +/** + * Start shocking + */ +void startShocking(){ + if(hasExpired(shock_time, IDLE_AFTER_SHOCK_DURATION)){ + shock_time = currentTime; + shock_on = 1; + } +} + +/* + * Start the timer + */ +void resetTimer(){ + timerStartTime = currentTime; + prevTimerDisplayType = 0; + timer.clearDisplay(); +} + + + +/** + * Display a duration, short version (seconds, cs) + */ +void timerDisplayShort(unsigned long duration){ + int temp; + timer.display(3, duration/10 % 10); + + temp = duration/100; + if(temp > 0){ + timer.display(2, temp % 10); + } + + temp = duration/1000; + if(temp > 0){ + timer.point(1); + timer.display(1, temp % 10); + } + + temp = duration/10000; + if(temp > 0){ + timer.display(0, temp % 10); + } + +} + +/** + * Display a duration, long version (minutes and seconds) + */ +void timerDisplayLong(unsigned long duration){ + int totalSeconds = duration/1000; + int minutes = totalSeconds / 60; + int seconds = totalSeconds % 60; + + timer.display(3, seconds % 10); + + if(totalSeconds > 9){ + timer.display(2, seconds / 10); + } + + if(minutes > 0){ + timer.display(1, minutes % 10); + timer.point(1); + } + + if(minutes > 9){ + timer.display(0, minutes / 10); + } + +} + +/** + * Display a duration on the timer + */ +void timerDisplay(unsigned long duration){ + int mode = duration >= 100000; + + if(mode == 0){ + timerDisplayShort(duration); + }else{ + if(prevTimerDisplayType == 0){ + timer.clearDisplay(); + prevTimerDisplayType = 1; + } + timerDisplayLong(duration); + } +} + +/** + * Update the timer with the current timer duration + */ +void updateTimer(){ + timerDuration = currentTime - timerStartTime; + int mode = timerDuration >= 100000; + + int updateDelay = (mode == 0) ? 10 : 1000; + + if(hasExpired(lastTimerUpdate, updateDelay) == 1){ + timerDisplay(timerDuration); + lastTimerUpdate = currentTime; + } +} + +/** + * Flashing the timer with a duration + */ +void timerFlashAnimation(unsigned long duration){ + if(hasExpired(lastTimerFlashSwitch, TIMER_FLASH_SWITCH_DURATION)){ + lastTimerFlashSwitch = currentTime; + timerFlashValue = !timerFlashValue; + + if( timerFlashValue == 0 ){ + timer.point(0); + timer.clearDisplay(); + }else{ + timerDisplay(duration); + } + + } +} + +/** + * Exectuted during the idle state + */ +void loopIdle(){} + +/** + * Exectuted during the ready state + */ +void loopReady(){} + +/** + * Exectuted during the playing state + */ +void loopPlaying(){ + updateTimer(); + currentState = digitalRead(SHOCK_SENSOR_PIN); + if(currentState == LOW) { /*Touché !*/ + startShocking(); + } + prevState = currentState; + + if(shock_on == 1){ + shock_on = !hasExpired(shock_time, SHOCK_DURATION); /*FALSE if duration expired, otherwise true*/ + } + + if(shock_on == 1) { + shocking(); + }else{ + digitalWrite(SHOCK_PIN, LOW); + } +} + +/** + * Exectuted during the ending state + */ +void loopEnding(){ + timerFlashAnimation(timerDuration); + if(hasExpired(endingTime, STAGE_ENDING_DURATION)){ + startIdle(); + } +} + + +/** + * Exectuted when the game switched to the playing state + */ +void startPlaying(){ + currentStage = STAGE_PLAYING; + resetTimer(); +} + +/** + * Exectuted when the game switched to the ending state + */ +void startEnding(){ + currentStage = STAGE_ENDING; + if(timerDuration < bestTime || bestTime == 0){ + bestTime = timerDuration; + } + + endingTime = currentTime; +} + +/** + * Exectuted when the game switched to the idle state + */ +void startIdle(){ + timer.point(0); + timer.clearDisplay(); + timerDisplay(bestTime); + currentStage = STAGE_IDLE; +} + +/** + * Exectuted when the game switched to the ready state + */ +void startReady(){ + timer.point(0); + timer.clearDisplay(); + currentStage = STAGE_READY; +} + + +/** + * Detecting buttons events + */ +void listenButtonEvents(){ + int rightButtonState = digitalRead(RIGHT_BUTTON_PIN); + int leftButtonState = digitalRead(LEFT_BUTTON_PIN); + + if(leftButtonCurrentState != leftButtonState){ + leftButtonLastTime = currentTime; + leftButtonCurrentState = leftButtonState; + } + + if(rightButtonCurrentState != rightButtonState){ + rightButtonLastTime = currentTime; + rightButtonCurrentState = rightButtonState; + } + + int leftChanged = (leftButtonCurrentState != leftButtonPreviousState); + int rightChanged = (rightButtonCurrentState != rightButtonPreviousState); + int leftPressed = leftChanged && leftButtonCurrentState == LOW; + int rightPressed = rightChanged && rightButtonCurrentState == LOW; + int bothChanged = leftChanged && rightChanged; + + + if((rightChanged && rightPressed == 0) || (leftChanged && leftPressed ==0)){ + onOneButtonReleased(); + }else if(leftChanged && rightChanged){ + unsigned long last = max(leftButtonLastTime, rightButtonLastTime); + if(hasExpired(last, BUTTON_SYNC_MAX_DURATION)){ + onBothButtonPressed(); + } + }else if(rightChanged && hasExpired(rightButtonLastTime, BUTTON_SYNC_MAX_DURATION)){ + onOneButtonPressed(); + }else if(leftChanged && hasExpired(leftButtonLastTime, BUTTON_SYNC_MAX_DURATION)){ + onOneButtonPressed(); + } +} + +/** + * update the state of the buttons + */ +void updateButtonState(){ + leftButtonPreviousState = leftButtonCurrentState; + rightButtonPreviousState = rightButtonCurrentState; +} + +/** + * When only one button is pressed + */ +void onOneButtonPressed(){ + if(currentStage == STAGE_PLAYING){ + startEnding(); + } + + updateButtonState(); +} + +/** + * When one button is released + */ +void onOneButtonReleased(){ + if(currentStage == STAGE_READY){ + startPlaying(); + } + updateButtonState(); + +} + +/** + * When both button are pressed at the same time + */ +void onBothButtonPressed(){ + if(currentStage == STAGE_PLAYING){ + startIdle(); + }else if(currentStage == STAGE_IDLE){ + startReady(); + } + updateButtonState(); + Serial.println("BOTH PRESSED"); + + +} + +/** + * Main loop + */ +void loop() { + currentTime = millis(); + + switch (currentStage) + { + case STAGE_IDLE: loopIdle();break; + case STAGE_READY: loopReady();break; + case STAGE_PLAYING: loopPlaying();break; + case STAGE_ENDING: loopEnding();break; + default:break; + } + + listenButtonEvents(); +}