Files
connect_four_pico/src/game.cpp
T
2026-03-27 15:37:18 +01:00

350 lines
8.4 KiB
C++

#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;
}