diff --git a/terminal/puzzle_colorRemember/.gitignore b/terminal/puzzle_colorRemember/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/terminal/puzzle_colorRemember/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/terminal/puzzle_colorRemember/platformio.ini b/terminal/puzzle_colorRemember/platformio.ini new file mode 100644 index 0000000..8681872 --- /dev/null +++ b/terminal/puzzle_colorRemember/platformio.ini @@ -0,0 +1,17 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:nanoatmega328new] +platform = atmelavr +board = nanoatmega328new +framework = arduino +lib_deps = + adafruit/Adafruit NeoPixel@^1.15.2 + robtillaart/PCF8575@^0.2.4 diff --git a/terminal/puzzle_colorRemember/src/main.cpp b/terminal/puzzle_colorRemember/src/main.cpp new file mode 100644 index 0000000..3f271cc --- /dev/null +++ b/terminal/puzzle_colorRemember/src/main.cpp @@ -0,0 +1,327 @@ + +// ================== Projekt: Modul 2 ================== +// SPIELMECHANIK – mit Gewinnanimation + Gewinnsignal +// +// Fix integriert: +// - zeitbasierte Entprellung pro Taster +// - verhindert Mehrfachregistrierung eines einzelnen Tastendrucks +#include +#include +#include +#include + +// ------------------ Hardware ------------------ + +PCF8575 expander(0x20); + +#define PIN_NEOPIXEL_BUTTONS 4 +#define NUM_NEOPIXEL_BUTTONS 18 + +#define PIN_NEOPIXEL_LOESUNG 6 +#define NUM_NEOPIXEL_LOESUNG 6 + +#define PIN_POWER_ON 13 +#define PIN_TRIGGER_SHUFFLE 2 +#define PIN_SEND_WIN 5 + +// ------------------ Timing ------------------ + +#define WIN_OFF_DELAY_MS 300 +#define WIN_STEP_DELAY_MS 60 +#define DEBOUNCE_TIME_MS 200 // <<< Entprellzeit + +Adafruit_NeoPixel pixelsButtons( + NUM_NEOPIXEL_BUTTONS, + PIN_NEOPIXEL_BUTTONS, + NEO_RGB + NEO_KHZ800 +); + +Adafruit_NeoPixel pixelsSolution( + NUM_NEOPIXEL_LOESUNG, + PIN_NEOPIXEL_LOESUNG, + NEO_RGB + NEO_KHZ800 +); + +// ------------------ Mapping ------------------ + +enum ButtonSource { SRC_NANO, SRC_PCF8575 }; + +struct LedButtonMap { + uint8_t ledIndex; + ButtonSource source; + uint8_t pin; +}; + +const LedButtonMap ledButtonMap[18] = { + { 0, SRC_NANO, A0 }, { 1, SRC_NANO, A1 }, { 2, SRC_NANO, A2 }, { 3, SRC_NANO, A3 }, + { 4, SRC_NANO, 7 }, { 5, SRC_NANO, 8 }, + { 6, SRC_PCF8575, 0 }, { 7, SRC_PCF8575, 1 }, { 8, SRC_PCF8575, 2 }, { 9, SRC_PCF8575, 3 }, + {10, SRC_PCF8575, 4 }, {11, SRC_PCF8575, 5 }, {12, SRC_PCF8575, 6 }, {13, SRC_PCF8575, 7 }, + {14, SRC_PCF8575, 8 }, {15, SRC_PCF8575, 9 }, {16, SRC_PCF8575,10 }, {17, SRC_PCF8575,11 } +}; + +// ------------------ Pride-Farben (dunkler) ------------------ + +const uint8_t prideColors[6][3] = { + {25, 0, 0}, + {35, 10, 0}, + {25, 25, 0}, + { 0, 25, 0}, + { 0, 0, 25}, + {20, 0, 25} +}; + +// ------------------ Spielzustände ------------------ + +uint8_t solutionOrder[6]; +uint8_t expectedOrder[6]; +int8_t buttonAssignment[18]; +bool winActive = false; +bool powerOnState = true; + + +bool lastPressed[18]; +unsigned long ledOffTime[18]; +unsigned long lastPressTime[18]; // <<< Entprellspeicher + +uint8_t progressIndex = 0; +bool lastTriggerState = LOW; + +// ------------------ Hilfsfunktionen ------------------ + +bool readButton(uint8_t i) { + if (ledButtonMap[i].source == SRC_NANO) + return digitalRead(ledButtonMap[i].pin) == LOW; + return expander.read(ledButtonMap[i].pin) == LOW; +} + +// ------------------ Neues Spiel ------------------ + +void generateNewGame() { + + for (uint8_t i = 0; i < 6; i++) solutionOrder[i] = i; + + for (int i = 5; i > 0; i--) { + int j = random(i + 1); + uint8_t t = solutionOrder[i]; + solutionOrder[i] = solutionOrder[j]; + solutionOrder[j] = t; + } + + for (uint8_t i = 0; i < 6; i++) + expectedOrder[i] = solutionOrder[5 - i]; + + for (uint8_t i = 0; i < 6; i++) { + uint8_t c = solutionOrder[i]; + pixelsSolution.setPixelColor( + i, + pixelsSolution.Color( + prideColors[c][0], + prideColors[c][1], + prideColors[c][2] + ) + ); + } + pixelsSolution.show(); + + for (uint8_t i = 0; i < 18; i++) { + buttonAssignment[i] = -1; + pixelsButtons.setPixelColor(i, 0); + ledOffTime[i] = 0; + lastPressTime[i] = 0; + } + pixelsButtons.show(); + + uint8_t idx[18]; + for (uint8_t i = 0; i < 18; i++) idx[i] = i; + + for (int i = 17; i > 0; i--) { + int j = random(i + 1); + uint8_t t = idx[i]; + idx[i] = idx[j]; + idx[j] = t; + } + + for (uint8_t i = 0; i < 6; i++) + buttonAssignment[idx[i]] = solutionOrder[i]; + + progressIndex = 0; +} + +// ------------------ Gewinnanimation + Signal ------------------ + +void winAnimation() { + + winActive = true; + digitalWrite(PIN_SEND_WIN, HIGH); + + // 1) letzte korrekte Farbe stehen lassen + delay(500); + + // 2) ALLES AUS (inkl. Off-Timer löschen) + for (uint8_t i = 0; i < 18; i++) + ledOffTime[i] = 0; + + pixelsSolution.clear(); + pixelsButtons.clear(); + pixelsSolution.show(); + pixelsButtons.show(); + delay(WIN_OFF_DELAY_MS); + + // 3) richtige Reihenfolge (umgekehrt), Lösung + Taster synchron + for (int i = 5; i >= 0; i--) { + + uint8_t color = solutionOrder[i]; + + // Lösungs-LED + pixelsSolution.setPixelColor( + i, + pixelsSolution.Color( + prideColors[color][0], + prideColors[color][1], + prideColors[color][2] + ) + ); + + // zugehöriger Taster + for (uint8_t b = 0; b < 18; b++) { + if (buttonAssignment[b] == color) { + pixelsButtons.setPixelColor( + b, + pixelsButtons.Color( + prideColors[color][0], + prideColors[color][1], + prideColors[color][2] + ) + ); + break; + } + } + + pixelsSolution.show(); + pixelsButtons.show(); + delay(WIN_STEP_DELAY_MS); + } + + digitalWrite(PIN_SEND_WIN, LOW); + winActive = false; +} + + + + +// ------------------ Setup ------------------ + +void setup() { + randomSeed(analogRead(A0)); + + Wire.begin(); + expander.begin(); + + pinMode(PIN_TRIGGER_SHUFFLE, INPUT); + pinMode(PIN_POWER_ON, INPUT); + powerOnState = digitalRead(PIN_POWER_ON); + pinMode(PIN_SEND_WIN, OUTPUT); + digitalWrite(PIN_SEND_WIN, LOW); + + pinMode(A0, INPUT_PULLUP); + pinMode(A1, INPUT_PULLUP); + pinMode(A2, INPUT_PULLUP); + pinMode(A3, INPUT_PULLUP); + pinMode(7, INPUT_PULLUP); + pinMode(8, INPUT_PULLUP); + + for (uint8_t i = 0; i < 12; i++) + expander.write(i, HIGH); + + pixelsButtons.begin(); + pixelsSolution.begin(); + + generateNewGame(); + + for (uint8_t i = 0; i < 18; i++) + lastPressed[i] = false; +} + +// ------------------ Loop ------------------ + +void loop() { + if (powerOnState && digitalRead(PIN_POWER_ON) == LOW) + { + powerOnState = false; + pixelsSolution.begin(); + for (size_t i = 0; i < 6; i++) + { + pixelsSolution.setPixelColor(i, pixelsSolution.Color(0, 0, 0, 0)); + } + pixelsSolution.show(); + pixelsButtons.begin(); + for (size_t i = 0; i < 18; i++) + { + pixelsButtons.setPixelColor(i, pixelsButtons.Color(0, 0, 0, 0)); + } + pixelsButtons.show(); + + } + // Detecting turn-on + if (!powerOnState && digitalRead(PIN_POWER_ON) == HIGH) + { + generateNewGame(); + powerOnState = true; + } + if (!powerOnState) + { + // We are turned-off and stop anything after here + return; + } + unsigned long now = millis(); + + bool trigger = digitalRead(PIN_TRIGGER_SHUFFLE); + if (trigger && !lastTriggerState) { + generateNewGame(); + } + lastTriggerState = trigger; + + for (uint8_t i = 0; i < 18; i++) { + bool pressed = readButton(i); + + if (pressed && !lastPressed[i] && + (now - lastPressTime[i] > DEBOUNCE_TIME_MS)) { + + lastPressTime[i] = now; + int8_t color = buttonAssignment[i]; + + if (color >= 0) { + pixelsButtons.setPixelColor( + i, + pixelsButtons.Color( + prideColors[color][0], + prideColors[color][1], + prideColors[color][2] + ) + ); + pixelsButtons.show(); + ledOffTime[i] = now + 1000; + } + + if (color < 0 || color != expectedOrder[progressIndex]) { + progressIndex = 0; + } else { + progressIndex++; + if (progressIndex == 6) { + winAnimation(); + progressIndex = 0; + } + } + } + + if (!winActive && ledOffTime[i] && now >= ledOffTime[i]) { + + pixelsButtons.setPixelColor(i, 0); + pixelsButtons.show(); + ledOffTime[i] = 0; + } + + lastPressed[i] = pressed; + } +} \ No newline at end of file