LEDs in the Wokwi simulator don't have much dynamic range, The simulated can't switch on-and-off as fast as real life leds with the mapping of one led to several pixels, delivering through html etc....
Calibrating the screen-represented html mediated simulation to real life colors would be fraught with difficulty, so the simulation just goes with "on" at the refresh rate of the page rendering.
(I just copied the color from @alto777's fine script)
Having some days off and not messing with this gave me time to think, I am wanting to make a completely stand alone game separate from what bigger project this is going to be part of and I went back to the random puzzle, or so I thought it was random. At first, I thought I modified something and did it wrong, but it happens in the
puzzle as well. It doesn't seem to go into random mode, just repeating a predetermined list every time you reset the game. Here is my locked project on WOKWI as well as the code, but it happens in the example game you mentioned in post 12 as well. Not truly random.
// 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_NeoPixel.h>
# define LED_PIN A2
# define LED_COUNT SEVEN
# define LED_PIN2 A5
# 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();
strip.clear();
strip.begin();
reset();
runState = RUNNING;
newCombo0();
}
unsigned char nIn = 0; // number of correct steps taken sofa
void loop() {
unsigned char pKey = scanKeys();
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
}
}
else {
Serial.print(" BZZZT!");
runState = FAULT;
// Sound the Buzzer
theaterChase(strip.Color(255, 0, 0), 100, 3, 5); // RED CHASE
digitalWrite(lockPin2, HIGH);
delay (1000);
digitalWrite(lockPin2, LOW);
}
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);
}
}
}
Another trick that would also is to just call random() frequently.
In scanKwys() where it has the print statement that is commented out, right after that just call random(), you don't need to do anything with the number, just calling it moves along the sequence.
random(777); // any number really
Since scanKeys() is getting called alla time, it's like dealing cards rapidly. When you need a random number for realz, you'll be at some, um, random point in the deck, so to speak.
No. Not Las Vegas Nevada random. But you can avoid repeating the same pseudorandom sequence, if you store the current random value in EEPROM after you have called random() a few times in your program. Then load that value and pass it to randomSeed() at boot time.
Yes, but just calling random() at the loop rate would do as well.
For "real" random numbers some larger amount of trouble must be gone to, including replacing the random() code, which is probably not suitable for, say, cryptographic purposes.
For an escape room toy, I wonder if users would catch on to being fed the same sequences if game play even did feed a batch of participants the same numbers one afternoon.
Or you could take the ultimate dodge and consider it to be a feature… clever participants wou,d gain an advantage.
While I was testing one or the other version above I did inadvertently memorize the first three combinations that always come out if no care is taken… and months later still knew the first two.
Meanwhile I can't remember my girlfriend's telephone number.
But loop() based calls to it is still deterministic, unless something injects some external entropy, like depending on user-supplied events.
If you need a seed at setup, you may not be able to wait for user input, and analogRead() of a floating pin or the power supply might get you something non-deterministic.
// RandomSeed.ino https://wokwi.com/projects/358276294916324353
// for https://forum.arduino.cc/t/toggle-switch-puzzle-but-sequential/1091460/47
const int seedtype = 4;
void setup() {
Serial.begin(115200);
switch (seedtype) {
case 1:
randomSeed(20230304) ; //
case 2:
pinMode(12, INPUT_PULLUP);
Serial.println("Ground pin 12 for a random number");
while (digitalRead(12)) random(RANDOM_MAX);
break;
case 3:
randomSeed(random(RANDOM_MAX) + micros() + analogRead(A0));
break;
case 4:
randomSeed(analogRead(A0)); // 10 bits if you're lucky
break;
case 5:
randomSeed(1+analogRead(A0) % 4); // maybe two bits of randomness
break;
case 0:
break;
default:
Serial.println("bad seed scheme");
;
}
Serial.println(random(RANDOM_MAX));
for (int i = 6; i; --i)
Serial.println(random(6));
}
void loop() {
}
Of those schemes, the user input one has the most randomness/entropy.
In non-cryptographic cases, usually setting seed just once with a good value is enough, because it will start you at one of the RANDOM_MAX positions in the random code's cycle, but if you need more entropy, injecting some when you can get it (i.e. from each UI interaction, analogRead() network delay,... etc.. ) is a good scheme.
Which we have here. loop() will run some hundreds or thousands of times between keystrokes, random() will be however further along in its sequence.
I've seen the analogRead() trick, but never looked to see it working well or not.
Meanwhile, I've changed the way the combination is made to a "just in time" algorithm, because coffee and waiting on my beach buddy, she who must never be kept waiting. Irony defined.
This
static unsigned char doleFrom[PSIZE];
static unsigned char nLeft;
void resetComboMachine()
{
nLeft = PSIZE;
for (unsigned char ii = 0; ii < PSIZE; ii++) {
combo[ii] = 0xff;
doleFrom[ii] = ii;
}
}
unsigned char doleNext()
{
unsigned char next;
unsigned char rx;
if (nLeft == 0) errorStop("no keys left to dole");
rx = random(0, nLeft);
next = doleFrom[rx];
nLeft--;
doleFrom[rx] = doleFrom[nLeft];
return next;
}
lets us not even have the next piece of the combination until it is needed. Only when you have entered N digits correctly and are trying for N + 1 is that number placed into the combination.
Now, to clarify, when I say random,I still only want to use each switch only once leaving just 120 different combinations, that could be easy to manage. If one introduces multiple use of the switches, it could get really off track.
The general idea is to have this as a waiting room puzzle. Add a countdown timer and a counter to determine how many puzzles solved in an amount of time. Also add some better feedback to show how on track one is. This could even be moved to a Simon game as an offshoot. Going to fit it into a small nanuk 910 case and theme it.
This is a test of mental strength. How systematically you can work through an unknown pattern and how many you can successfully do. Possibly adding a "wrong" counter as well.
This is similar to the "crack the code" laser cut safe with 8 LEDs and a four digit combo. I'll try to find the link.
Forgive me, it sounded like it was going along the lines of constantly calling up the next "digit" or "switch" meaning it could be used twice. My mistake.
It WOULD be similar to a wordle game though, in all actuality. More or less just a battery powered pass along game that can be handed off to multiple people.
With 120 combinations, you only need to consume about log(120)/log(2)=7 bits of randomness per run,
The plain AVR random() function has a cycle length of 2^31, so it would be fine for millions of consecutive runs without falling into the same cycle. As long as you can make sure you don't start with at the same default, it will be fine.
@alto77's code defers selection until the user hits a button, so it's stepping along the 2^31 cycle well enough to cover the 7 bits of randomess the problem needs. However, if you knew the timing to 6.25ns, or could count loops(), you'd could then know how many times random() had been called and you could know the next value. A motivated hacker could program a machine to push the buttons at the right times to win with the code 0,1,2,3,4,5,6 every time.
in the program, it does randomize the codes being required, but it only does it after the first code. The first one is always the same though. weird, and OK as it does lull a player into a sense of false security I guess, then panic sets in when they memorize the second and third and so on thinking they are good and then failure.
// 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_NeoPixel.h>
# define LED_PIN A2
# define LED_COUNT SEVEN
# define LED_PIN2 A5
# 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();
strip.clear();
strip.begin();
reset();
runState = RUNNING;
newCombo0();
}
unsigned char nIn = 0; // number of correct steps taken sofa
void loop() {
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
}
}
else {
Serial.print(" BZZZT!");
runState = FAULT;
// Sound the Buzzer
theaterChase(strip.Color(255, 0, 0), 100, 3, 5); // RED CHASE
digitalWrite(lockPin2, HIGH);
delay (500);
digitalWrite(lockPin2, LOW);
}
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);
}
}
}
If you weren't using all your analog pins, you could use a floating analog input to inject some randomness before the first code with something like this:
And I don't like kludges, no matter how you spell kluge, but a first-time flag can fix you up as well:
void loop() {
static unsigned char firstCombo = true;
unsigned char pKey = scanKeys();
random(); // spin the RNG
switch (runState) {
case RUNNING:
if (pKey == NOKEY)
return;
// now that random has run ahead a bit
if (firstCombo) {
newCombo0();
firstCombo = false;
}
// presst a key, all key are valid so
if (pKey == combo[nIn]) {...
Just holds off forming the first combination until the loop has run for awhile. Everything else operates as it always did. The kluge is only across a few lines of code here, so can be trusted.
Yes, @DaveX - if I time the very first button press with precision, I will be able to game this.
That's the trouble with algorithmic approches. I have just spent time looking at true random number generators, lotsa nice ideas like using radioactivity or noise from transistor junctions...
Sry, tried to test it but I can yet make no sense of the new game. Confidence is high, however.
Edit the *.ino file to reflect the proper link to the wokwi
https://wokwi.com/projects/358460632962894849
If you make a new wokwi, keep that comment up to date.
Use the global search and replace facility in the wokwi to fix my stupid SEVEN, maybe replace it with PSIZE for "puzzle size"
I tried with the switches preset. I did see some green sectors I think, and some red sectors and red LEDs. But I tired of guessing - I never did solve it. If that's your aim, you have made a challenging puzzle!
And please explain what we're supposed to be seeing with the slide switches replacing the pushbuttons. I just don't get how you are expecting them to be manipulated.