#include "game.h" #include "touch.h" #include "storage.h" // --- Global definitions --- Adafruit_ST7789 tft = Adafruit_ST7789(&SPI1, LCD_CS, LCD_DC, LCD_RST); int8_t board[COLS][ROWS]; WinPos winPos[4]; int winCount = 0; State gameState = MENU; int8_t menuMode = 0; int8_t currentPlayer = 1; int8_t winnerPlayer = 0; int8_t activeCol = 3; int8_t gameMenuMode = 0; uint8_t demoPly[2] = {4, 4}; bool abortAi = false; String currentMoves; uint8_t gameLevel = 0; uint8_t currentLookAhead = DEFAULT_LOOK_AHEAD; bool blunderEnabled = BLUNDER_ENABLED; uint8_t blunderChance = BLUNDER_CHANCE; GameEntry gameLog[MAX_GAME_LOG]; uint8_t gameLogCount = 0; uint32_t lastActivityTime = 0; uint32_t demoResetTimer = 0; uint32_t lastDemoMove = 0; bool flashToggle = true; uint32_t lastFlash = 0; bool needRedraw = true; const int8_t colOrder[] = {3, 2, 4, 1, 5, 0, 6}; // --- Board helpers --- uint16_t playerColor(int8_t p) { return p == 1 ? C_P1 : C_P2; } uint16_t playerColorDim(int8_t p) { return p == 1 ? C_P1DIM : C_P2DIM; } int cellX(int c) { return BRD_X + c * CELL + CELL / 2; } int cellY(int r) { return BRD_Y + (ROWS - 1 - r) * CELL + CELL / 2; } void resetBoard() { memset(board, 0, sizeof(board)); winnerPlayer = 0; winCount = 0; } int getFirstEmptyRow(int col) { for (int r = 0; r < ROWS; r++) if (board[col][r] == 0) return r; return -1; } bool isBoardFull() { for (int c = 0; c < COLS; c++) if (board[c][ROWS - 1] == 0) return false; return true; } bool isWinPos(int c, int r) { for (int i = 0; i < winCount; i++) if (winPos[i].c == c && winPos[i].r == r) return true; return false; } void randomizeDemoPlies() { uint8_t strong = random(4, 6), weak = random(2, 4); if (random(2)) { demoPly[0] = strong; demoPly[1] = weak; } else { demoPly[0] = weak; demoPly[1] = strong; } } // --- Game logic --- int8_t scanBoard() { winCount = 0; for (int r = 0; r < ROWS; r++) for (int c = 0; c <= COLS - 4; c++) { int8_t p = board[c][r]; if (p && board[c + 1][r] == p && board[c + 2][r] == p && board[c + 3][r] == p) { for (int i = 0; i < 4; i++) winPos[i] = {(int8_t)(c + i), (int8_t)r}; winCount = 4; return p; } } for (int r = 0; r <= ROWS - 4; r++) for (int c = 0; c < COLS; c++) { int8_t p = board[c][r]; if (p && board[c][r + 1] == p && board[c][r + 2] == p && board[c][r + 3] == p) { for (int i = 0; i < 4; i++) winPos[i] = {(int8_t)c, (int8_t)(r + i)}; winCount = 4; return p; } } for (int r = 0; r <= ROWS - 4; r++) for (int c = 0; c <= COLS - 4; c++) { int8_t p = board[c][r]; if (p && board[c + 1][r + 1] == p && board[c + 2][r + 2] == p && board[c + 3][r + 3] == p) { for (int i = 0; i < 4; i++) winPos[i] = {(int8_t)(c + i), (int8_t)(r + i)}; winCount = 4; return p; } } for (int r = 3; r < ROWS; r++) for (int c = 0; c <= COLS - 4; c++) { int8_t p = board[c][r]; if (p && board[c + 1][r - 1] == p && board[c + 2][r - 2] == p && board[c + 3][r - 3] == p) { for (int i = 0; i < 4; i++) winPos[i] = {(int8_t)(c + i), (int8_t)(r - i)}; winCount = 4; return p; } } return 0; } int evaluateBoard(int8_t aiP, int8_t huP) { int score = 0; for (int r = 0; r < ROWS; r++) { if (board[3][r] == aiP) score += 3; else if (board[3][r] == huP) score -= 3; } auto sw = [&](int c, int r, int dc, int dr) -> int { int ai = 0, hu = 0; for (int i = 0; i < 4; i++) { int8_t v = board[c + i * dc][r + i * dr]; if (v == aiP) ai++; else if (v == huP) hu++; } if (ai && hu) return 0; if (ai == 3) return 50; if (ai == 2) return 5; if (hu == 3) return -50; if (hu == 2) return -5; return 0; }; for (int r = 0; r < 6; r++) for (int c = 0; c < 4; c++) score += sw(c, r, 1, 0); for (int r = 0; r < 3; r++) for (int c = 0; c < 7; c++) score += sw(c, r, 0, 1); for (int r = 0; r < 3; r++) for (int c = 0; c < 4; c++) score += sw(c, r, 1, 1); for (int r = 3; r < 6; r++) for (int c = 0; c < 4; c++) score += sw(c, r, 1, -1); return score; } bool checkGameEnd() { winnerPlayer = scanBoard(); 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(); flashToggle = false; lastFlash = millis(); return true; } // --- AI --- int minimax(int depth, int alpha, int beta, bool isMax, int8_t aiP, int8_t huP) { if (gameState == DEMO && depth >= currentLookAhead - 1) { int16_t rx, ry; if (readRawTouch(rx, ry)) { abortAi = true; return 0; } } yield(); if (abortAi) return 0; int8_t win = scanBoard(); if (win == aiP) return 1000 + depth; if (win == huP) return -1000 - depth; if (depth == 0 || isBoardFull()) return evaluateBoard(aiP, huP); int best = isMax ? -10000 : 10000; for (int ci = 0; ci < COLS; ci++) { int c = colOrder[ci]; if (abortAi) return 0; int r = getFirstEmptyRow(c); if (r == -1) continue; board[c][r] = isMax ? aiP : huP; int score = minimax(depth - 1, alpha, beta, !isMax, aiP, huP); board[c][r] = 0; if (isMax) { if (score > best) best = score; if (best > alpha) alpha = best; } else { if (score < best) best = score; if (best < beta) beta = best; } if (beta <= alpha) break; } return best; } int computeAiMove(int8_t aiP) { abortAi = false; int8_t huP = (aiP == 1) ? 2 : 1; int bestScore = -30000, bestCol = 3; int originalPly = currentLookAhead; if (gameState == DEMO) currentLookAhead = demoPly[aiP - 1]; bool found = false; for (int c = 0; c < COLS && !found; c++) { int r = getFirstEmptyRow(c); if (r == -1) continue; board[c][r] = aiP; if (scanBoard() == aiP) { board[c][r] = 0; bestCol = c; found = true; } else board[c][r] = 0; } for (int c = 0; c < COLS && !found; c++) { int r = getFirstEmptyRow(c); if (r == -1) continue; board[c][r] = huP; if (scanBoard() == huP) { board[c][r] = 0; bestCol = c; found = true; } else board[c][r] = 0; } 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; } if (!found) { for (int ci = 0; ci < COLS; ci++) { int c = colOrder[ci]; if (abortAi) break; int r = getFirstEmptyRow(c); if (r == -1) continue; board[c][r] = aiP; int score = minimax(currentLookAhead, -30000, 30000, false, aiP, huP); board[c][r] = 0; if (score > bestScore) { bestScore = score; bestCol = c; } } } currentLookAhead = originalPly; return bestCol; }