From 8fb373f5d63484b1e4bc9e4ab007abc2b6dc14c5 Mon Sep 17 00:00:00 2001 From: Seppe De Loore Date: Fri, 6 Mar 2026 17:07:10 +0100 Subject: [PATCH] [add] Web config and ply 1 --- platformio.ini | 4 ++ src/main.cpp | 112 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 91 insertions(+), 25 deletions(-) diff --git a/platformio.ini b/platformio.ini index d198ca0..9a21651 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,6 +15,10 @@ build_flags = -D IDLE_TIMEOUT=45000 -D DEMO_RESET_PAUSE=20000 -D DEBOUNCE_DELAY=50 + -D DEFAULT_LOOK_AHEAD=8 + -D DEFAULT_BRIGHTNESS=25 + -D DEFAULT_IDLE_TIMEOUT=45 + -D WIFI_PASSWORD=\"youlose4\" lib_deps = fastled/FastLED @ ^3.6.0 paulstoffregen/Encoder @ ^1.4.4 \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 14bd234..50c2d38 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,14 +1,18 @@ #include #include #include +#include +#include +#include #define NUM_LEDS 64 const int COLS = 7; const int ROWS = 6; -const int LOOK_AHEAD = 8; CRGB leds[NUM_LEDS]; Encoder myEnc(ENC_A, ENC_B); +WebServer server(80); +Preferences prefs; int8_t board[COLS][ROWS]; bool winMask[NUM_LEDS]; @@ -30,20 +34,24 @@ uint32_t lastActivityTime = 0; uint32_t demoResetTimer = 0; bool isDemoOver = false; -// Thinking Animation Variables +// Web-Configurable Parameters (Stored in Flash) +uint8_t current_look_ahead; +uint8_t current_brightness; +uint32_t current_idle_timeout_ms; + +// Thinking Animation Helpers uint8_t aiBrightness = 0; bool aiFadeUp = true; +// --- Helper Functions --- int getIdx(int x, int y) { return (y * 8) + x; } -// RESTORED: Thinking Animation Function void updateThinkingLED(int8_t p) { static uint32_t lastCycle = 0; if (millis() - lastCycle < 20) return; lastCycle = millis(); - if (aiFadeUp) { aiBrightness += 15; @@ -56,7 +64,6 @@ void updateThinkingLED(int8_t p) if (aiBrightness <= 15) aiFadeUp = true; } - CRGB compColor = (p == 1) ? CRGB::Yellow : CRGB::Red; leds[getIdx(7, 0)] = compColor.nscale8(aiBrightness); FastLED.show(); @@ -143,10 +150,10 @@ bool scanBoard(int8_t p) return found; } +// --- AI Engine --- int minimax(int depth, int alpha, int beta, bool isMax, int8_t aiP, int8_t huP) { - // Show thinking animation during deep recursion - if (depth == LOOK_AHEAD || depth == LOOK_AHEAD - 1) + if (depth >= current_look_ahead - 1) updateThinkingLED(aiP); else yield(); @@ -191,8 +198,7 @@ void performAiMove(int8_t aiP) { int8_t huP = (aiP == 1) ? 2 : 1; aiBrightness = 0; - aiFadeUp = true; // Reset animation - + aiFadeUp = true; for (int c = 0; c < COLS; c++) { int r = getFirstEmptyRow(c); @@ -205,7 +211,7 @@ void performAiMove(int8_t aiP) return; } board[c][r] = huP; - if (scanBoard(huP)) + if (current_look_ahead >= 2 && scanBoard(huP)) { board[c][r] = aiP; leds[getIdx(7, 0)] = CRGB::Black; @@ -214,7 +220,6 @@ void performAiMove(int8_t aiP) board[c][r] = 0; } } - int bestScore = -30000; int bestCol = 3; for (int c : {3, 2, 4, 1, 5, 0, 6}) @@ -223,7 +228,7 @@ void performAiMove(int8_t aiP) if (r != -1) { board[c][r] = aiP; - int score = minimax(LOOK_AHEAD, -30000, 30000, false, aiP, huP); + int score = minimax(current_look_ahead, -30000, 30000, false, aiP, huP); board[c][r] = 0; if (score > bestScore) { @@ -233,9 +238,10 @@ void performAiMove(int8_t aiP) } } board[bestCol][getFirstEmptyRow(bestCol)] = aiP; - leds[getIdx(7, 0)] = CRGB::Black; // Clear thinking LED + leds[getIdx(7, 0)] = CRGB::Black; } +// --- Menu UI with Restored Serifs --- void showMenu() { isDemoOver = false; @@ -273,20 +279,73 @@ void showMenu() FastLED.show(); } +// --- Web Portal --- +void handleRoot() +{ + String html = ""; + html += ""; + html += "

Connect 4 Admin

"; + html += "AI Ply (1-10):"; + html += "Brightness (5-255):"; + html += "Idle Timeout (Sec):"; + html += "
"; + server.send(200, "text/html", html); +} + +void handleSave() +{ + if (server.hasArg("ply")) + { + current_look_ahead = server.arg("ply").toInt(); + prefs.putUChar("ply", current_look_ahead); + } + if (server.hasArg("br")) + { + current_brightness = server.arg("br").toInt(); + FastLED.setBrightness(current_brightness); + prefs.putUChar("br", current_brightness); + } + if (server.hasArg("idle")) + { + uint32_t s = server.arg("idle").toInt(); + current_idle_timeout_ms = s * 1000; + prefs.putUInt("idle", s); + } + server.sendHeader("Location", "/"); + server.send(303); +} + void setup() { + Serial.begin(115200); + prefs.begin("c4-game", false); + current_look_ahead = prefs.getUChar("ply", 8); + current_brightness = prefs.getUChar("br", 25); + current_idle_timeout_ms = prefs.getUInt("idle", 60) * 1000; + FastLED.addLeds(leds, NUM_LEDS); - FastLED.setBrightness(BRIGHTNESS); + FastLED.setBrightness(current_brightness); pinMode(ENC_SW, INPUT_PULLUP); + + WiFi.softAPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0)); + WiFi.softAP("Connect4-Config", WIFI_PASSWORD, 1, 0, 4); // SSID, Pass, Channel 1, Hidden 0, Max Connections 4 + WiFi.softAP("Connect4-Config"); + + server.on("/", handleRoot); + server.on("/save", HTTP_POST, handleSave); + server.begin(); + lastActivityTime = millis(); showMenu(); } void loop() { + server.handleClient(); long newPos = myEnc.read() / SENSITIVITY; bool pressed = (digitalRead(ENC_SW) == LOW); + // Escape Demo / Interrupt if (newPos != oldEncPos || (pressed && (millis() - lastActivityTime > 500))) { if (gameState == DEMO || isDemoOver) @@ -297,9 +356,7 @@ void loop() FastLED.show(); delay(30); } - FastLED.clear(); - FastLED.show(); - delay(500); + delay(2000); gameState = MENU; memset(board, 0, sizeof(board)); showMenu(); @@ -333,7 +390,7 @@ void loop() } delay(300); } - if (millis() - lastActivityTime > IDLE_TIMEOUT) + if (millis() - lastActivityTime > current_idle_timeout_ms) { gameState = DEMO; memset(board, 0, sizeof(board)); @@ -359,13 +416,9 @@ void loop() renderBoard(); FastLED.show(); if (scanBoard(currentPlayer)) - { gameState = FINISHED_WIN; - } else if (isBoardFull()) - { gameState = FINISHED_DRAW; - } else { if (menuMode < 2) @@ -380,9 +433,7 @@ void loop() gameState = FINISHED_WIN; } else if (isBoardFull()) - { gameState = FINISHED_DRAW; - } } else { @@ -395,6 +446,7 @@ void loop() } else if (gameState == DEMO) { + // No idle timeout check here to prevent premature restarts renderBoard(); FastLED.show(); delay(600); @@ -418,6 +470,15 @@ void loop() } else { + // Monitor for Idle in Win screen to return to Demo + if (!isDemoOver && (millis() - lastActivityTime > current_idle_timeout_ms)) + { + memset(board, 0, sizeof(board)); + gameState = DEMO; + currentPlayer = 1; + return; + } + static uint32_t lastFlash = 0; static bool toggle = true; if (millis() - lastFlash > 300) @@ -444,7 +505,8 @@ void loop() } FastLED.show(); } - if (isDemoOver && (millis() - demoResetTimer > DEMO_RESET_PAUSE)) + // Restart Demo loop if it was a demo game + if (isDemoOver && (millis() - demoResetTimer > 30000)) { memset(board, 0, sizeof(board)); gameState = DEMO;