[fix] Interrupt demo when button is pressed.
This commit is contained in:
+173
-166
@@ -13,6 +13,7 @@
|
|||||||
const int COLS = 7;
|
const int COLS = 7;
|
||||||
const int ROWS = 6;
|
const int ROWS = 6;
|
||||||
|
|
||||||
|
// --- Global Variables ---
|
||||||
CRGB leds[NUM_LEDS];
|
CRGB leds[NUM_LEDS];
|
||||||
Encoder myEnc(ENC_A, ENC_B);
|
Encoder myEnc(ENC_A, ENC_B);
|
||||||
WebServer server(80);
|
WebServer server(80);
|
||||||
@@ -24,6 +25,7 @@ enum State
|
|||||||
{
|
{
|
||||||
MENU,
|
MENU,
|
||||||
PLAYING,
|
PLAYING,
|
||||||
|
AI_TURN,
|
||||||
FINISHED_WIN,
|
FINISHED_WIN,
|
||||||
FINISHED_DRAW,
|
FINISHED_DRAW,
|
||||||
DEMO
|
DEMO
|
||||||
@@ -37,34 +39,37 @@ int8_t activeCol = 3;
|
|||||||
long oldEncPos = -999;
|
long oldEncPos = -999;
|
||||||
uint32_t lastActivityTime = 0;
|
uint32_t lastActivityTime = 0;
|
||||||
uint32_t demoResetTimer = 0;
|
uint32_t demoResetTimer = 0;
|
||||||
bool isDemoOver = false;
|
uint32_t globalInputCooldown = 0;
|
||||||
uint8_t demoPly = 4;
|
uint8_t demoPly = 4; // FIXED: Restored global declaration
|
||||||
bool abortAi = false;
|
bool abortAi = false;
|
||||||
|
bool lastButtonState = HIGH;
|
||||||
|
bool buttonBlocked = false;
|
||||||
|
|
||||||
uint8_t current_look_ahead;
|
uint8_t current_look_ahead = 6;
|
||||||
uint8_t current_brightness;
|
uint8_t current_brightness = 30;
|
||||||
uint32_t current_idle_timeout_ms;
|
uint32_t current_idle_timeout_ms = 60000;
|
||||||
bool blunder_enabled = false;
|
bool blunder_enabled = false;
|
||||||
bool progressive_difficulty = false;
|
bool progressive_difficulty = false;
|
||||||
|
|
||||||
uint8_t aiBrightness = 0;
|
uint8_t aiBrightness = 0;
|
||||||
bool aiFadeUp = true;
|
bool aiFadeUp = true;
|
||||||
|
|
||||||
// --- Function Prototypes ---
|
// --- Prototypes ---
|
||||||
int getIdx(int x, int y);
|
int getIdx(int x, int y);
|
||||||
void drawStaticUI();
|
void drawStaticUI();
|
||||||
void renderBoard();
|
void renderBoard();
|
||||||
int getFirstEmptyRow(int col);
|
int getFirstEmptyRow(int col);
|
||||||
bool isBoardFull();
|
bool isBoardFull();
|
||||||
int8_t scanBoard();
|
int8_t scanBoard();
|
||||||
void updateThinkingVisuals(int8_t playerColor, int8_t column);
|
void updateThinkingVisuals(int8_t pColor, int8_t column);
|
||||||
void animateDrop(int col, int player);
|
void animateDrop(int col, int player);
|
||||||
void moveDiscToCol(int startCol, int targetCol, int player, int speed);
|
void moveDiscToCol(int startCol, int targetCol, int player, int speed);
|
||||||
int minimax(int depth, int alpha, int beta, bool isMax, int8_t aiPlayer, int8_t humanPlayer, int8_t rootCol);
|
int minimax(int depth, int alpha, int beta, bool isMax, int8_t aiP, int8_t huP, int8_t rootCol);
|
||||||
void performAiMove(int8_t aiPlayer);
|
void performAiMove(int8_t aiP);
|
||||||
void showMenu();
|
void showMenu();
|
||||||
int getDynamicPly();
|
int getDynamicPly();
|
||||||
|
|
||||||
|
// --- Functions ---
|
||||||
int getIdx(int x, int y) { return (y * 8) + x; }
|
int getIdx(int x, int y) { return (y * 8) + x; }
|
||||||
|
|
||||||
void drawStaticUI()
|
void drawStaticUI()
|
||||||
@@ -72,7 +77,7 @@ void drawStaticUI()
|
|||||||
FastLED.clear();
|
FastLED.clear();
|
||||||
#if SHOW_BORDER == 1
|
#if SHOW_BORDER == 1
|
||||||
CRGB borderColor = CRGB::Blue;
|
CRGB borderColor = CRGB::Blue;
|
||||||
if (gameState == DEMO || gameState >= 2)
|
if (gameState == DEMO || gameState >= 3)
|
||||||
{
|
{
|
||||||
uint8_t glow = beat8(15);
|
uint8_t glow = beat8(15);
|
||||||
borderColor = blend(CRGB::Blue, CRGB::White, glow / 4);
|
borderColor = blend(CRGB::Blue, CRGB::White, glow / 4);
|
||||||
@@ -87,35 +92,31 @@ void drawStaticUI()
|
|||||||
void renderBoard()
|
void renderBoard()
|
||||||
{
|
{
|
||||||
drawStaticUI();
|
drawStaticUI();
|
||||||
for (int column = 0; column < COLS; column++)
|
for (int c = 0; c < COLS; c++)
|
||||||
{
|
{
|
||||||
for (int row = 0; row < ROWS; row++)
|
for (int r = 0; r < ROWS; r++)
|
||||||
{
|
{
|
||||||
if (board[column][row] == 1)
|
if (board[c][r] == 1)
|
||||||
leds[getIdx(column, 7 - row)] = CRGB::Yellow;
|
leds[getIdx(c, 7 - r)] = CRGB::Yellow;
|
||||||
if (board[column][row] == 2)
|
if (board[c][r] == 2)
|
||||||
leds[getIdx(column, 7 - row)] = CRGB::Red;
|
leds[getIdx(c, 7 - r)] = CRGB::Red;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int getFirstEmptyRow(int col)
|
int getFirstEmptyRow(int col)
|
||||||
{
|
{
|
||||||
for (int row = 0; row < ROWS; row++)
|
for (int r = 0; r < ROWS; r++)
|
||||||
{
|
if (board[col][r] == 0)
|
||||||
if (board[col][row] == 0)
|
return r;
|
||||||
return row;
|
|
||||||
}
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isBoardFull()
|
bool isBoardFull()
|
||||||
{
|
{
|
||||||
for (int column = 0; column < COLS; column++)
|
for (int c = 0; c < COLS; c++)
|
||||||
{
|
if (board[c][ROWS - 1] == 0)
|
||||||
if (board[column][ROWS - 1] == 0)
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,36 +124,40 @@ int getDynamicPly()
|
|||||||
{
|
{
|
||||||
if (!progressive_difficulty && gameState != DEMO)
|
if (!progressive_difficulty && gameState != DEMO)
|
||||||
return current_look_ahead;
|
return current_look_ahead;
|
||||||
int occupiedCount = 0;
|
int count = 0;
|
||||||
for (int column = 0; column < COLS; column++)
|
for (int c = 0; c < COLS; c++)
|
||||||
for (int row = 0; row < ROWS; row++)
|
for (int r = 0; r < ROWS; r++)
|
||||||
if (board[column][row] != 0)
|
if (board[c][r] != 0)
|
||||||
occupiedCount++;
|
count++;
|
||||||
return constrain(current_look_ahead + (occupiedCount / 7), 1, 10);
|
return constrain(current_look_ahead + (count / 7), 1, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateThinkingVisuals(int8_t playerColor, int8_t column)
|
void updateThinkingVisuals(int8_t pColor, int8_t column)
|
||||||
{
|
{
|
||||||
static uint32_t lastCycle = 0;
|
static uint32_t lastCycle = 0;
|
||||||
if (millis() - lastCycle < 25)
|
if (millis() - lastCycle < 20)
|
||||||
return;
|
return;
|
||||||
lastCycle = millis();
|
lastCycle = millis();
|
||||||
if (aiFadeUp)
|
if (aiFadeUp)
|
||||||
{
|
{
|
||||||
aiBrightness += 15;
|
aiBrightness += 25;
|
||||||
if (aiBrightness >= 240)
|
if (aiBrightness >= 230)
|
||||||
aiFadeUp = false;
|
aiFadeUp = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
aiBrightness -= 15;
|
aiBrightness -= 25;
|
||||||
if (aiBrightness <= 15)
|
if (aiBrightness <= 25)
|
||||||
aiFadeUp = true;
|
aiFadeUp = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int x = 0; x < COLS; x++)
|
for (int x = 0; x < COLS; x++)
|
||||||
leds[getIdx(x, 0)] = CRGB::Black;
|
leds[getIdx(x, 0)] = CRGB::Black;
|
||||||
CRGB aiColor = (playerColor == 1) ? CRGB::Yellow : CRGB::Red;
|
|
||||||
leds[getIdx(column, 0)] = aiColor.nscale8(aiBrightness);
|
// FIXED: Explicit color initialization to avoid nscale8 compiler error
|
||||||
|
CRGB aiColor = (pColor == 1) ? CRGB(CRGB::Yellow) : CRGB(CRGB::Red);
|
||||||
|
aiColor.nscale8(aiBrightness);
|
||||||
|
leds[getIdx(column, 0)] = aiColor;
|
||||||
FastLED.show();
|
FastLED.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,32 +166,33 @@ void animateDrop(int col, int player)
|
|||||||
int targetRow = getFirstEmptyRow(col);
|
int targetRow = getFirstEmptyRow(col);
|
||||||
if (targetRow == -1)
|
if (targetRow == -1)
|
||||||
return;
|
return;
|
||||||
for (int row = 5; row >= targetRow; row--)
|
for (int r = 5; r >= targetRow; r--)
|
||||||
{
|
{
|
||||||
renderBoard();
|
renderBoard();
|
||||||
leds[getIdx(col, 7 - row)] = (player == 1) ? CRGB::Yellow : CRGB::Red;
|
leds[getIdx(col, 7 - r)] = (player == 1) ? CRGB::Yellow : CRGB::Red;
|
||||||
FastLED.show();
|
FastLED.show();
|
||||||
delay(max(20, 80 - (5 - row) * 15));
|
delay(max(10, 60 - (5 - r) * 10));
|
||||||
}
|
}
|
||||||
board[col][targetRow] = player;
|
board[col][targetRow] = player;
|
||||||
renderBoard();
|
|
||||||
FastLED.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void moveDiscToCol(int startCol, int targetCol, int player, int speed)
|
void moveDiscToCol(int startCol, int targetCol, int player, int speed)
|
||||||
{
|
{
|
||||||
int current = startCol;
|
int current = startCol;
|
||||||
CRGB pColor = (player == 1) ? CRGB::Yellow : CRGB::Red;
|
CRGB colr = (player == 1) ? CRGB::Yellow : CRGB::Red;
|
||||||
while (current != targetCol && !abortAi)
|
while (current != targetCol && !abortAi)
|
||||||
{
|
{
|
||||||
|
if (gameState == DEMO && digitalRead(ENC_SW) == LOW)
|
||||||
|
{
|
||||||
|
abortAi = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
leds[getIdx(current, 0)] = CRGB::Black;
|
leds[getIdx(current, 0)] = CRGB::Black;
|
||||||
current += (targetCol > current) ? 1 : -1;
|
current += (targetCol > current) ? 1 : -1;
|
||||||
renderBoard();
|
renderBoard();
|
||||||
leds[getIdx(current, 0)] = pColor;
|
leds[getIdx(current, 0)] = colr;
|
||||||
FastLED.show();
|
FastLED.show();
|
||||||
delay(speed);
|
delay(speed);
|
||||||
if (digitalRead(ENC_SW) == LOW)
|
|
||||||
abortAi = true;
|
|
||||||
}
|
}
|
||||||
activeCol = targetCol;
|
activeCol = targetCol;
|
||||||
}
|
}
|
||||||
@@ -194,180 +200,168 @@ void moveDiscToCol(int startCol, int targetCol, int player, int speed)
|
|||||||
int8_t scanBoard()
|
int8_t scanBoard()
|
||||||
{
|
{
|
||||||
memset(winMask, 0, sizeof(winMask));
|
memset(winMask, 0, sizeof(winMask));
|
||||||
auto checkMatch = [&](int col, int row, int dCol, int dRow)
|
auto check = [&](int c, int r, int dc, int dr)
|
||||||
{
|
{
|
||||||
int8_t pAtPos = board[col][row];
|
int8_t p = board[c][r];
|
||||||
if (pAtPos != 0 && board[col + dCol][row + dRow] == pAtPos &&
|
if (p != 0 && board[c + dc][r + dr] == p && board[c + 2 * dc][r + 2 * dr] == p && board[c + 3 * dc][r + 3 * dr] == p)
|
||||||
board[col + 2 * dCol][row + 2 * dRow] == pAtPos && board[col + 3 * dCol][row + 3 * dRow] == pAtPos)
|
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
winMask[getIdx(col + i * dCol, 7 - (row + i * dRow))] = true;
|
winMask[getIdx(c + i * dc, 7 - (r + i * dr))] = true;
|
||||||
return pAtPos;
|
return p;
|
||||||
}
|
}
|
||||||
return (int8_t)0;
|
return (int8_t)0;
|
||||||
};
|
};
|
||||||
for (int r = 0; r < 6; r++)
|
for (int r = 0; r < 6; r++)
|
||||||
for (int c = 0; c < 4; c++)
|
for (int c = 0; c < 4; c++)
|
||||||
{
|
{
|
||||||
int8_t res = checkMatch(c, r, 1, 0);
|
int8_t res = check(c, r, 1, 0);
|
||||||
if (res)
|
if (res)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
for (int r = 0; r < 3; r++)
|
for (int r = 0; r < 3; r++)
|
||||||
for (int c = 0; c < 7; c++)
|
for (int c = 0; c < 7; c++)
|
||||||
{
|
{
|
||||||
int8_t res = checkMatch(c, r, 0, 1);
|
int8_t res = check(c, r, 0, 1);
|
||||||
if (res)
|
if (res)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
for (int r = 0; r < 3; r++)
|
for (int r = 0; r < 3; r++)
|
||||||
for (int c = 0; c < 4; c++)
|
for (int c = 0; c < 4; c++)
|
||||||
{
|
{
|
||||||
int8_t res = checkMatch(c, r, 1, 1);
|
int8_t res = check(c, r, 1, 1);
|
||||||
if (res)
|
if (res)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
for (int r = 3; r < 6; r++)
|
for (int r = 3; r < 6; r++)
|
||||||
for (int c = 0; c < 4; c++)
|
for (int c = 0; c < 4; c++)
|
||||||
{
|
{
|
||||||
int8_t res = checkMatch(c, r, 1, -1);
|
int8_t res = check(c, r, 1, -1);
|
||||||
if (res)
|
if (res)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int minimax(int depth, int alpha, int beta, bool isMax, int8_t aiPlayer, int8_t humanPlayer, int8_t rootCol)
|
int minimax(int depth, int alpha, int beta, bool isMax, int8_t aiP, int8_t huP, int8_t rootCol)
|
||||||
{
|
{
|
||||||
if (depth % 2 == 0)
|
if (gameState == DEMO && digitalRead(ENC_SW) == LOW)
|
||||||
{
|
|
||||||
if (digitalRead(ENC_SW) == LOW)
|
|
||||||
{
|
{
|
||||||
abortAi = true;
|
abortAi = true;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (depth >= current_look_ahead - 1)
|
if (depth >= current_look_ahead - 1)
|
||||||
updateThinkingVisuals(aiPlayer, rootCol);
|
updateThinkingVisuals(aiP, rootCol);
|
||||||
else
|
else
|
||||||
yield();
|
yield();
|
||||||
|
|
||||||
if (abortAi)
|
if (abortAi)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
int8_t winner = scanBoard();
|
int8_t win = scanBoard();
|
||||||
if (winner == aiPlayer)
|
if (win == aiP)
|
||||||
return 1000 + depth; // Win sooner is better
|
return 1000 + depth;
|
||||||
if (winner == humanPlayer)
|
if (win == huP)
|
||||||
return -1000 - depth; // Lose later is better
|
return -1000 - depth;
|
||||||
if (depth == 0 || isBoardFull())
|
if (depth == 0 || isBoardFull())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
int colOrder[] = {3, 2, 4, 1, 5, 0, 6};
|
int colOrder[] = {3, 2, 4, 1, 5, 0, 6};
|
||||||
int bestScore = isMax ? -10000 : 10000;
|
int best = isMax ? -10000 : 10000;
|
||||||
|
for (int c : colOrder)
|
||||||
for (int column : colOrder)
|
|
||||||
{
|
{
|
||||||
if (abortAi)
|
if (abortAi)
|
||||||
return 0;
|
return 0;
|
||||||
int row = getFirstEmptyRow(column);
|
int r = getFirstEmptyRow(c);
|
||||||
if (row != -1)
|
if (r != -1)
|
||||||
{
|
{
|
||||||
board[column][row] = isMax ? aiPlayer : humanPlayer;
|
board[c][r] = isMax ? aiP : huP;
|
||||||
int score = minimax(depth - 1, alpha, beta, !isMax, aiPlayer, humanPlayer, (depth == current_look_ahead ? column : rootCol));
|
int score = minimax(depth - 1, alpha, beta, !isMax, aiP, huP, (depth == current_look_ahead ? c : rootCol));
|
||||||
board[column][row] = 0;
|
board[c][r] = 0;
|
||||||
if (isMax)
|
if (isMax)
|
||||||
{
|
{
|
||||||
bestScore = max(bestScore, score);
|
if (score > best)
|
||||||
alpha = max(alpha, bestScore);
|
best = score;
|
||||||
|
if (best > alpha)
|
||||||
|
alpha = best;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bestScore = min(bestScore, score);
|
if (score < best)
|
||||||
beta = min(beta, bestScore);
|
best = score;
|
||||||
|
if (best < beta)
|
||||||
|
beta = best;
|
||||||
}
|
}
|
||||||
if (beta <= alpha)
|
if (beta <= alpha)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bestScore;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
void performAiMove(int8_t aiPlayer)
|
void performAiMove(int8_t aiP)
|
||||||
{
|
{
|
||||||
abortAi = false;
|
abortAi = false;
|
||||||
int humanPlayer = (aiPlayer == 1) ? 2 : 1;
|
int huP = (aiP == 1) ? 2 : 1;
|
||||||
int bestScore = -30000;
|
int bestScore = -30000;
|
||||||
int bestCol = 3;
|
int bestCol = 3;
|
||||||
int originalPly = current_look_ahead;
|
int originalPly = current_look_ahead;
|
||||||
current_look_ahead = (gameState == DEMO) ? demoPly : getDynamicPly();
|
current_look_ahead = (gameState == DEMO) ? demoPly : getDynamicPly();
|
||||||
|
|
||||||
// PHASE 1: Immediate Win Check (OFFENSE)
|
for (int c = 0; c < COLS; c++)
|
||||||
for (int column = 0; column < COLS; column++)
|
|
||||||
{
|
{
|
||||||
int row = getFirstEmptyRow(column);
|
if (gameState == DEMO && digitalRead(ENC_SW) == LOW)
|
||||||
if (row != -1)
|
|
||||||
{
|
{
|
||||||
board[column][row] = aiPlayer;
|
abortAi = true;
|
||||||
if (scanBoard() == aiPlayer)
|
goto finalizeMove;
|
||||||
{
|
|
||||||
board[column][row] = 0;
|
|
||||||
bestCol = column;
|
|
||||||
goto finalizeMove; // TAKE THE WIN IMMEDIATELY
|
|
||||||
}
|
}
|
||||||
board[column][row] = 0;
|
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] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (int c : {3, 2, 4, 1, 5, 0, 6})
|
||||||
// PHASE 2: Immediate Block Check (DEFENSE)
|
|
||||||
for (int column = 0; column < COLS; column++)
|
|
||||||
{
|
|
||||||
int row = getFirstEmptyRow(column);
|
|
||||||
if (row != -1)
|
|
||||||
{
|
|
||||||
board[column][row] = humanPlayer;
|
|
||||||
if (scanBoard() == humanPlayer)
|
|
||||||
{
|
|
||||||
board[column][row] = 0;
|
|
||||||
bestCol = column;
|
|
||||||
goto finalizeMove; // MUST BLOCK
|
|
||||||
}
|
|
||||||
board[column][row] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PHASE 3: Minimax Look-ahead
|
|
||||||
for (int column : {3, 2, 4, 1, 5, 0, 6})
|
|
||||||
{
|
{
|
||||||
if (abortAi)
|
if (abortAi)
|
||||||
goto finalizeMove;
|
goto finalizeMove;
|
||||||
int row = getFirstEmptyRow(column);
|
int r = getFirstEmptyRow(c);
|
||||||
if (row != -1)
|
if (r != -1)
|
||||||
{
|
{
|
||||||
board[column][row] = aiPlayer;
|
board[c][r] = aiP;
|
||||||
int score = minimax(current_look_ahead, -30000, 30000, false, aiPlayer, humanPlayer, column);
|
int score = minimax(current_look_ahead, -30000, 30000, false, aiP, huP, c);
|
||||||
board[column][row] = 0;
|
board[c][r] = 0;
|
||||||
if (score > bestScore)
|
if (score > bestScore)
|
||||||
{
|
{
|
||||||
bestScore = score;
|
bestScore = score;
|
||||||
bestCol = column;
|
bestCol = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((gameState == DEMO || blunder_enabled) && random(100) < 20 && !abortAi)
|
|
||||||
{
|
|
||||||
int randomColumn = random(0, 7);
|
|
||||||
if (getFirstEmptyRow(randomColumn) != -1)
|
|
||||||
bestCol = randomColumn;
|
|
||||||
}
|
|
||||||
|
|
||||||
finalizeMove:
|
finalizeMove:
|
||||||
current_look_ahead = originalPly;
|
current_look_ahead = originalPly;
|
||||||
if (!abortAi)
|
if (!abortAi)
|
||||||
{
|
{
|
||||||
moveDiscToCol(activeCol, bestCol, aiPlayer, 100);
|
moveDiscToCol(activeCol, bestCol, aiP, 80);
|
||||||
delay(450);
|
if (!abortAi)
|
||||||
animateDrop(bestCol, aiPlayer);
|
{
|
||||||
|
delay(150);
|
||||||
|
animateDrop(bestCol, aiP);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +369,7 @@ void handleRoot()
|
|||||||
{
|
{
|
||||||
String html = "<html><head><meta name='viewport' content='width=device-width, initial-scale=1'><style>body{font-family:sans-serif;background:#121212;color:white;text-align:center;} .card{background:#222;padding:25px;border-radius:15px;display:inline-block;margin-top:20px;} input{width:100%;padding:10px;margin:10px 0;border-radius:5px;border:none;}</style></head><body><h1>Connect 4 Admin</h1><div class='card'><form action='/save' method='POST'>";
|
String html = "<html><head><meta name='viewport' content='width=device-width, initial-scale=1'><style>body{font-family:sans-serif;background:#121212;color:white;text-align:center;} .card{background:#222;padding:25px;border-radius:15px;display:inline-block;margin-top:20px;} input{width:100%;padding:10px;margin:10px 0;border-radius:5px;border:none;}</style></head><body><h1>Connect 4 Admin</h1><div class='card'><form action='/save' method='POST'>";
|
||||||
html += "Base AI Ply:<input type='number' name='ply' value='" + String(current_look_ahead) + "'>Brightness:<input type='number' name='br' value='" + String(current_brightness) + "'>Idle Timeout (s):<input type='number' name='idle' value='" + String(current_idle_timeout_ms / 1000) + "'>";
|
html += "Base AI Ply:<input type='number' name='ply' value='" + String(current_look_ahead) + "'>Brightness:<input type='number' name='br' value='" + String(current_brightness) + "'>Idle Timeout (s):<input type='number' name='idle' value='" + String(current_idle_timeout_ms / 1000) + "'>";
|
||||||
html += "Blunders: <input type='checkbox' name='blunder' " + String(blunder_enabled ? "checked" : "") + "><br>Evolution: <input type='checkbox' name='evolve' " + String(progressive_difficulty ? "checked" : "") + "><br><br><input type='submit' value='Save' style='background:#28a745;color:white;'></form></div></body></html>";
|
html += "Blunders: <input type='checkbox' name='blunder' " + String(blunder_enabled ? "checked" : "") + "><br>Evolution: <input type='checkbox' name='evolve' " + String(progressive_difficulty ? "checked" : "") + "><br><br><input type='submit' value='Save Settings' style='background:#28a745;color:white;'></form></div></body></html>";
|
||||||
server.send(200, "text/html", html);
|
server.send(200, "text/html", html);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +401,6 @@ void handleSave()
|
|||||||
|
|
||||||
void showMenu()
|
void showMenu()
|
||||||
{
|
{
|
||||||
isDemoOver = false;
|
|
||||||
FastLED.clear();
|
FastLED.clear();
|
||||||
#if SHOW_BORDER == 1
|
#if SHOW_BORDER == 1
|
||||||
for (int x = 0; x < 7; x++)
|
for (int x = 0; x < 7; x++)
|
||||||
@@ -415,9 +408,9 @@ void showMenu()
|
|||||||
for (int y = 1; y < 8; y++)
|
for (int y = 1; y < 8; y++)
|
||||||
leds[getIdx(7, y)] = CRGB::Blue;
|
leds[getIdx(7, y)] = CRGB::Blue;
|
||||||
#endif
|
#endif
|
||||||
|
CRGB pCol = (menuMode == 1) ? CRGB::Red : CRGB::Yellow;
|
||||||
if (menuMode < 2)
|
if (menuMode < 2)
|
||||||
{
|
{
|
||||||
CRGB pCol = (menuMode == 1) ? CRGB::Red : CRGB::Yellow;
|
|
||||||
for (int y = 3; y <= 6; y++)
|
for (int y = 3; y <= 6; y++)
|
||||||
leds[getIdx(3, y)] = pCol;
|
leds[getIdx(3, y)] = pCol;
|
||||||
leds[getIdx(2, 3)] = pCol;
|
leds[getIdx(2, 3)] = pCol;
|
||||||
@@ -446,17 +439,16 @@ void showMenu()
|
|||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
Serial.begin(115200);
|
|
||||||
prefs.begin("c4-game", false);
|
prefs.begin("c4-game", false);
|
||||||
current_look_ahead = prefs.getUChar("ply", 8);
|
current_look_ahead = prefs.getUChar("ply", 8);
|
||||||
current_brightness = prefs.getUChar("br", 25);
|
current_brightness = prefs.getUChar("br", 25);
|
||||||
current_idle_timeout_ms = prefs.getUInt("idle", 60) * 1000;
|
current_idle_timeout_ms = prefs.getUInt("idle", 60) * 1000;
|
||||||
blunder_enabled = prefs.getBool("blunder", false);
|
blunder_enabled = prefs.getBool("blunder", false);
|
||||||
progressive_difficulty = prefs.getBool("evolve", false);
|
progressive_difficulty = prefs.getBool("evolve", false);
|
||||||
FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);
|
FastLED.addLeds<WS2812B, 4, GRB>(leds, NUM_LEDS);
|
||||||
FastLED.setBrightness(current_brightness);
|
FastLED.setBrightness(current_brightness);
|
||||||
pinMode(ENC_SW, INPUT_PULLUP);
|
pinMode(2, INPUT_PULLUP);
|
||||||
WiFi.softAP("Connect4-Config", WIFI_PASSWORD);
|
WiFi.softAP("Connect4-Config", "12345678");
|
||||||
server.on("/", handleRoot);
|
server.on("/", handleRoot);
|
||||||
server.on("/save", HTTP_POST, handleSave);
|
server.on("/save", HTTP_POST, handleSave);
|
||||||
server.begin();
|
server.begin();
|
||||||
@@ -467,44 +459,47 @@ void setup()
|
|||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
server.handleClient();
|
server.handleClient();
|
||||||
long newPos = myEnc.read() / SENSITIVITY;
|
long newPos = myEnc.read() / 2;
|
||||||
bool pressed = (digitalRead(ENC_SW) == LOW);
|
bool currentButton = digitalRead(2);
|
||||||
|
|
||||||
if (newPos != oldEncPos || (pressed && (millis() - lastActivityTime > 500)))
|
bool pressed = false;
|
||||||
|
if (currentButton == LOW && lastButtonState == HIGH)
|
||||||
{
|
{
|
||||||
if (gameState >= 2 || gameState == DEMO)
|
if (millis() > globalInputCooldown)
|
||||||
|
pressed = true;
|
||||||
|
}
|
||||||
|
lastButtonState = currentButton;
|
||||||
|
|
||||||
|
if ((newPos != oldEncPos || pressed) && (gameState >= 3 || gameState == DEMO))
|
||||||
{
|
{
|
||||||
abortAi = true;
|
abortAi = true;
|
||||||
memset(board, 0, sizeof(board));
|
memset(board, 0, sizeof(board));
|
||||||
winnerPlayer = 0;
|
winnerPlayer = 0;
|
||||||
demoResetTimer = 0;
|
|
||||||
for (int i = 0; i < 10; i++)
|
for (int i = 0; i < 10; i++)
|
||||||
{
|
{
|
||||||
fadeToBlackBy(leds, NUM_LEDS, 40);
|
fadeToBlackBy(leds, NUM_LEDS, 50);
|
||||||
FastLED.show();
|
FastLED.show();
|
||||||
delay(20);
|
delay(15);
|
||||||
}
|
}
|
||||||
gameState = MENU;
|
gameState = MENU;
|
||||||
showMenu();
|
showMenu();
|
||||||
oldEncPos = newPos;
|
oldEncPos = newPos;
|
||||||
lastActivityTime = millis();
|
lastActivityTime = millis();
|
||||||
delay(300);
|
globalInputCooldown = millis() + 600;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastActivityTime = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t activeLimit = (gameState == PLAYING) ? (current_idle_timeout_ms * 2) : current_idle_timeout_ms;
|
if (gameState != DEMO && (gameState < 3) && (millis() - lastActivityTime > current_idle_timeout_ms))
|
||||||
if (gameState != DEMO && (gameState < 2) && (millis() - lastActivityTime > activeLimit))
|
|
||||||
{
|
{
|
||||||
gameState = DEMO;
|
gameState = DEMO;
|
||||||
memset(board, 0, sizeof(board));
|
memset(board, 0, sizeof(board));
|
||||||
currentPlayer = 1;
|
currentPlayer = 1;
|
||||||
demoPly = random(3, 7);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameState == MENU)
|
if (gameState == MENU)
|
||||||
|
{
|
||||||
|
if (millis() > globalInputCooldown)
|
||||||
{
|
{
|
||||||
if (newPos != oldEncPos)
|
if (newPos != oldEncPos)
|
||||||
{
|
{
|
||||||
@@ -515,17 +510,18 @@ void loop()
|
|||||||
if (pressed)
|
if (pressed)
|
||||||
{
|
{
|
||||||
memset(board, 0, sizeof(board));
|
memset(board, 0, sizeof(board));
|
||||||
gameState = PLAYING;
|
|
||||||
if (menuMode == 1)
|
if (menuMode == 1)
|
||||||
{
|
{
|
||||||
performAiMove(1);
|
currentPlayer = 1;
|
||||||
currentPlayer = 2;
|
gameState = AI_TURN;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
currentPlayer = 1;
|
currentPlayer = 1;
|
||||||
|
gameState = PLAYING;
|
||||||
|
}
|
||||||
|
globalInputCooldown = millis() + 400;
|
||||||
}
|
}
|
||||||
delay(300);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (gameState == PLAYING)
|
else if (gameState == PLAYING)
|
||||||
@@ -541,7 +537,6 @@ void loop()
|
|||||||
FastLED.show();
|
FastLED.show();
|
||||||
if (pressed)
|
if (pressed)
|
||||||
{
|
{
|
||||||
lastActivityTime = millis();
|
|
||||||
int row = getFirstEmptyRow(activeCol);
|
int row = getFirstEmptyRow(activeCol);
|
||||||
if (row != -1)
|
if (row != -1)
|
||||||
{
|
{
|
||||||
@@ -560,6 +555,19 @@ void loop()
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (menuMode < 2)
|
if (menuMode < 2)
|
||||||
|
{
|
||||||
|
gameState = AI_TURN;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentPlayer = (currentPlayer == 1) ? 2 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastActivityTime = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (gameState == AI_TURN)
|
||||||
{
|
{
|
||||||
int8_t aiP = (menuMode == 0) ? 2 : 1;
|
int8_t aiP = (menuMode == 0) ? 2 : 1;
|
||||||
performAiMove(aiP);
|
performAiMove(aiP);
|
||||||
@@ -577,14 +585,10 @@ void loop()
|
|||||||
gameState = FINISHED_DRAW;
|
gameState = FINISHED_DRAW;
|
||||||
demoResetTimer = millis();
|
demoResetTimer = millis();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
currentPlayer = (currentPlayer == 1) ? 2 : 1;
|
gameState = PLAYING;
|
||||||
}
|
currentPlayer = (aiP == 1) ? 2 : 1;
|
||||||
}
|
|
||||||
delay(300);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -592,7 +596,7 @@ void loop()
|
|||||||
{
|
{
|
||||||
renderBoard();
|
renderBoard();
|
||||||
FastLED.show();
|
FastLED.show();
|
||||||
delay(600);
|
delay(300);
|
||||||
performAiMove(currentPlayer);
|
performAiMove(currentPlayer);
|
||||||
if (!abortAi)
|
if (!abortAi)
|
||||||
{
|
{
|
||||||
@@ -633,7 +637,11 @@ void loop()
|
|||||||
if (winMask[i])
|
if (winMask[i])
|
||||||
leds[i] = toggle ? (winnerPlayer == 1 ? CRGB::Yellow : CRGB::Red) : CRGB::Black;
|
leds[i] = toggle ? (winnerPlayer == 1 ? CRGB::Yellow : CRGB::Red) : CRGB::Black;
|
||||||
else
|
else
|
||||||
leds[i].nscale8(60);
|
{
|
||||||
|
CRGB c = leds[i];
|
||||||
|
c.nscale8(60);
|
||||||
|
leds[i] = c;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (gameState == FINISHED_DRAW)
|
else if (gameState == FINISHED_DRAW)
|
||||||
{
|
{
|
||||||
@@ -648,7 +656,6 @@ void loop()
|
|||||||
memset(board, 0, sizeof(board));
|
memset(board, 0, sizeof(board));
|
||||||
gameState = DEMO;
|
gameState = DEMO;
|
||||||
demoResetTimer = 0;
|
demoResetTimer = 0;
|
||||||
demoPly = random(3, 7);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user