diff --git a/platformio.ini b/platformio.ini
index e9fde22..e8a33f0 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -12,14 +12,12 @@ build_flags =
-D ENC_SW=2
-D SENSITIVITY=4
-D SHOW_BORDER=0
- -D BRIGHTNESS=25
- -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 MAX_GAME_LOG=100
+ -D WIFI_SSID=\"Connect4\"
-D WIFI_PASSWORD=\"youlose4\"
lib_deps =
fastled/FastLED @ 3.9.12
diff --git a/src/main.cpp b/src/main.cpp
index 889431d..54c7d7f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -13,16 +13,48 @@
#define SENSITIVITY 4
#endif
+#ifndef LED_PIN
#define LED_PIN 4
+#endif
+
+#ifndef ENC_A
#define ENC_A 0
+#endif
+
+#ifndef ENC_B
#define ENC_B 1
+#endif
+
+#ifndef ENC_SW
#define ENC_SW 2
+#endif
+
#define NUM_LEDS 64
#ifndef MAX_GAME_LOG
#define MAX_GAME_LOG 5
#endif
+#ifndef DEFAULT_LOOK_AHEAD
+#define DEFAULT_LOOK_AHEAD 8
+#endif
+
+#ifndef DEFAULT_BRIGHTNESS
+#define DEFAULT_BRIGHTNESS 25
+#endif
+
+#ifndef DEFAULT_IDLE_TIMEOUT
+#define DEFAULT_IDLE_TIMEOUT 60
+#endif
+
+#ifndef DEMO_RESET_PAUSE
+#define DEMO_RESET_PAUSE 30000
+#endif
+
+#ifndef WIFI_SSID
+#define WIFI_SSID "Connect4"
+#endif
+
const int COLS = 7;
const int ROWS = 6;
const int colOrder[] = {3, 2, 4, 1, 5, 0, 6};
@@ -49,11 +81,11 @@ uint8_t demoPly[2] = {4, 4};
bool abortAi = false;
bool lastButtonState = HIGH;
-uint8_t currentLookAhead = 6;
-uint8_t currentBrightness = 30;
-uint32_t currentIdleTimeoutMs = 60000;
+uint8_t currentLookAhead = DEFAULT_LOOK_AHEAD;
+uint8_t currentBrightness = DEFAULT_BRIGHTNESS;
+uint32_t currentIdleTimeoutMs = DEFAULT_IDLE_TIMEOUT * 1000;
bool blunderEnabled = false;
-bool progressiveDifficulty = false;
+uint8_t blunderChance = 20;
uint8_t aiBrightness = 0;
bool aiFadeUp = true;
@@ -84,7 +116,6 @@ void renderBoard();
void showMenu();
int getFirstEmptyRow(int col);
bool isBoardFull();
-int getDynamicPly();
int8_t scanBoard();
bool checkGameEnd();
void updateThinkingVisuals(int8_t pColor, int8_t column);
@@ -217,13 +248,6 @@ bool isBoardFull() {
return true;
}
-int getDynamicPly() {
- if (!progressiveDifficulty && gameState != DEMO) return currentLookAhead;
- int count = 0;
- for (int c = 0; c < COLS; c++) for (int r = 0; r < ROWS; r++) if (board[c][r] != 0) count++;
- return constrain(currentLookAhead + (count / 7), 1, 10);
-}
-
int8_t scanBoard() {
memset(winMask, 0, sizeof(winMask));
auto check = [&](int c, int r, int dc, int dr) {
@@ -243,21 +267,14 @@ int8_t scanBoard() {
bool checkGameEnd() {
winnerPlayer = scanBoard();
- if (winnerPlayer != 0) {
- if (gameState != DEMO) logGame(winnerPlayer);
- gameState = FINISHED_WIN;
- demoResetTimer = millis();
- lastActivityTime = millis();
- return true;
- }
- if (isBoardFull()) {
- if (gameState != DEMO) logGame(0);
- gameState = FINISHED_DRAW;
- demoResetTimer = millis();
- lastActivityTime = millis();
- return true;
- }
- return false;
+ bool won = winnerPlayer != 0;
+ bool draw = !won && isBoardFull();
+ if (!won && !draw) return false;
+ if (gameState != DEMO) logGame(won ? winnerPlayer : 0);
+ gameState = won ? FINISHED_WIN : FINISHED_DRAW;
+ demoResetTimer = millis();
+ lastActivityTime = millis();
+ return true;
}
// --- Animation ---
@@ -337,27 +354,41 @@ void performAiMove(int8_t aiP) {
int huP = (aiP == 1) ? 2 : 1;
int bestScore = -30000; int bestCol = 3;
int originalPly = currentLookAhead;
- currentLookAhead = (gameState == DEMO) ? demoPly[aiP - 1] : getDynamicPly();
+ if (gameState == DEMO) currentLookAhead = demoPly[aiP - 1];
- for (int c = 0; c < COLS; c++) {
+ // Phase 1: always take an instant win or block an opponent's win
+ bool found = false;
+ for (int c = 0; c < COLS && !found; c++) {
int r = getFirstEmptyRow(c);
if (r != -1) {
- board[c][r] = aiP; if (scanBoard() == aiP) { board[c][r]=0; bestCol=c; goto finalizeMove; }
- board[c][r] = huP; if (scanBoard() == huP) { board[c][r]=0; bestCol=c; goto finalizeMove; }
+ board[c][r] = aiP; if (scanBoard() == aiP) { board[c][r]=0; bestCol=c; found=true; break; }
+ board[c][r] = huP; if (scanBoard() == huP) { board[c][r]=0; bestCol=c; found=true; break; }
board[c][r] = 0;
}
}
- for (int c : colOrder) {
- if (abortAi) goto finalizeMove;
- int r = getFirstEmptyRow(c);
- if (r != -1) {
- board[c][r] = aiP;
- int score = minimax(currentLookAhead, -30000, 30000, false, aiP, huP, c);
- board[c][r] = 0;
- if (score > bestScore) { bestScore = score; bestCol = c; }
+
+ // Phase 2: blunder — pick a random column instead of deep search
+ if (!found && blunderEnabled && gameState != DEMO && (random(100) < blunderChance)) {
+ int validCols[COLS], count = 0;
+ for (int c = 0; c < COLS; c++) if (getFirstEmptyRow(c) != -1) validCols[count++] = c;
+ bestCol = validCols[random(count)];
+ found = true;
+ }
+
+ // Phase 3: deep minimax search
+ if (!found) {
+ for (int c : colOrder) {
+ if (abortAi) break;
+ int r = getFirstEmptyRow(c);
+ if (r != -1) {
+ board[c][r] = aiP;
+ int score = minimax(currentLookAhead, -30000, 30000, false, aiP, huP, c);
+ board[c][r] = 0;
+ if (score > bestScore) { bestScore = score; bestCol = c; }
+ }
}
}
-finalizeMove:
+
currentLookAhead = originalPly;
if (!abortAi) { moveDiscToCol(activeCol, bestCol, aiP, 80); if (!abortAi) { delay(100); animateDrop(bestCol, aiP); } }
}
@@ -377,8 +408,8 @@ void handleRoot() {
html += "Base AI Ply:";
html += "Brightness:";
html += "Idle Timeout (s):";
- html += "Blunders:
";
- html += "Evolution:
";
+ html += "Blunders: ";
+ html += " Chance (%):
";
html += "";
html += "";
html += "