[init] Initial commit
This commit is contained in:
+349
@@ -0,0 +1,349 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user