From 117d078efcd4d23266096a4daef83cb35b6ea184 Mon Sep 17 00:00:00 2001 From: Seppe De Loore Date: Mon, 9 Mar 2026 10:15:49 +0100 Subject: [PATCH] [add] Option to hide blue borders. --- platformio.ini | 1 + src/main.cpp | 152 +++++++++++++++++++++++++++---------------------- 2 files changed, 86 insertions(+), 67 deletions(-) diff --git a/platformio.ini b/platformio.ini index 9a21651..e020d7d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,6 +11,7 @@ build_flags = -D ENC_B=1 -D ENC_SW=2 -D SENSITIVITY=4 + -D SHOW_BORDER=0 -D BRIGHTNESS=25 -D IDLE_TIMEOUT=45000 -D DEMO_RESET_PAUSE=20000 diff --git a/src/main.cpp b/src/main.cpp index 7df2ca5..be664fc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,11 @@ #include #include +// Build Flag Default (can be overridden in platformio.ini) +#ifndef SHOW_BORDER +#define SHOW_BORDER 1 +#endif + #define NUM_LEDS 64 const int COLS = 7; const int ROWS = 6; @@ -29,7 +34,7 @@ State gameState = MENU; int8_t menuMode = 0; int8_t currentPlayer = 1; -int8_t winnerPlayer = 0; // Tracks who actually won for the flashing effect +int8_t winnerPlayer = 0; int8_t activeCol = 3; long oldEncPos = -999; uint32_t lastActivityTime = 0; @@ -52,7 +57,7 @@ void drawStaticUI(); void renderBoard(); int getFirstEmptyRow(int col); bool isBoardFull(); -int8_t scanBoard(); // Changed to return the winner ID +int8_t scanBoard(); void updateThinkingVisuals(int8_t p, int8_t col); void animateDrop(int col, int player); void moveDiscToCol(int startCol, int targetCol, int player, int speed); @@ -68,6 +73,7 @@ int getIdx(int x, int y) { return (y * 8) + x; } void drawStaticUI() { FastLED.clear(); +#if SHOW_BORDER == 1 CRGB borderColor = CRGB::Blue; if (gameState == DEMO || gameState >= 2) { @@ -78,29 +84,30 @@ void drawStaticUI() leds[getIdx(x, 1)] = borderColor; for (int y = 1; y < 8; y++) leds[getIdx(7, y)] = borderColor; +#endif } void renderBoard() { drawStaticUI(); - for (int c = 0; c < COLS; c++) + for (int column = 0; column < COLS; column++) { - for (int r = 0; r < ROWS; r++) + for (int row = 0; row < ROWS; row++) { - if (board[c][r] == 1) - leds[getIdx(c, 7 - r)] = CRGB::Yellow; - if (board[c][r] == 2) - leds[getIdx(c, 7 - r)] = CRGB::Red; + if (board[column][row] == 1) + leds[getIdx(column, 7 - row)] = CRGB::Yellow; + if (board[column][row] == 2) + leds[getIdx(column, 7 - row)] = CRGB::Red; } } } int getFirstEmptyRow(int col) { - for (int r = 0; r < ROWS; r++) + for (int row = 0; row < ROWS; row++) { - if (board[col][r] == 0) - return r; + if (board[col][row] == 0) + return row; } return -1; } @@ -109,17 +116,17 @@ int getDynamicPly() { if (!progressive_difficulty && gameState != DEMO) return current_look_ahead; - 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(current_look_ahead + (count / 7), 1, 10); + int occupiedCount = 0; + for (int column = 0; column < COLS; column++) + for (int row = 0; row < ROWS; row++) + if (board[column][row] != 0) + occupiedCount++; + return constrain(current_look_ahead + (occupiedCount / 7), 1, 10); } // --- Visuals & Animations --- -void updateThinkingVisuals(int8_t p, int8_t col) +void updateThinkingVisuals(int8_t playerColor, int8_t column) { static uint32_t lastCycle = 0; if (millis() - lastCycle < 25) @@ -139,8 +146,8 @@ void updateThinkingVisuals(int8_t p, int8_t col) } for (int x = 0; x < COLS; x++) leds[getIdx(x, 0)] = CRGB::Black; - CRGB aiColor = (p == 1) ? CRGB::Yellow : CRGB::Red; - leds[getIdx(col, 0)] = aiColor.nscale8(aiBrightness); + CRGB aiColor = (playerColor == 1) ? CRGB::Yellow : CRGB::Red; + leds[getIdx(column, 0)] = aiColor.nscale8(aiBrightness); FastLED.show(); } @@ -149,12 +156,12 @@ void animateDrop(int col, int player) int targetRow = getFirstEmptyRow(col); if (targetRow == -1) return; - for (int r = 5; r >= targetRow; r--) + for (int row = 5; row >= targetRow; row--) { renderBoard(); - leds[getIdx(col, 7 - r)] = (player == 1) ? CRGB::Yellow : CRGB::Red; + leds[getIdx(col, 7 - row)] = (player == 1) ? CRGB::Yellow : CRGB::Red; FastLED.show(); - delay(max(20, 80 - (5 - r) * 15)); + delay(max(20, 80 - (5 - row) * 15)); } board[col][targetRow] = player; renderBoard(); @@ -181,8 +188,8 @@ void moveDiscToCol(int startCol, int targetCol, int player, int speed) bool isBoardFull() { - for (int c = 0; c < COLS; c++) - if (board[c][5] == 0) + for (int column = 0; column < COLS; column++) + if (board[column][5] == 0) return false; return true; } @@ -190,44 +197,52 @@ bool isBoardFull() int8_t scanBoard() { memset(winMask, 0, sizeof(winMask)); - auto check = [&](int column, int row, int columnOffset, int rowOffset) + auto checkMatch = [&](int col, int row, int dCol, int dRow) { - int8_t postion = board[column][row]; - if (postion != 0 && board[column + columnOffset][row + rowOffset] == postion && board[column + 2 * columnOffset][row + 2 * rowOffset] == postion && board[column + 3 * columnOffset][row + 3 * rowOffset] == postion) + int8_t playerAtPos = board[col][row]; + if (playerAtPos != 0 && + board[col + dCol][row + dRow] == playerAtPos && + board[col + 2 * dCol][row + 2 * dRow] == playerAtPos && + board[col + 3 * dCol][row + 3 * dRow] == playerAtPos) { - for (int index = 0; index < 4; index++) - winMask[getIdx(column + index * columnOffset, 7 - (row + index * rowOffset))] = true; - return postion; + for (int i = 0; i < 4; i++) + winMask[getIdx(col + i * dCol, 7 - (row + i * dRow))] = true; + return playerAtPos; } return (int8_t)0; }; + + // Horizontal for (int row = 0; row < 6; row++) - for (int column = 0; column < 4; column++) + for (int col = 0; col < 4; col++) { - int8_t res = check(column, row, 1, 0); - if (res) - return res; + int8_t result = checkMatch(col, row, 1, 0); + if (result) + return result; } + // Vertical for (int row = 0; row < 3; row++) - for (int column = 0; column < 7; column++) + for (int col = 0; col < 7; col++) { - int8_t res = check(column, row, 0, 1); - if (res) - return res; + int8_t result = checkMatch(col, row, 0, 1); + if (result) + return result; } + // Diagonal Up for (int row = 0; row < 3; row++) - for (int column = 0; column < 4; column++) + for (int col = 0; col < 4; col++) { - int8_t res = check(column, row, 1, 1); - if (res) - return res; + int8_t result = checkMatch(col, row, 1, 1); + if (result) + return result; } + // Diagonal Down for (int row = 3; row < 6; row++) - for (int column = 0; column < 4; column++) + for (int col = 0; col < 4; col++) { - int8_t res = check(column, row, 1, -1); - if (res) - return res; + int8_t result = checkMatch(col, row, 1, -1); + if (result) + return result; } return 0; } @@ -239,40 +254,39 @@ int minimax(int depth, int alpha, int beta, bool isMax, int8_t aiPlayer, int8_t else yield(); - // Check for wins within minimax - int8_t win = scanBoard(); - if (win == aiPlayer) + int8_t winner = scanBoard(); + if (winner == aiPlayer) return 1000 + depth; - if (win == humanPlayer) + if (winner == humanPlayer) return -1000 - depth; if (depth == 0 || isBoardFull()) return 0; - int order[] = {3, 2, 4, 1, 5, 0, 6}; - int best = isMax ? -2000 : 2000; - for (int column : order) + int colOrder[] = {3, 2, 4, 1, 5, 0, 6}; + int bestScore = isMax ? -2000 : 2000; + for (int column : colOrder) { int row = getFirstEmptyRow(column); if (row != -1) { board[column][row] = isMax ? aiPlayer : humanPlayer; - int val = minimax(depth - 1, alpha, beta, !isMax, aiPlayer, humanPlayer, (depth == current_look_ahead ? column : rootCol)); + int score = minimax(depth - 1, alpha, beta, !isMax, aiPlayer, humanPlayer, (depth == current_look_ahead ? column : rootCol)); board[column][row] = 0; if (isMax) { - best = max(best, val); - alpha = max(alpha, best); + bestScore = max(bestScore, score); + alpha = max(alpha, bestScore); } else { - best = min(best, val); - beta = min(beta, best); + bestScore = min(bestScore, score); + beta = min(beta, bestScore); } if (beta <= alpha) break; } } - return best; + return bestScore; } void performAiMove(int8_t aiPlayer) @@ -293,14 +307,14 @@ void performAiMove(int8_t aiPlayer) { board[column][row] = 0; bestCol = column; - goto finalize; + goto finalizeMove; } board[column][row] = humanPlayer; if (current_look_ahead >= 2 && scanBoard() == humanPlayer) { board[column][row] = 0; bestCol = column; - goto finalize; + goto finalizeMove; } board[column][row] = 0; } @@ -322,11 +336,11 @@ void performAiMove(int8_t aiPlayer) } if ((gameState == DEMO || blunder_enabled) && random(100) < 20) { - int rCol = random(0, 7); - if (getFirstEmptyRow(rCol) != -1) - bestCol = rCol; + int randomColumn = random(0, 7); + if (getFirstEmptyRow(randomColumn) != -1) + bestCol = randomColumn; } -finalize: +finalizeMove: current_look_ahead = originalPly; moveDiscToCol(activeCol, bestCol, aiPlayer, 100); delay(450); @@ -382,10 +396,12 @@ void showMenu() { isDemoOver = false; FastLED.clear(); +#if SHOW_BORDER == 1 for (int x = 0; x < 7; x++) leds[getIdx(x, 1)] = CRGB::Blue; for (int y = 1; y < 8; y++) leds[getIdx(7, y)] = CRGB::Blue; +#endif if (menuMode < 2) { CRGB p1Col = (menuMode == 1) ? CRGB::Red : CRGB::Yellow; @@ -584,8 +600,10 @@ void loop() renderBoard(); for (int i = 0; i < NUM_LEDS; i++) { +#if SHOW_BORDER == 1 if (leds[i] == CRGB::Blue) continue; +#endif if (gameState == FINISHED_WIN) { if (winMask[i]) @@ -606,7 +624,7 @@ void loop() memset(board, 0, sizeof(board)); gameState = DEMO; demoResetTimer = 0; - demoPly = random(3, 7); + demoPly = random(2, 5); } if (pressed) {