// from learnSequencePuzzle.ino
// https://forum.arduino.cc/t/toggle-switch-puzzle-but-sequential/1091460
// https://wokwi.com/projects/357012577248058369
# define SEVEN 5 // puzzle size
# include <SPI.h>
# include <Adafruit_SSD1306.h>
# define SCREEN_WIDTH 128 // OLED display width, in pixels
# define SCREEN_HEIGHT 32 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display1(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
/* Set Display Digits - 4 or 6 */
#define DIGITS 4
/* Digital Pins to TM1637 */
#define CLK 3
#define DIO 2
/* Set up 6-Digit Display */
#if DIGITS == 6
#include <TM1637TinyDisplay6.h>
TM1637TinyDisplay6 display(CLK, DIO);
uint8_t dots = 0b01010000; // Add dots or colons (depends on display module)
#else
/* Set up 4-Digit Display */
#include <TM1637TinyDisplay.h>
TM1637TinyDisplay display(CLK, DIO);
uint8_t dots = 0b01000000; // Add dots or colons (depends on display module)
#endif
/* Useful Constants */
#define SECS_PER_MIN (60UL)
#define SECS_PER_HOUR (3600UL)
#define SECS_PER_DAY (SECS_PER_HOUR * 24L)
/* Useful Macros for time (s) */
#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN)
#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN)
#define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR)
#define elapsedDays(_time_) ( _time_ / SECS_PER_DAY)
#define hmsToMillis(_h_, _m_, _s_) ((_h_ * SECS_PER_HOUR) + (_m_ * SECS_PER_MIN) + _s_ ) * 1000ul;
/* Global Variables in milliseconds */
unsigned long startTime;
unsigned long lastLoopTime;
unsigned long countDown;
# include <Adafruit_NeoPixel.h>
# define LED_PIN A2
# define LED_COUNT SEVEN
# define LED_PIN2 4
# define LED_COUNT2 45
Adafruit_NeoPixel disaply(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip(LED_COUNT2, LED_PIN2, NEO_GRB + NEO_KHZ800);
unsigned char combo[SEVEN];
unsigned char lastSwitch[SEVEN];
enum Runstate {START, RUNNING, FAULT, SUCCESS} runState;
# define redLED A1 // bad
# define greenLED A0 // good
# define NOKEY 99 // impossible key value as a flag
const byte inputPin[SEVEN] = {9, 10, 11, 12, 13};
// This pin will be driven LOW to release a lock when puzzle is solved
const byte lockPin = A3;
const byte lockPin2 = A4;
void setup() {
Serial.begin(115200); Serial.println("HE LL O!\n");
for (unsigned char ii = 0; ii < SEVEN; ii++) {
pinMode(inputPin[ii], INPUT_PULLUP);
lastSwitch[ii] = digitalRead(inputPin[ii]);
}
pinMode(greenLED, OUTPUT);
pinMode(redLED, OUTPUT);
// Set the lock pin as output and secure the lock
pinMode(lockPin, OUTPUT);
digitalWrite(lockPin, HIGH);
pinMode(lockPin2, OUTPUT);
digitalWrite(lockPin2, LOW);
disaply.begin();
disaply.show();
display1.begin();
strip.clear();
strip.begin();
// Clear the display and show it.
display1.clearDisplay();
display1.display();
display1.setCursor(0, 0);
display1.setTextSize(1); // Draw normal-scale text
display1.setTextColor(SSD1306_WHITE);
display1.print(F("Throw The Switches"));
display1.setCursor(0, 20);
display1.setTextSize(1); // Draw normal-scale text
display1.setTextColor(SSD1306_WHITE);
display1.print(F("In Proper Order"));
display1.display();
display1.clearDisplay();
display1.setCursor(0, 0);
reset();
runState = RUNNING;
newCombo0();
display.begin();
display.setBrightness(BRIGHT_HIGH);
// Record Epoch - Same as Timer Reset
startTime = millis();
// Set countdown timer in h, m, s
int Hour = 0;
int Min = 01;
int Sec = 00;
countDown = hmsToMillis(Hour, Min, Sec);
pinMode(lockPin, OUTPUT);
digitalWrite(lockPin, HIGH);
}
unsigned char nIn = 0; // number of correct steps taken sofa
unsigned char GG = 0; //Number of correct Puzzles
void loop() {
unsigned long timeNow = millis();
unsigned long timeElapsed = timeNow - startTime; // amount of time since start
unsigned long counter = countDown - timeElapsed; // current state of countdown
// Update Display - every 10ms
if (timeNow - lastLoopTime >= 10) {
lastLoopTime = timeNow; // remember last time we displayed
if (timeElapsed >= countDown) {
// If we hit zero - flash every second
unsigned long since = (long)((timeElapsed - countDown) / 500);
if (since % 2) {
display.clear();
// Release the lock
digitalWrite(lockPin, LOW);
theaterChase(strip.Color(0, 255, 0), 100, 3, 5); // Green, full brightness
Serial.println(GG);
display1.clearDisplay();
display1.display();
display1.setCursor(0, 0);
display1.setTextSize(1); // Draw normal-scale text
display1.setTextColor(SSD1306_WHITE);
display1.print(F("Correct Guesses "));
display1.setCursor(45, 20);
display1.setTextSize(1); // Draw normal-scale text
display1.setTextColor(SSD1306_WHITE);
display1.print(GG);
display1.display();
}
else {
display.showNumberDec(000000, dots, true);
}
}
else {
// Compute the values
unsigned long HOURS = numberOfHours(counter / 1000);
unsigned long MINUTES = numberOfMinutes(counter / 1000);
unsigned long SECONDS = numberOfSeconds(counter / 1000);
unsigned long MILLISECONDS = (counter % 1000) / 10;
// Convert time values to integer to display
if (HOURS > 0) {
// Display M:S.hs if timer is below one hour
display.showNumberDec(HOURS, dots, true, 2, 0);
display.showNumberDec(MINUTES, dots, true, 2, 2);
#if DIGITS == 6
display.showNumberDec(MILLISECONDS, 0, true, 2, 4);
#endif
}
if (HOURS <= 0 && (MINUTES >0)) {
// Display M:S.hs if timer is below one hour
display.showNumberDec(MINUTES, dots, true, 2, 0);
display.showNumberDec(SECONDS, dots, true, 2, 2);
#if DIGITS == 6
display.showNumberDec(MILLISECONDS, dots, true, 2, 4);
#endif
}
else if (HOURS <= 0 && (MINUTES <=0 && (SECONDS >=0))) {
// Display M:S.hs if timer is below one hour
display.showNumberDec(SECONDS, dots, true, 2, 0);
display.showNumberDec(MILLISECONDS, 0, true, 2, 2);
#if DIGITS == 6
display.showNumberDec(MILLISECONDS, 0, true, 2, 4);
#endif
} else {
// Display H:M:S if timer is above one hour
display.showNumberDec(HOURS, dots, true, 2, 0);
display.showNumberDec(MINUTES, dots, true, 2, 2);
#if DIGITS == 6
display.showNumberDec(SECONDS, 0, true, 2, 4);
#endif
}
}
}
unsigned char pKey = scanKeys();
random();
switch (runState) {
case RUNNING:
if (pKey == NOKEY)
return;
// presst a key, all key are valid so
if (pKey == combo[nIn]) {
nIn++;
displayDisplay();
goodFlash();
if (nIn == SEVEN) {
Serial.println("You are in!");
runState = SUCCESS;
// Release the lock
digitalWrite(lockPin, LOW);
theaterChase(strip.Color(255, 255, 255), 100, 3, 5); // White, full brightness
GG++;
Serial.println(GG);
display1.clearDisplay();
display1.display();
display1.setCursor(0, 0);
display1.setTextSize(1); // Draw normal-scale text
display1.setTextColor(SSD1306_WHITE);
display1.print(F("Correct Guesses "));
display1.setCursor(0, 20);
display1.setTextSize(1); // Draw normal-scale text
display1.setTextColor(SSD1306_WHITE);
display1.print(GG);
display1.display();
delay (2000);
display1.clearDisplay();
display1.display();
display1.setCursor(0, 0);
display1.setTextSize(1); // Draw normal-scale text
display1.setTextColor(SSD1306_WHITE);
display1.print(F("All SWITCHES CORRECT"));
display1.setCursor(0, 20);
display1.setTextSize(1); // Draw normal-scale text
display1.setTextColor(SSD1306_WHITE);
display1.print(F("RESET AND POWERING UP"));
display1.display();
}
}
else {
Serial.print(" BZZZT!");
runState = FAULT;
display1.clearDisplay();
display1.display();
display1.setCursor(0, 0);
display1.setTextSize(1); // Draw normal-scale text
display1.setTextColor(SSD1306_WHITE);
display1.print(F("OVERLOAD!!!!"));
display1.setCursor(0, 20);
display1.setTextSize(1); // Draw normal-scale text
display1.setTextColor(SSD1306_WHITE);
display1.print(F("RESET ALL SWITCHES"));
display1.display();
theaterChase(strip.Color(255, 0, 0), 100, 3, 5); // RED CHASE
digitalWrite(lockPin2, HIGH);
delay (1000);
digitalWrite(lockPin2, LOW);
display1.clearDisplay();
display1.display();
display1.setCursor(0, 0);
display1.setTextSize(1); // Draw normal-scale text
display1.setTextColor(SSD1306_WHITE);
display1.print(F("Throw The Switches"));
display1.setCursor(0, 20);
display1.setTextSize(1); // Draw normal-scale text
display1.setTextColor(SSD1306_WHITE);
display1.print(F("In Proper Order"));
display1.display();
}
break;
case FAULT:
badFlash();
if (keysum() == 0) {
reset();
displayDisplay();
runState = START;
}
break;
case SUCCESS:
reward();
if (pKey != NOKEY) {
newCombo0();
reset();
displayDisplay();
runState = FAULT;
}
break;
case START:
runState = RUNNING;
break;
default:
runState = START;
break;
}
}
void goodFlash()
{
digitalWrite(A1, HIGH);
delay(100);
digitalWrite(A1, LOW);
displayDisplay();
}
void badFlash()
{
static uint32_t last = 0;
const int interval = 50;
if (millis() - last >= interval) {
last = millis();
digitalWrite(A0, !digitalRead(A0));
displayDisplay();
digitalWrite(A0, LOW);
}
}
void reward()
{
static uint32_t last = 0;
const int interval = 600;
static bool off = true;
if (millis() - last < interval) return;
last = millis();
if (off) { // turn on
nIn = SEVEN; displayDisplay();
digitalWrite(A1, HIGH);
} else {
digitalWrite(A1, LOW);
nIn = 0; displayDisplay();
off = true;
}
}
void reset()
{
nIn = 0;
digitalWrite(A1, LOW);
}
void displayDisplay()
{
for (unsigned char tt = 0; tt < SEVEN; tt++) {
// disaply.setPixelColor(tt, tt >= nIn ? 0x001010 : 0x00ff00);
if (runState != FAULT) {
disaply.setPixelColor(combo[tt], tt >= nIn ? 0x000000 : 0x00ff00);
strip.fill(tt >= nIn ? 0x000000 : 0x00ff00, combo[tt] *9, 9);
} else {
disaply.setPixelColor(tt, lastSwitch[tt] ? 0xff0000 : 0x000000);
strip.fill(lastSwitch[tt] ? 0xff0000 : 0x000000, tt *9, 9);
}
}
disaply.show();
strip.show();
}
// scanKeysO looks for a button going pressed
// scanKeys just looks for a change of state
unsigned char scanKeys()
{
// printf("scan keys ");
unsigned char newKeyQ = NOKEY;
static unsigned long lastTime;
unsigned char currentKey = NOKEY;
unsigned long now = millis();
if (now - lastTime < 40) // too soon to look at anything
return currentKey;
lastTime = now;
for (unsigned char ii = 0; ii < SEVEN; ii++) {
unsigned char aSwitch = digitalRead(inputPin[ii]);
if (aSwitch != lastSwitch[ii]) {
currentKey = ii;
lastSwitch[ii] = aSwitch;
}
}
return currentKey;
}
int keysum() {
int retval = 0;
for (unsigned char ii = 0 ; ii < SEVEN; ii++) {
retval += lastSwitch[ii];
}
return retval;
}
unsigned char scanKeys0()
{
// printf("scan keys ");
unsigned char newKeyQ = NOKEY;
static unsigned long lastTime;
static unsigned char wasPressed = 0;
char isPressed = 0;
unsigned char currentKey = NOKEY;
unsigned long now = millis();
if (now - lastTime < 40)
return currentKey;
for (unsigned char ii = 0; ii < SEVEN; ii++) {
if (!digitalRead(inputPin[ii])) {
newKeyQ = ii;
isPressed = 1;
}
}
if (isPressed != wasPressed) {
lastTime = now;
if (isPressed)
currentKey = newKeyQ;
wasPressed = isPressed;
}
return currentKey;
}
void newCombo0()
{
for (unsigned char ii = 0; ii < SEVEN; ii++)
combo[ii] = ii;
for (int ii = 0; ii < 5000; ii++) {
unsigned char tt = random(SEVEN);
unsigned char ss = random(SEVEN);
unsigned char temp;
temp = combo[tt];
combo[tt] = combo[ss];
combo[ss] = temp;
}
// return; // if you do not want to print the combination!
for (unsigned char ii = 0; ii < SEVEN; ii++) {
Serial.print(combo[ii]);
Serial.print(" ");
}
Serial.println();
}
/*
* Fills a strip with a specific color, starting at 0 and continuing
* until the entire strip is filled. Takes two arguments:
*
* 1. the color to use in the fill
* 2. the amount of time to wait after writing each LED
*/
void colorWipe(uint32_t color, unsigned long wait) {
for (unsigned int i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, color);
strip.show();
delay(wait);
}
}
/*
* Runs a marquee style "chase" sequence. Takes three arguments:
*
* 1. the color to use in the chase
* 2. the amount of time to wait between frames
* 3. the number of LEDs in each 'chase' group
* 3. the number of chases sequences to perform
*/
void theaterChase(uint32_t color, unsigned long wait, unsigned int groupSize, unsigned int numChases) {
for (unsigned int chase = 0; chase < numChases; chase++) {
for (unsigned int pos = 0; pos < groupSize; pos++) {
strip.clear(); // turn off all LEDs
for (unsigned int i = pos; i < strip.numPixels(); i += groupSize) {
strip.setPixelColor(i, color); // turn on the current group
}
strip.show();
delay(wait);
}
}
}
/*
* Simple rainbow animation, iterating through all 8-bit hues. LED color changes
* based on position in the strip. Takes two arguments:
*
* 1. the amount of time to wait between frames
* 2. the number of rainbows to loop through
*/
void rainbow(unsigned long wait, unsigned int numLoops) {
for (unsigned int count = 0; count < numLoops; count++) {
// iterate through all 8-bit hues, using 16-bit values for granularity
for (unsigned long firstPixelHue = 0; firstPixelHue < 65536; firstPixelHue += 256) {
for (unsigned int i = 0; i < strip.numPixels(); i++) {
unsigned long pixelHue = firstPixelHue + (i * 65536UL / strip.numPixels()); // vary LED hue based on position
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue))); // assign color, using gamma curve for a more natural look
}
strip.show();
delay(wait);
}
}
}