I found this installation at the "Mathematikum" in the town of Giessen (Germany), similar ones can be found in the "Universum" located in the city of Bremen and many other places.
Visitors are very keen to reach the goal turning on all of the seven lights but it turns out it is not so easy as you might think. Keep in mind: if you press S3, LED3 will toggle but also LED2 and LED4. May be a good reason to pay a visit to the Mathematikum
but as most of you won't take a trip to Giessen just to try it yourself, you can easily have it at home or in school. (Actually, most of the sites called "Phaenomenta" also offer this kind of puzzle.) When you managed to solve the puzzle, you can still show it to your friends and let them try.
Version 1:First Step: cut the circle to make it a line of 7 LEDsIf you are not handy using drilling machines and soldering tools you can go for ready-made kits offering eight keys and eight LEDs.
Obviously, there is one key and one LED too much, and they are not arranged in a circle. So, just ignore key S8 and LED8. That means, S1 is neighbouring S7 and LED1 is neighbouring LED7. To connect the module to your Arduino you just need 5 female-to-female jumper wires.
If you are using the Arduino NANO, the wiring looks just like this as they rotated the ISCP header by 180 degrees:
Just for fun, I extended the display, making use of the 7-segment display. After RESET, the word "arduino" (7 letters!) is displayed in lower case, and at each toggle the corresponding character is switched from lower case to upper case and vice versa. So, at the end the word "ARDUINO" will be displayed in upper case letters.
If you start the game with all LEDs off, you can easily cheat by pressing S1 to S7 once in any sequence you like, and at the end all LEDs will lit. But when you start with only some of the LEDs switched on, the game becomes very hard.
In order to use this module you will have to download and install the TM1638 library. The sketch will take some 100 lines of code:
/*
fuer LED&KEY
https://www.matheboard.de/archive/595175/thread.html
https://universum-bremen.de/sieben-lampen-raetsel/
http://www.zahlreich.de/messages/74315/200756.html
http://www.gerdlamprecht.de/SiebenSchalterUndLampen.htm
https://ar-ar.facebook.com/hcm.school/posts/1655627454467901/
https://www.geogebra.org/m/wd2gqrhj
*/
const int X = 7; // also 5 works well
int Y = X - 1;
#include <TM1638.h>
byte data = 11;
byte clock = 12;
byte strobe = 13;
TM1638 m(data, clock, strobe);
void setup() {
Serial.begin(9600);
Serial.println(__FILE__);
m.clearDisplay();
show();
}
boolean b[X];
void loop() {
int k = bitNum(m.getButtons());
if (k < 0 || k > Y) return;
Serial.println(k);
toggle(b[k]);
if (k > 0) toggle(b[k - 1]); else toggle(b[Y]);
if (k < Y) toggle(b[k + 1]); else toggle(b[0]);
show();
// wait until button released:
while (bitNum(m.getButtons()) >= 0);
if (c() == X) trailer();
}
void show() {
char v[X] = "ARDUINO";
char w[X];
for (byte i = 0; i < X; i++) {
int c = v[i];
if (b[i]) c = c - 32; // upper case
int d = FONT_DEFAULT[c];
w[i] = d;
m.setLED(b[i], i);
}
m.setDisplay(w, X);
}
void trailer() {
delay(1000);
char b[] = "gOOd - pEErLEss - "
"sUpErb - NOtAbLE - "
"gREAT - OUtstANdINg - "
"dEsIrAbLE - prICELEss - ";
byte l = strlen(b);
char e[l];
for (int i = 0; i < l; i++) {
int c = b[i] - 32;
int d = FONT_DEFAULT[c];
e[i] = d;
Serial.println(d);
}
byte i = 0;
while (true) {
m.setDisplay(&e[i], 8);
if (i == 0) delay(1000);
else delay(350);
i++;
if (i > l - 8) {
i = 0;
delay(1000);
}
}
}
int bitNum(int x) {
int i = -1;
while (x > 0) {
x = x / 2;
i++;
}
return i;
}
void toggle(boolean &x) {
x = !x;
}
byte c() {
byte retVal = 0;
for (byte i = 0; i < sizeof b; i++)
if (b[i]) retVal++;
return retVal;
}
The trailer()-function will give you some kind of confirmation. You can modify the game by counting the number of keys getting pressed and give different confirmation texts depending on the amount of keys pressed. When selecting new messages, better avoid letters that cannot be shown on 7 segments.
Actually, it can be proven that you never will need more than seven key presses.
You can also modify the game to start with a random configuration.
Version 2:Hundred days later: in version One, an array of boolean was used to store and modify the actual state of the LEDs. This obviously is a waste of resources and can be done using seven of the bits of one byte. This also gives an easy way to analyze the game and show hints which key to press using the minimum number of key-presses. Analyzing is invoked by pressing key-8 which had no function in the previous version. In this version. A jumper cable between pin-6 and pin-7 is used to enable or disable the hint-function. In case anyone asks why 6x7 were chosen? As everybody knows it is the answer to all the questions of the universe. (Just use the translator of your choice to read the comments.)
/*
7 Tasten - 7 Lampen mit dem Modul "LED&KEY"
ARDUINO UNO + TM1638
letzte Version: 14.5.2023
benutzt das undokumentierte Macro bitToggle
Verwendung der achten Taste:
Hilfe aktiviert mit Bruecke pin-6-7
Hilfe anfordern mit Taste S8.
*/
#include <TM1638.h>
byte data = 11;
byte clock = 12;
byte strobe = 13;
TM1638 m(data, clock, strobe);
const byte h1 = 6; // fuer Hilfe
const byte h2 = 7; // fuer Hilfe
const int N = 7;
const int M = N - 1; // M kommt vor N
// alle Bits gesetzt:
const int ALL_BITS_SET = (1 << N) - 1;
// zustand:
int zustand;
int btnCnt;
void setup() {
Serial.begin(9600);
Serial.println(__FILE__);
Serial.println(__TIME__);
pinMode(h1, INPUT_PULLUP);
pinMode(h2, OUTPUT);
wuerfel();
show7seg();
showLEDs();
}
void loop() {
int k = getHighestBit(m.getButtons());
// Hilfe-Taste gedrueckt?
if (!digitalRead(h1) && k == N) analyze();
if (k < 0 || k > M) return;
Serial.println(k + 1);
bitToggle3(zustand, k);
btnCnt++;
showLEDs();
// warte bis Taste losgelassen:
while (getHighestBit(m.getButtons()) >= 0);
if (zustand == ALL_BITS_SET) trailer();
}
void bitToggle3(int &i, byte j) {
bitToggle(i, j);
bitToggle(i, j > 0 ? j - 1 : M);
bitToggle(i, j < M ? j + 1 : 0);
}
void showLEDs() {
if (btnCnt == 0)
m.setDisplayToString("0", 0, 7);
else
m.setDisplayToDecNumber(btnCnt, 0, false);
for (byte i = 0; i < 7; i++)
m.setLED(bitRead(zustand, i), i);
}
void show7seg() {
m.setDisplayToString("ALLE7 AN");
delay(2000);
m.clearDisplay();
}
void trailer() {
delay(1000);
Serial.println("Solved");
char anzeige[] = "TOLL - ECHT GUT - "
"SUPER - GROSSARTIG - ";
byte L = strlen(anzeige);
for (int i = 0; i < L - 8; i++) {
m.setDisplayToString(&anzeige[i]);
delay(500);
}
wuerfel();
btnCnt = 0;
m.clearDisplay();
showLEDs();
}
int8_t getHighestBit(long x) {
/*
fuer getButttons und analyze, liefert die
Nummer des hoechsten gesetzten Bits in x,
also fuer x = 8 ist das Ergebnis = 3
*/
int8_t retVal = -1;
while (x) {
x = x >> 1;
retVal++;
}
return retVal;
}
void wuerfel() {
// vermeide 0 und 1111111
for (int i = -100; i < analogRead(A0); i++)
zustand = random(ALL_BITS_SET - 2) + 1;
Serial.println(zustand, BIN);
}
void analyze() {
// teste alle moeglichen Tastendruecke:
for (byte i = 1; i <= ALL_BITS_SET; i++) {
int temp = zustand;
// verarbeite die zugehoerigen Tastendruecke:
for (byte pos = 0; pos < N; pos++)
if (bitRead(i, pos)) bitToggle3(temp, pos);
// wenn Ziel erreicht, drucke einen Hinweis:
if (temp == ALL_BITS_SET) {
byte key = getHighestBit(i) + 1;
char msg[] = "PrESS ";
msg[7] = '0' + key;
Serial.println(msg);
m.setDisplayToString(msg);
delay(1000);
return;
}
}
}
Version 3Another hundred days later:
Same hardware, but some improvements: now you can select a "circle" of either 5, 6, 7 or 8 buttons/LEDs. After the program started, it will calculate and display the minimum number of steps neccessary to reach the goal. With each press, this number is decremented. As soon as you exceed zero, the step counter will become negative. The program even allows you to set the number of buttons to 6. But with 6 buttons most initial configurations lead no way to "all LEDs on". Only that configurations are presented that allow to reach the goal. To modify the number of buttons/LEDs you have to press the RESET key. As the size of the program exceeds 200 lines of code it is only available as ZIP file in the attachments.
----------------------------------------------------------
If you still have free time you are welcome to extend the puzzle to 31 keys/LEDs:
Only little changes of the code will be necessary. If you want different sizes, avoid values that are divisable by three; the puzzle might become unsolvable.
Version using UNO R4 WifiThe R4 Wifi offers 96 LEDs, we only need seven, and they should be arranged in a circle, but this is not possible. I decided to indicate a light which is off by only one LED, and a light which is on, by a block of four LEDs. It all fits in a small wooden box, covered by some perspex which holds the seven pushbuttons. The modified code is presented in the attachments.
Last but not Least: A Multi-Master VersionAll versions shown above are not suitable for visually impaired people. So the target was to replace the LEDs by loudspeakers. To make it more easy to find the right one, their tones should be distinguishable. But the ARDUINO-tone()-function offers only one tone at a time. The idea was to use one ARDUINO for each speaker, and this is how I came up at the end:
Doing it this way, you can use the RESET key as a button, and the built-in LED. When you use the RESET key as a button, it is a good idea to use UPLOAD USING PROGRAMMER which will overwrite the bootloader which leads to a faster reaction. (In case you ever dismantle this project to use the components for a different project, do not forget to reburn the bootloader.)
There are two wires between adjacent ARDUINOs: from pin-4 of the left neighbor to pin-2, which acts as INT0, and from pin-A5 of the right neighbor to pin-3, which acts as INT1. And there are two wires to get the power to all devices using only one power source.
Doing it this ways the program became very short as no ARDUINO has to find the source of alteration. All the work is done in setup() function
void setup() {
Serial.begin(9600);
Serial.println(__FILE__);
ID = EEPROM.read(0);
toggle();
// show your actual state:
setPin(LED_BUILTIN, s);
// inform your neighbors:
setPin(right, HIGH);
setPin(left, HIGH);
// react to your neighbors:
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(2),
toggle, FALLING);
attachInterrupt(digitalPinToInterrupt(3),
toggle, FALLING);
}
and the toggle() function.
void toggle() {
if (s) s = false;
else s = true;
setPin(LED_BUILTIN, s);
if (s) tone(12, 400 + 30 * ID);
else noTone(12);
}
The EEPROM-location 0 should be set to different values so the generated sounds of each ARDUINO would be different.
While the goal initially was turn all lamps on, in this case the goal should be turn all speakers off.
Final remarksLet me have another try. What is the use of doing the same job in many different ways? Well, there are many aspects:
speed, space, correctness, portability, understandability, readability, elegance, and possibly more.
The main problem was processors do not offer ROTATE instructions for seven bits. And in this case, you always need two neighbors, and none of them should have a negative index value (there is only one programming language I know that allows negative indices: Pascal). That is why you better use indices from 1 to 7 rather than 0 to 6.
The main differences between the five versions are how to toggle three bits, version (a) and (b) store the actual state in array elements, the other ones use the bits of one byte.
The last one uses an assembler routine to toggle the three bits. As a matter of course it will not compile using the new UNO R4, but it is a good way to learn AVR assembler.
You can download the ZIP file containing all five versions.
Comments