Checkers
Source: src/checkers.c · 556 lines · 26 functions.
Screen captures
Views of this part running on the Mini Arcade.

Module entry points
These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.
How to read these diagrams
Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.
These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.
Functions in this file
squareMask() · source line 36
static uint64_t squareMask(uint8_t square)
- Exit this function
return 1ULL << square;
Read original function source
static uint64_t squareMask(uint8_t square)
{
return 1ULL << square;
}opponent() · source line 41
static uint8_t opponent(uint8_t piece)
- Exit this function
return piece == BLACK_PIECE ? WHITE_PIECE : BLACK_PIECE;
Read original function source
static uint8_t opponent(uint8_t piece)
{
return piece == BLACK_PIECE
? WHITE_PIECE
: BLACK_PIECE;
}insideBoard() · source line 48
static bool insideBoard(int8_t row,int8_t column)
- Exit this function
return row >= 0 && row < 8 && column >= 0 && column < 8;
Read original function source
static bool insideBoard(int8_t row,int8_t column)
{
return row >= 0 && row < 8 && column >= 0 && column < 8;
}pieceMask() · source line 53
static uint64_t pieceMask(uint8_t piece)
- Exit this function
return piece == BLACK_PIECE ? board.black : board.white;
Read original function source
static uint64_t pieceMask(uint8_t piece)
{
return piece == BLACK_PIECE
? board.black
: board.white;
}occupied() · source line 60
static bool occupied(uint8_t square)
Calls into this documentation:
- Exit this function
return (board.black | board.white) & squareMask(square);
Read original function source
static bool occupied(uint8_t square)
{
return (board.black | board.white) & squareMask(square);
}isKing() · source line 65
static bool isKing(uint8_t square)
Calls into this documentation:
- Exit this function
return board.kings & squareMask(square);
Read original function source
static bool isKing(uint8_t square)
{
return board.kings & squareMask(square);
}countBits() · source line 70
static uint8_t countBits(uint64_t value)
- Execute
uint8_t count = 0; - WHILE loop
valueRepeat body while the loop condition permits; then continue below.
- Execute
count += value & 1ULL; - Execute
value >>= 1;
- Exit this function
return count;
Read original function source
static uint8_t countBits(uint64_t value)
{
uint8_t count = 0;
while (value)
{
count += value & 1ULL;
value >>= 1;
}
return count;
}countPieces() · source line 83
static uint8_t countPieces(uint8_t piece)
Calls into this documentation:
- Exit this function
return countBits(pieceMask(piece));
Read original function source
static uint8_t countPieces(uint8_t piece)
{
return countBits(pieceMask(piece));
}directionAllowed() · source line 88
static bool directionAllowed(uint8_t piece,bool king,int8_t rowDirection)
- Decision
kingTrue- Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return piece == BLACK_PIECE ? rowDirection > 0 : rowDirection < 0;
Read original function source
static bool directionAllowed(uint8_t piece,bool king,int8_t rowDirection)
{
if (king)
return true;
return piece == BLACK_PIECE
? rowDirection > 0
: rowDirection < 0;
}generateMoves() · source line 98
static uint8_t generateMoves(
uint8_t piece,
Move *moves,
uint8_t onlyFrom)Calls into this documentation:
- Execute
uint8_t count = 0; - Execute
bool captureAvailable = false; - Execute
const uint64_t ownPieces = pieceMask(piece); - Execute
const uint64_t otherPieces = pieceMask(opponent(piece)); - FOR loop
uint8_t from = 0; from < 64; from++Repeat body while the loop condition permits; then continue below.
- Decision
!(ownPieces & squareMask(from)) || (onlyFrom != NO_SQUARE && from != onlyFrom)True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int8_t row = from / 8; - Execute
const int8_t column = from % 8; - Execute
const bool king = isKing(from); - FOR loop
int8_t rowDirection = -1; rowDirection <= 1; rowDirection += 2Repeat body while the loop condition permits; then continue below.
- Decision
!directionAllowed(piece,king,rowDirection)True- Next loop iteration
continue;
FalseContinue after this decision.
- FOR loop
int8_t columnDirection = -1; columnDirection <= 1; columnDirection += 2Repeat body while the loop condition permits; then continue below.
- Execute
const int8_t middleRow = row + rowDirection; - Execute
const int8_t middleColumn = column + columnDirection; - Execute
const int8_t toRow = row + rowDirection * 2; - Execute
const int8_t toColumn = column + columnDirection * 2; - Decision
!insideBoard(middleRow,middleColumn) || !insideBoard(toRow,toColumn)True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const uint8_t middle = middleRow * 8 + middleColumn; - Execute
const uint8_t to = toRow * 8 + toColumn; - Decision
(otherPieces & squareMask(middle)) && !occupied(to)True- Execute
captureAvailable = true; - Decision
count < MAX_MOVESTrue- Execute
moves[count++] = (Move){from,to,middle,true};
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
captureAvailable || onlyFrom != NO_SQUARETrue- Exit this function
return count;
FalseContinue after this decision.
- FOR loop
uint8_t from = 0; from < 64; from++Repeat body while the loop condition permits; then continue below.
- Decision
!(ownPieces & squareMask(from))True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int8_t row = from / 8; - Execute
const int8_t column = from % 8; - Execute
const bool king = isKing(from); - FOR loop
int8_t rowDirection = -1; rowDirection <= 1; rowDirection += 2Repeat body while the loop condition permits; then continue below.
- Decision
!directionAllowed(piece,king,rowDirection)True- Next loop iteration
continue;
FalseContinue after this decision.
- FOR loop
int8_t columnDirection = -1; columnDirection <= 1; columnDirection += 2Repeat body while the loop condition permits; then continue below.
- Execute
const int8_t toRow = row + rowDirection; - Execute
const int8_t toColumn = column + columnDirection; - Decision
!insideBoard(toRow,toColumn)True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const uint8_t to = toRow * 8 + toColumn; - Decision
!occupied(to) && count < MAX_MOVESTrue- Execute
moves[count++] = (Move){from,to,NO_SQUARE,false};
FalseContinue after this decision.
- Exit this function
return count;
Read original function source
static uint8_t generateMoves(
uint8_t piece,
Move *moves,
uint8_t onlyFrom)
{
uint8_t count = 0;
bool captureAvailable = false;
const uint64_t ownPieces = pieceMask(piece);
const uint64_t otherPieces = pieceMask(opponent(piece));
for (uint8_t from = 0; from < 64; from++)
{
if (!(ownPieces & squareMask(from)) ||
(onlyFrom != NO_SQUARE && from != onlyFrom))
continue;
const int8_t row = from / 8;
const int8_t column = from % 8;
const bool king = isKing(from);
for (int8_t rowDirection = -1;
rowDirection <= 1;
rowDirection += 2)
{
if (!directionAllowed(piece,king,rowDirection))
continue;
for (int8_t columnDirection = -1;
columnDirection <= 1;
columnDirection += 2)
{
const int8_t middleRow = row + rowDirection;
const int8_t middleColumn = column + columnDirection;
const int8_t toRow = row + rowDirection * 2;
const int8_t toColumn = column + columnDirection * 2;
if (!insideBoard(middleRow,middleColumn) ||
!insideBoard(toRow,toColumn))
continue;
const uint8_t middle = middleRow * 8 + middleColumn;
const uint8_t to = toRow * 8 + toColumn;
if ((otherPieces & squareMask(middle)) &&
!occupied(to))
{
captureAvailable = true;
if (count < MAX_MOVES)
moves[count++] = (Move){from,to,middle,true};
}
}
}
}
if (captureAvailable || onlyFrom != NO_SQUARE)
return count;
for (uint8_t from = 0; from < 64; from++)
{
if (!(ownPieces & squareMask(from)))
continue;
const int8_t row = from / 8;
const int8_t column = from % 8;
const bool king = isKing(from);
for (int8_t rowDirection = -1;
rowDirection <= 1;
rowDirection += 2)
{
if (!directionAllowed(piece,king,rowDirection))
continue;
for (int8_t columnDirection = -1;
columnDirection <= 1;
columnDirection += 2)
{
const int8_t toRow = row + rowDirection;
const int8_t toColumn = column + columnDirection;
if (!insideBoard(toRow,toColumn))
continue;
const uint8_t to = toRow * 8 + toColumn;
if (!occupied(to) && count < MAX_MOVES)
moves[count++] = (Move){from,to,NO_SQUARE,false};
}
}
}
return count;
}resetBoard() · source line 193
static void resetBoard(void)
Calls into this documentation:
- Execute
board = (Board){0}; - FOR loop
uint8_t row = 0; row < 3; row++Repeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t column = 0; column < 8; column++Repeat body while the loop condition permits; then continue below.
- Decision
(row + column) % 2 == 1True- Execute
board.black |= squareMask(row * 8 + column);
FalseContinue after this decision.
- FOR loop
uint8_t row = 5; row < 8; row++Repeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t column = 0; column < 8; column++Repeat body while the loop condition permits; then continue below.
- Decision
(row + column) % 2 == 1True- Execute
board.white |= squareMask(row * 8 + column);
FalseContinue after this decision.
- Execute
currentPlayer = BLACK_PIECE; - Execute
forcedFrom = NO_SQUARE; - Execute
selectedMove = 0; - Execute
noProgressMoves = 0;
Read original function source
static void resetBoard(void)
{
board = (Board){0};
for (uint8_t row = 0; row < 3; row++)
{
for (uint8_t column = 0; column < 8; column++)
{
if ((row + column) % 2 == 1)
board.black |= squareMask(row * 8 + column);
}
}
for (uint8_t row = 5; row < 8; row++)
{
for (uint8_t column = 0; column < 8; column++)
{
if ((row + column) % 2 == 1)
board.white |= squareMask(row * 8 + column);
}
}
currentPlayer = BLACK_PIECE;
forcedFrom = NO_SQUARE;
selectedMove = 0;
noProgressMoves = 0;
}applyMove() · source line 221
static bool applyMove(const Move move)
Calls into this documentation:
- Execute
const uint64_t fromMask = squareMask(move.from); - Execute
const uint64_t toMask = squareMask(move.to); - Execute
const bool king = board.kings & fromMask; - Decision
currentPlayer == BLACK_PIECETrue- Execute
board.black &= ~fromMask; - Execute
board.black |= toMask;
False- Execute
board.white &= ~fromMask; - Execute
board.white |= toMask;
- Execute
board.kings &= ~fromMask; - Decision
kingTrue- Execute
board.kings |= toMask;
FalseContinue after this decision.
- Decision
move.captureTrue- Execute
const uint64_t capturedMask = squareMask(move.captured); - Execute
board.black &= ~capturedMask; - Execute
board.white &= ~capturedMask; - Execute
board.kings &= ~capturedMask;
FalseContinue after this decision.
- Execute
const uint8_t destinationRow = move.to / 8; - Execute
const bool promoted = !king && ((currentPlayer == BLACK_PIECE && destinationRow == 7) || (currentPlayer == WHITE_PIECE && destinationRow == 0)); - Decision
promotedTrue- Execute
board.kings |= toMask;
FalseContinue after this decision.
- Decision
move.capture || promotedTrue- Execute
noProgressMoves = 0;
False- Decision
noProgressMoves < UINT8_MAXTrue- Execute
noProgressMoves++;
FalseContinue after this decision.
- Exit this function
return promoted;
Read original function source
static bool applyMove(const Move move)
{
const uint64_t fromMask = squareMask(move.from);
const uint64_t toMask = squareMask(move.to);
const bool king = board.kings & fromMask;
if (currentPlayer == BLACK_PIECE)
{
board.black &= ~fromMask;
board.black |= toMask;
}
else
{
board.white &= ~fromMask;
board.white |= toMask;
}
board.kings &= ~fromMask;
if (king)
board.kings |= toMask;
if (move.capture)
{
const uint64_t capturedMask = squareMask(move.captured);
board.black &= ~capturedMask;
board.white &= ~capturedMask;
board.kings &= ~capturedMask;
}
const uint8_t destinationRow = move.to / 8;
const bool promoted = !king &&
((currentPlayer == BLACK_PIECE && destinationRow == 7) ||
(currentPlayer == WHITE_PIECE && destinationRow == 0));
if (promoted)
board.kings |= toMask;
if (move.capture || promoted)
noProgressMoves = 0;
else if (noProgressMoves < UINT8_MAX)
noProgressMoves++;
return promoted;
}refreshLegalMoves() · source line 267
static void refreshLegalMoves(void)
Calls into this documentation:
- Execute
legalMoveCount = generateMoves(currentPlayer, legalMoves, forcedFrom); - Execute
selectedMove = 0; - Execute
stickCentred = false; - Execute
heldDirection = 0;
Read original function source
static void refreshLegalMoves(void)
{
legalMoveCount = generateMoves(currentPlayer, legalMoves, forcedFrom);
selectedMove = 0;
stickCentred = false;
heldDirection = 0;
}incrementScore() · source line 275
static void incrementScore(uint32_t *value)
- Decision
*value < UINT32_MAXTrue- Execute
++*value;
FalseContinue after this decision.
Read original function source
static void incrementScore(uint32_t *value)
{
if (*value < UINT32_MAX) ++*value;
}finishGame() · source line 280
static void finishGame(uint8_t winner)
Calls into this documentation:
- Execute
gameState = STATE_FINISHED; - Execute
stateStarted = clockNow; - Execute
boardMessage = winner == EMPTY ? "DRAW" : winner == BLACK_PIECE ? "BLACK WINS" : "WHITE WINS"; - Decision
autoModeTrue- Decision
winner == goldEarningPiece && !addAIGold(CHECKERS_WIN_GOLD)True- Execute
goldSaveFailed = true;
FalseContinue after this decision.
False- Decision
winner == humanPieceTrue- Execute
incrementScore(&scores.wins); - Execute
incrementScore(&scores.streak); - Decision
!addUserGoldWon(CHECKERS_WIN_GOLD)True- Execute
goldSaveFailed = true;
FalseContinue after this decision.
- Execute
boardMessage = "YOU WIN";
False- Execute
scores.streak = 0; - Decision
winner == EMPTYTrue- Execute
incrementScore(&scores.draws);
False- Execute
incrementScore(&scores.losses); - Execute
boardMessage = "CPU WINS";
- Decision
scoreStatus == SCORES_OK || scoreStatus == SCORES_NOT_FOUNDTrue- Execute
scoreSaveFailed = !game_scores_save(GAME_CHECKERS, &scores); - Decision
!scoreSaveFailedTrue- Execute
scoreStatus = SCORES_OK;
FalseContinue after this decision.
False- Execute
scoreSaveFailed = true;
- Execute
ESP_LOGI("checkers", "%s; score save %s; Gold %s", boardMessage, scoreSaveFailed ? "ERROR" : autoMode ? "NOT REQUIRED" : "OK", goldSaveFailed ? "ERROR" : "OK");
Read original function source
static void finishGame(uint8_t winner)
{
gameState = STATE_FINISHED;
stateStarted = clockNow;
boardMessage = winner == EMPTY ? "DRAW" : winner == BLACK_PIECE ? "BLACK WINS" : "WHITE WINS";
if (autoMode) {
if (winner == goldEarningPiece && !addAIGold(CHECKERS_WIN_GOLD)) goldSaveFailed = true;
} else {
if (winner == humanPiece) {
incrementScore(&scores.wins);
incrementScore(&scores.streak);
if (!addUserGoldWon(CHECKERS_WIN_GOLD)) goldSaveFailed = true;
boardMessage = "YOU WIN";
} else {
scores.streak = 0;
if (winner == EMPTY) incrementScore(&scores.draws);
else { incrementScore(&scores.losses); boardMessage = "CPU WINS"; }
}
if (scoreStatus == SCORES_OK || scoreStatus == SCORES_NOT_FOUND) {
scoreSaveFailed = !game_scores_save(GAME_CHECKERS, &scores);
if (!scoreSaveFailed) scoreStatus = SCORES_OK;
} else scoreSaveFailed = true;
}
ESP_LOGI("checkers", "%s; score save %s; Gold %s", boardMessage,
scoreSaveFailed ? "ERROR" : autoMode ? "NOT REQUIRED" : "OK",
goldSaveFailed ? "ERROR" : "OK");
}finishTurn() · source line 308
static void finishTurn(void)
Calls into this documentation:
- Decision
noProgressMoves >= NO_PROGRESS_DRAW_LIMITTrue- Execute
finishGame(EMPTY); - Exit this function
return;
FalseContinue after this decision.
- Execute
const uint8_t nextPlayer = opponent(currentPlayer); - Decision
countPieces(nextPlayer) == 0True- Execute
finishGame(currentPlayer); - Exit this function
return;
FalseContinue after this decision.
- Execute
currentPlayer = nextPlayer; - Execute
forcedFrom = NO_SQUARE; - Execute
refreshLegalMoves(); - Decision
legalMoveCount == 0True- Execute
finishGame(opponent(currentPlayer)); - Exit this function
return;
FalseContinue after this decision.
- Execute
boardMessage = autoMode || currentPlayer != humanPiece ? "CPU TURN" : "YOUR TURN"; - Execute
computerMoveDue = clockNow + aiSpeedDelay(aiSpeedIndex);
Read original function source
static void finishTurn(void)
{
if (noProgressMoves >= NO_PROGRESS_DRAW_LIMIT) { finishGame(EMPTY); return; }
const uint8_t nextPlayer = opponent(currentPlayer);
if (countPieces(nextPlayer) == 0) { finishGame(currentPlayer); return; }
currentPlayer = nextPlayer;
forcedFrom = NO_SQUARE;
refreshLegalMoves();
if (legalMoveCount == 0) { finishGame(opponent(currentPlayer)); return; }
boardMessage = autoMode || currentPlayer != humanPiece ? "CPU TURN" : "YOUR TURN";
computerMoveDue = clockNow + aiSpeedDelay(aiSpeedIndex);
}performMove() · source line 321
static void performMove(uint8_t index)
Calls into this documentation:
- Decision
index >= legalMoveCountTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const Move move = legalMoves[index]; - Execute
const bool promoted = applyMove(move); - Execute
audioPlay(move.capture ? AUDIO_SFX_BOARD_CAPTURE : AUDIO_SFX_BOARD_PLACE); - Decision
move.capture && !promotedTrue- Execute
forcedFrom = move.to; - Execute
refreshLegalMoves(); - Decision
legalMoveCount > 0True- Execute
boardMessage = "JUMP AGAIN"; - Execute
computerMoveDue = clockNow + aiSpeedDelay(aiSpeedIndex); - Exit this function
return;
FalseContinue after this decision.
FalseContinue after this decision.
- Execute
finishTurn();
Read original function source
static void performMove(uint8_t index)
{
if (index >= legalMoveCount) return;
const Move move = legalMoves[index];
const bool promoted = applyMove(move);
audioPlay(move.capture ? AUDIO_SFX_BOARD_CAPTURE : AUDIO_SFX_BOARD_PLACE);
if (move.capture && !promoted) {
forcedFrom = move.to;
refreshLegalMoves();
if (legalMoveCount > 0) {
boardMessage = "JUMP AGAIN";
computerMoveDue = clockNow + aiSpeedDelay(aiSpeedIndex);
return;
}
}
finishTurn();
}chooseComputerMove() · source line 339
static uint8_t chooseComputerMove(void)
Calls into this documentation:
- Execute
uint8_t best = 0; - Execute
int bestScore = -10000; - FOR loop
uint8_t i = 0; i < legalMoveCount; ++iRepeat body while the loop condition permits; then continue below.
- Execute
const Move move = legalMoves[i]; - Execute
const bool king = isKing(move.from); - Execute
const uint8_t row = move.to / 8, column = move.to % 8; - Execute
int score = (int)(esp_random() % 12); - Decision
move.captureTrue- Execute
score += 100;
FalseContinue after this decision.
- Decision
!king && ((currentPlayer == BLACK_PIECE && row == 7) || (currentPlayer == WHITE_PIECE && row == 0))True- Execute
score += 35;
FalseContinue after this decision.
- Decision
kingTrue- Execute
score += 8;
FalseContinue after this decision.
- Decision
column >= 2 && column <= 5True- Execute
score += 5;
FalseContinue after this decision.
- Decision
column == 0 || column == 7True- Execute
score += 3;
FalseContinue after this decision.
- Decision
score > bestScoreTrue- Execute
bestScore = score; - Execute
best = i;
FalseContinue after this decision.
- Exit this function
return best;
Read original function source
static uint8_t chooseComputerMove(void)
{
uint8_t best = 0;
int bestScore = -10000;
for (uint8_t i = 0; i < legalMoveCount; ++i) {
const Move move = legalMoves[i];
const bool king = isKing(move.from);
const uint8_t row = move.to / 8, column = move.to % 8;
int score = (int)(esp_random() % 12);
if (move.capture) score += 100;
if (!king && ((currentPlayer == BLACK_PIECE && row == 7) ||
(currentPlayer == WHITE_PIECE && row == 0))) score += 35;
if (king) score += 8;
if (column >= 2 && column <= 5) score += 5;
if (column == 0 || column == 7) score += 3;
if (score > bestScore) { bestScore = score; best = i; }
}
return best;
}choosePiece() · source line 359
static void choosePiece(void)
- Execute
gameState = STATE_CHOOSE_PIECE; - Execute
autoMode = false; - Execute
humanPiece = BLACK_PIECE; - Execute
stickCentred = false; - Execute
heldDirection = 0;
Read original function source
static void choosePiece(void)
{
gameState = STATE_CHOOSE_PIECE;
autoMode = false;
humanPiece = BLACK_PIECE;
stickCentred = false;
heldDirection = 0;
}beginGame() · source line 368
static void beginGame(void)
Calls into this documentation:
- Execute
resetBoard(); - Execute
gameState = STATE_PLAYING; - Decision
autoModeTrue- Execute
goldEarningPiece = esp_random() % 2 == 0 ? BLACK_PIECE : WHITE_PIECE;
FalseContinue after this decision.
- Execute
refreshLegalMoves(); - Execute
boardMessage = autoMode || humanPiece != BLACK_PIECE ? "CPU TURN" : "YOUR TURN"; - Execute
computerMoveDue = clockNow + aiSpeedDelay(aiSpeedIndex);
Read original function source
static void beginGame(void)
{
resetBoard();
gameState = STATE_PLAYING;
if (autoMode) goldEarningPiece = esp_random() % 2 == 0 ? BLACK_PIECE : WHITE_PIECE;
refreshLegalMoves();
boardMessage = autoMode || humanPiece != BLACK_PIECE ? "CPU TURN" : "YOUR TURN";
computerMoveDue = clockNow + aiSpeedDelay(aiSpeedIndex);
}startCheckers() · source line 378
void startCheckers(uint32_t now_ms)
Calls into this documentation:
- Execute
clockNow = stateStarted = now_ms; - Execute
running = true; - Execute
scoreSaveFailed = goldSaveFailed = false; - Execute
scores = (game_scores_t){0}; - Execute
scoreStatus = game_scores_load(GAME_CHECKERS, &scores); - Execute
aiSpeedIndex = AI_SPEED_DEFAULT_INDEX; - Execute
resetBoard(); - Execute
choosePiece(); - Execute
gameState = STATE_SHOW_SCORES; - Execute
ESP_LOGI("checkers", "START: %s", game_scores_status_text(scoreStatus));
Read original function source
void startCheckers(uint32_t now_ms)
{
clockNow = stateStarted = now_ms;
running = true;
scoreSaveFailed = goldSaveFailed = false;
scores = (game_scores_t){0};
scoreStatus = game_scores_load(GAME_CHECKERS, &scores);
aiSpeedIndex = AI_SPEED_DEFAULT_INDEX;
resetBoard();
choosePiece();
gameState = STATE_SHOW_SCORES;
ESP_LOGI("checkers", "START: %s", game_scores_status_text(scoreStatus));
}stopCheckers() · source line 392
void stopCheckers(void)
- Execute
running = false; - Execute
ESP_LOGI("checkers", "STOP");
Read original function source
void stopCheckers(void)
{
running = false;
ESP_LOGI("checkers", "STOP");
}checkersAction() · source line 398
void checkersAction(bool select, bool speed, bool choose, uint32_t now_ms)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
clockNow = now_ms; - Decision
chooseTrue- Execute
choosePiece(); - Exit this function
return;
FalseContinue after this decision.
- Decision
gameState == STATE_SHOW_SCORESTrue- Decision
selectTrue- Execute
choosePiece();
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
gameState == STATE_CHOOSE_PIECETrue- Decision
selectTrue- Execute
beginGame();
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
autoModeTrue- Decision
speedTrue- Execute
cycleAISpeed(&aiSpeedIndex); - Execute
computerMoveDue = now_ms + aiSpeedDelay(aiSpeedIndex);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
select && gameState == STATE_FINISHEDTrue- Execute
choosePiece(); - Exit this function
return;
FalseContinue after this decision.
- Decision
select && gameState == STATE_PLAYING && currentPlayer == humanPieceTrue- Execute
performMove(selectedMove);
FalseContinue after this decision.
Read original function source
void checkersAction(bool select, bool speed, bool choose, uint32_t now_ms)
{
if (!running) return;
clockNow = now_ms;
if (choose) { choosePiece(); return; }
if (gameState == STATE_SHOW_SCORES) {
if (select) choosePiece();
return;
}
if (gameState == STATE_CHOOSE_PIECE) {
if (select) beginGame();
return;
}
if (autoMode) {
if (speed) {
cycleAISpeed(&aiSpeedIndex);
computerMoveDue = now_ms + aiSpeedDelay(aiSpeedIndex);
}
return;
}
if (select && gameState == STATE_FINISHED) { choosePiece(); return; }
if (select && gameState == STATE_PLAYING && currentPlayer == humanPiece) performMove(selectedMove);
}updateCheckers() · source line 422
void updateCheckers(int x_offset, int y_offset, bool valid, bool left, bool right,
uint32_t now_ms)Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
clockNow = now_ms; - Decision
gameState == STATE_SHOW_SCORES && now_ms - stateStarted >= 2000UTrue- Execute
choosePiece();
FalseContinue after this decision.
- Decision
gameState == STATE_FINISHED && autoModeTrue- Decision
now_ms - stateStarted >= aiRestartDelay()True- Execute
beginGame();
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
gameState == STATE_PLAYING && (autoMode || currentPlayer != humanPiece)True- Decision
(int32_t)(now_ms - computerMoveDue) >= 0True- Decision
legalMoveCount == 0True- Execute
finishGame(opponent(currentPlayer));
False- Execute
performMove(chooseComputerMove());
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
!validTrue- Execute
stickCentred = false;
FalseContinue after this decision.
- Execute
const int ax = x_offset < 0 ? -x_offset : x_offset; - Execute
const int ay = y_offset < 0 ? -y_offset : y_offset; - Decision
valid && ax <= 350 && ay <= 350True- Execute
stickCentred = true;
FalseContinue after this decision.
- Execute
int direction = 0; - Decision
left || rightTrue- Execute
direction = (int)right - (int)left; - Decision
direction == 0True- Execute
heldDirection = 0; - Execute
repeating = false; - Exit this function
return;
FalseContinue after this decision.
False- Decision
!validTrue- Execute
heldDirection = 0; - Exit this function
return;
FalseContinue after this decision.
- Decision
ax <= 350 && ay <= 350True- Execute
heldDirection = 0; - Execute
repeating = false; - Exit this function
return;
FalseContinue after this decision.
- Decision
!stickCentredTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
ax >= ay && ax >= 600True- Execute
direction = x_offset > 0 ? 1 : -1;
False- Decision
ay >= 600True- Execute
direction = y_offset < 0 ? 1 : -1;
FalseContinue after this decision.
- Decision
direction == 0True- Exit this function
return;
FalseContinue after this decision.
- Decision
heldDirection == direction && now_ms - lastStickMove < (repeating ? 150U : 400U)True- Exit this function
return;
FalseContinue after this decision.
- Execute
repeating = heldDirection == direction; - Execute
heldDirection = direction; - Execute
lastStickMove = now_ms; - Decision
gameState == STATE_CHOOSE_PIECETrue- Execute
int choice = autoMode ? 2 : humanPiece == BLACK_PIECE ? 0 : 1; - Execute
choice = (choice + direction + 3) % 3; - Execute
autoMode = choice == 2; - Execute
humanPiece = choice == 1 ? WHITE_PIECE : BLACK_PIECE;
False- Decision
gameState == STATE_PLAYING && legalMoveCount > 0True- Execute
selectedMove = (uint8_t)((selectedMove + direction + legalMoveCount) % legalMoveCount);
FalseContinue after this decision.
Read original function source
void updateCheckers(int x_offset, int y_offset, bool valid, bool left, bool right,
uint32_t now_ms)
{
if (!running) return;
clockNow = now_ms;
if (gameState == STATE_SHOW_SCORES && now_ms - stateStarted >= 2000U) choosePiece();
if (gameState == STATE_FINISHED && autoMode) {
if (now_ms - stateStarted >= aiRestartDelay()) beginGame();
return;
}
if (gameState == STATE_PLAYING && (autoMode || currentPlayer != humanPiece)) {
if ((int32_t)(now_ms - computerMoveDue) >= 0) {
if (legalMoveCount == 0) finishGame(opponent(currentPlayer));
else performMove(chooseComputerMove());
}
return;
}
if (!valid) stickCentred = false;
const int ax = x_offset < 0 ? -x_offset : x_offset;
const int ay = y_offset < 0 ? -y_offset : y_offset;
if (valid && ax <= 350 && ay <= 350) stickCentred = true;
int direction = 0;
if (left || right) {
direction = (int)right - (int)left;
if (direction == 0) { heldDirection = 0; repeating = false; return; }
} else {
if (!valid) { heldDirection = 0; return; }
if (ax <= 350 && ay <= 350) {
heldDirection = 0; repeating = false; return;
}
if (!stickCentred) return;
if (ax >= ay && ax >= 600) direction = x_offset > 0 ? 1 : -1;
else if (ay >= 600) direction = y_offset < 0 ? 1 : -1;
}
if (direction == 0) return;
if (heldDirection == direction && now_ms - lastStickMove < (repeating ? 150U : 400U)) return;
repeating = heldDirection == direction;
heldDirection = direction;
lastStickMove = now_ms;
if (gameState == STATE_CHOOSE_PIECE) {
int choice = autoMode ? 2 : humanPiece == BLACK_PIECE ? 0 : 1;
choice = (choice + direction + 3) % 3;
autoMode = choice == 2;
humanPiece = choice == 1 ? WHITE_PIECE : BLACK_PIECE;
} else if (gameState == STATE_PLAYING && legalMoveCount > 0) {
selectedMove = (uint8_t)((selectedMove + direction + legalMoveCount) % legalMoveCount);
}
}drawPiece() · source line 471
static void drawPiece(uint16_t *frame, uint8_t square, uint8_t piece)
Calls into this documentation:
- Execute
const int x = BOARD_X + (square % 8) * CELL_SIZE + 10; - Execute
const int y = BOARD_Y + (square / 8) * CELL_SIZE + 10; - Execute
fill_circle(frame, x, y, 8, piece == BLACK_PIECE ? 0 : 0xffff); - Execute
draw_ring(frame, x, y, 8, piece == BLACK_PIECE ? 0xffff : 0); - Decision
isKing(square)True- Execute
draw_ring(frame, x, y, 4, 0xfea0); - Execute
draw_ring(frame, x, y, 5, 0xfea0);
FalseContinue after this decision.
Read original function source
static void drawPiece(uint16_t *frame, uint8_t square, uint8_t piece)
{
const int x = BOARD_X + (square % 8) * CELL_SIZE + 10;
const int y = BOARD_Y + (square / 8) * CELL_SIZE + 10;
fill_circle(frame, x, y, 8, piece == BLACK_PIECE ? 0 : 0xffff);
draw_ring(frame, x, y, 8, piece == BLACK_PIECE ? 0xffff : 0);
if (isKing(square)) {
draw_ring(frame, x, y, 4, 0xfea0);
draw_ring(frame, x, y, 5, 0xfea0);
}
}drawCheckers() · source line 483
void drawCheckers(uint16_t *frame)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
clear(frame, 0); - Execute
char line[64]; - Decision
gameState == STATE_SHOW_SCORES || gameState == STATE_CHOOSE_PIECETrue- Execute
draw_text_centred(frame, 160, 8, "CHECKERS", 3, 0x07ff); - Decision
gameState == STATE_SHOW_SCORESTrue- Execute
snprintf(line, sizeof(line), "WINS %u LOSSES %u", (unsigned)scores.wins, (unsigned)scores.losses); - Execute
draw_text_centred(frame, 160, 55, line, 1, 0xffff); - Execute
snprintf(line, sizeof(line), "DRAWS %u STREAK %u", (unsigned)scores.draws, (unsigned)scores.streak); - Execute
draw_text_centred(frame, 160, 78, line, 1, 0xffff);
False- Execute
const char *labels[] = {"BLACK", "WHITE", "AUTO"}; - Execute
const int selected = autoMode ? 2 : humanPiece == BLACK_PIECE ? 0 : 1; - FOR loop
int i = 0; i < 3; ++iRepeat body while the loop condition permits; then continue below.
- Execute
draw_rect(frame, 8 + i * 104, 58, 96, 40, i == selected ? 0xffe0 : 0x7bef); - Execute
draw_text_centred(frame, 56 + i * 104, 72, labels[i], 2, 0xffff);
- Execute
draw_text_centred(frame, 160, 116, "D/B/STICK CHOOSE A/K START", 1, 0xffff);
- Execute
draw_text_centred(frame, 160, 139, scoreSaveFailed ? "SCORE SAVE ERROR" : game_scores_status_text(scoreStatus), 1, 0xffe0); - Execute
draw_text_centred(frame, 160, 157, "BOOT BACK", 1, 0x7bef); - Exit this function
return;
FalseContinue after this decision.
- FOR loop
uint8_t square = 0; square < 64; ++squareRepeat body while the loop condition permits; then continue below.
- Execute
const int row = square / 8, column = square % 8; - Execute
const bool selected = gameState == STATE_PLAYING && legalMoveCount > 0 && square == legalMoves[selectedMove].from; - Execute
fill_rect(frame, BOARD_X + column * 20, BOARD_Y + row * 20, 20, 20, selected ? 0xffe0 : ((row + column) & 1) ? 0x4208 : 0xad55); - Decision
board.black & squareMask(square)True- Execute
drawPiece(frame, square, BLACK_PIECE);
False- Decision
board.white & squareMask(square)True- Execute
drawPiece(frame, square, WHITE_PIECE);
FalseContinue after this decision.
- Decision
gameState == STATE_PLAYING && legalMoveCount > 0True- Execute
const Move move = legalMoves[selectedMove]; - Execute
draw_rect(frame, BOARD_X + (move.to % 8) * 20 + 2, BOARD_Y + (move.to / 8) * 20 + 2, 16, 16, 0x07e0);
FalseContinue after this decision.
- Execute
drawGameGold(frame, 236, 3, 82, true); - Execute
draw_text(frame, 170, 4, "TURN", 1, 0x07ff); - Execute
draw_text(frame, 170, 16, currentPlayer == BLACK_PIECE ? "BLACK" : "WHITE", 1, 0xffff); - Execute
snprintf(line, sizeof(line), "B %u W %u", (unsigned)countPieces(BLACK_PIECE), (unsigned)countPieces(WHITE_PIECE)); - Execute
draw_text(frame, 170, 36, line, 1, 0xffff); - Execute
draw_text(frame, 170, 53, boardMessage, 1, 0xffe0); - Decision
gameState == STATE_PLAYING && legalMoveCount > 0True- Execute
const Move move = legalMoves[selectedMove]; - Execute
snprintf(line, sizeof(line), "%c%u / %c%u", 'A' + move.from % 8, (unsigned)(move.from / 8 + 1), 'A' + move.to % 8, (unsigned)(move.to / 8 + 1)); - Execute
draw_text(frame, 170, 70, line, 1, 0xffff);
FalseContinue after this decision.
- Decision
autoModeTrue- Execute
snprintf(line, sizeof(line), "AI %s WINNER %s", aiSpeedShortText(aiSpeedIndex), goldEarningPiece == BLACK_PIECE ? "B" : "W"); - Execute
draw_text(frame, 170, 87, line, 1, 0x07ff); - Decision
gameState == STATE_FINISHEDTrue- Execute
const uint32_t elapsed = clockNow - stateStarted; - Execute
const uint32_t delay = aiRestartDelay(); - Execute
const unsigned seconds = elapsed >= delay ? 0 : (unsigned)((delay - elapsed + 999) / 1000); - Execute
snprintf(line, sizeof(line), "NEXT GAME %u", seconds); - Execute
draw_text(frame, 170, 102, line, 1, 0xffff);
FalseContinue after this decision.
- Execute
draw_text(frame, 170, 121, "E SPEED C STOP", 1, 0xffff);
False- Execute
draw_text(frame, 170, 87, "WIN 35 GOLD", 1, 0xfea0); - Execute
draw_text(frame, 170, 104, "D/B/STICK NEXT/PREV", 1, 0xffff); - Execute
draw_text(frame, 170, 121, gameState == STATE_FINISHED ? "A/K NEW GAME" : "A/K PLAY MOVE", 1, 0xffff);
- Execute
const char *status = goldSaveFailed ? "GOLD SAVE ERROR" : scoreSaveFailed ? "SCORE SAVE ERROR" : scoreStatus != SCORES_OK && scoreStatus != SCORES_NOT_FOUND ? game_scores_status_text(scoreStatus) : !aiTimingStorageHealthy() ? "AI TIMING ERROR" : "C CHOOSE BOOT BACK"; - Execute
draw_text(frame, 170, 151, status, 1, 0xffe0);
Read original function source
void drawCheckers(uint16_t *frame)
{
if (!running) return;
clear(frame, 0);
char line[64];
if (gameState == STATE_SHOW_SCORES || gameState == STATE_CHOOSE_PIECE) {
draw_text_centred(frame, 160, 8, "CHECKERS", 3, 0x07ff);
if (gameState == STATE_SHOW_SCORES) {
snprintf(line, sizeof(line), "WINS %u LOSSES %u", (unsigned)scores.wins, (unsigned)scores.losses);
draw_text_centred(frame, 160, 55, line, 1, 0xffff);
snprintf(line, sizeof(line), "DRAWS %u STREAK %u", (unsigned)scores.draws, (unsigned)scores.streak);
draw_text_centred(frame, 160, 78, line, 1, 0xffff);
} else {
const char *labels[] = {"BLACK", "WHITE", "AUTO"};
const int selected = autoMode ? 2 : humanPiece == BLACK_PIECE ? 0 : 1;
for (int i = 0; i < 3; ++i) {
draw_rect(frame, 8 + i * 104, 58, 96, 40, i == selected ? 0xffe0 : 0x7bef);
draw_text_centred(frame, 56 + i * 104, 72, labels[i], 2, 0xffff);
}
draw_text_centred(frame, 160, 116, "D/B/STICK CHOOSE A/K START", 1, 0xffff);
}
draw_text_centred(frame, 160, 139, scoreSaveFailed ? "SCORE SAVE ERROR" : game_scores_status_text(scoreStatus), 1, 0xffe0);
draw_text_centred(frame, 160, 157, "BOOT BACK", 1, 0x7bef);
return;
}
for (uint8_t square = 0; square < 64; ++square) {
const int row = square / 8, column = square % 8;
const bool selected = gameState == STATE_PLAYING && legalMoveCount > 0 && square == legalMoves[selectedMove].from;
fill_rect(frame, BOARD_X + column * 20, BOARD_Y + row * 20, 20, 20,
selected ? 0xffe0 : ((row + column) & 1) ? 0x4208 : 0xad55);
if (board.black & squareMask(square)) drawPiece(frame, square, BLACK_PIECE);
else if (board.white & squareMask(square)) drawPiece(frame, square, WHITE_PIECE);
}
if (gameState == STATE_PLAYING && legalMoveCount > 0) {
const Move move = legalMoves[selectedMove];
draw_rect(frame, BOARD_X + (move.to % 8) * 20 + 2, BOARD_Y + (move.to / 8) * 20 + 2, 16, 16, 0x07e0);
}
drawGameGold(frame, 236, 3, 82, true);
draw_text(frame, 170, 4, "TURN", 1, 0x07ff);
draw_text(frame, 170, 16, currentPlayer == BLACK_PIECE ? "BLACK" : "WHITE", 1, 0xffff);
snprintf(line, sizeof(line), "B %u W %u", (unsigned)countPieces(BLACK_PIECE), (unsigned)countPieces(WHITE_PIECE));
draw_text(frame, 170, 36, line, 1, 0xffff);
draw_text(frame, 170, 53, boardMessage, 1, 0xffe0);
if (gameState == STATE_PLAYING && legalMoveCount > 0) {
const Move move = legalMoves[selectedMove];
snprintf(line, sizeof(line), "%c%u / %c%u", 'A' + move.from % 8, (unsigned)(move.from / 8 + 1),
'A' + move.to % 8, (unsigned)(move.to / 8 + 1));
draw_text(frame, 170, 70, line, 1, 0xffff);
}
if (autoMode) {
snprintf(line, sizeof(line), "AI %s WINNER %s", aiSpeedShortText(aiSpeedIndex), goldEarningPiece == BLACK_PIECE ? "B" : "W");
draw_text(frame, 170, 87, line, 1, 0x07ff);
if (gameState == STATE_FINISHED) {
const uint32_t elapsed = clockNow - stateStarted;
const uint32_t delay = aiRestartDelay();
const unsigned seconds = elapsed >= delay ? 0 : (unsigned)((delay - elapsed + 999) / 1000);
snprintf(line, sizeof(line), "NEXT GAME %u", seconds);
draw_text(frame, 170, 102, line, 1, 0xffff);
}
draw_text(frame, 170, 121, "E SPEED C STOP", 1, 0xffff);
} else {
draw_text(frame, 170, 87, "WIN 35 GOLD", 1, 0xfea0);
draw_text(frame, 170, 104, "D/B/STICK NEXT/PREV", 1, 0xffff);
draw_text(frame, 170, 121, gameState == STATE_FINISHED ? "A/K NEW GAME" : "A/K PLAY MOVE", 1, 0xffff);
}
const char *status = goldSaveFailed ? "GOLD SAVE ERROR" : scoreSaveFailed ? "SCORE SAVE ERROR" :
scoreStatus != SCORES_OK && scoreStatus != SCORES_NOT_FOUND ? game_scores_status_text(scoreStatus) :
!aiTimingStorageHealthy() ? "AI TIMING ERROR" : "C CHOOSE BOOT BACK";
draw_text(frame, 170, 151, status, 1, 0xffe0);
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "checkers.h"
#include "audio.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include "game_scores.h"
#include "gold.h"
#include "ai_speed.h"
#include "esp_random.h"
#include "esp_log.h"
#include <stdio.h>
// Rules ported from the other LilyGO project's checkers.cpp.
#define MAX_MOVES 64
#define NO_SQUARE 255
#define NO_PROGRESS_DRAW_LIMIT 80
#define CHECKERS_WIN_GOLD 35U
#define BOARD_X 4
#define BOARD_Y 5
#define CELL_SIZE 20
enum { EMPTY, BLACK_PIECE, WHITE_PIECE };
typedef enum { STATE_SHOW_SCORES, STATE_CHOOSE_PIECE, STATE_PLAYING, STATE_FINISHED } GameState;
typedef struct { uint64_t black, white, kings; } Board;
typedef struct { uint8_t from, to, captured; bool capture; } Move;
static Board board;
static game_scores_t scores;
static game_score_result_t scoreStatus;
static Move legalMoves[MAX_MOVES];
static GameState gameState;
static uint8_t legalMoveCount, selectedMove, forcedFrom;
static uint8_t humanPiece, currentPlayer, goldEarningPiece, noProgressMoves, aiSpeedIndex;
static uint32_t stateStarted, computerMoveDue, clockNow, lastStickMove;
static bool running, autoMode, stickCentred, repeating, scoreSaveFailed, goldSaveFailed;
static int heldDirection;
static const char *boardMessage;
static uint64_t squareMask(uint8_t square)
{
return 1ULL << square;
}
static uint8_t opponent(uint8_t piece)
{
return piece == BLACK_PIECE
? WHITE_PIECE
: BLACK_PIECE;
}
static bool insideBoard(int8_t row,int8_t column)
{
return row >= 0 && row < 8 && column >= 0 && column < 8;
}
static uint64_t pieceMask(uint8_t piece)
{
return piece == BLACK_PIECE
? board.black
: board.white;
}
static bool occupied(uint8_t square)
{
return (board.black | board.white) & squareMask(square);
}
static bool isKing(uint8_t square)
{
return board.kings & squareMask(square);
}
static uint8_t countBits(uint64_t value)
{
uint8_t count = 0;
while (value)
{
count += value & 1ULL;
value >>= 1;
}
return count;
}
static uint8_t countPieces(uint8_t piece)
{
return countBits(pieceMask(piece));
}
static bool directionAllowed(uint8_t piece,bool king,int8_t rowDirection)
{
if (king)
return true;
return piece == BLACK_PIECE
? rowDirection > 0
: rowDirection < 0;
}
static uint8_t generateMoves(
uint8_t piece,
Move *moves,
uint8_t onlyFrom)
{
uint8_t count = 0;
bool captureAvailable = false;
const uint64_t ownPieces = pieceMask(piece);
const uint64_t otherPieces = pieceMask(opponent(piece));
for (uint8_t from = 0; from < 64; from++)
{
if (!(ownPieces & squareMask(from)) ||
(onlyFrom != NO_SQUARE && from != onlyFrom))
continue;
const int8_t row = from / 8;
const int8_t column = from % 8;
const bool king = isKing(from);
for (int8_t rowDirection = -1;
rowDirection <= 1;
rowDirection += 2)
{
if (!directionAllowed(piece,king,rowDirection))
continue;
for (int8_t columnDirection = -1;
columnDirection <= 1;
columnDirection += 2)
{
const int8_t middleRow = row + rowDirection;
const int8_t middleColumn = column + columnDirection;
const int8_t toRow = row + rowDirection * 2;
const int8_t toColumn = column + columnDirection * 2;
if (!insideBoard(middleRow,middleColumn) ||
!insideBoard(toRow,toColumn))
continue;
const uint8_t middle = middleRow * 8 + middleColumn;
const uint8_t to = toRow * 8 + toColumn;
if ((otherPieces & squareMask(middle)) &&
!occupied(to))
{
captureAvailable = true;
if (count < MAX_MOVES)
moves[count++] = (Move){from,to,middle,true};
}
}
}
}
if (captureAvailable || onlyFrom != NO_SQUARE)
return count;
for (uint8_t from = 0; from < 64; from++)
{
if (!(ownPieces & squareMask(from)))
continue;
const int8_t row = from / 8;
const int8_t column = from % 8;
const bool king = isKing(from);
for (int8_t rowDirection = -1;
rowDirection <= 1;
rowDirection += 2)
{
if (!directionAllowed(piece,king,rowDirection))
continue;
for (int8_t columnDirection = -1;
columnDirection <= 1;
columnDirection += 2)
{
const int8_t toRow = row + rowDirection;
const int8_t toColumn = column + columnDirection;
if (!insideBoard(toRow,toColumn))
continue;
const uint8_t to = toRow * 8 + toColumn;
if (!occupied(to) && count < MAX_MOVES)
moves[count++] = (Move){from,to,NO_SQUARE,false};
}
}
}
return count;
}
static void resetBoard(void)
{
board = (Board){0};
for (uint8_t row = 0; row < 3; row++)
{
for (uint8_t column = 0; column < 8; column++)
{
if ((row + column) % 2 == 1)
board.black |= squareMask(row * 8 + column);
}
}
for (uint8_t row = 5; row < 8; row++)
{
for (uint8_t column = 0; column < 8; column++)
{
if ((row + column) % 2 == 1)
board.white |= squareMask(row * 8 + column);
}
}
currentPlayer = BLACK_PIECE;
forcedFrom = NO_SQUARE;
selectedMove = 0;
noProgressMoves = 0;
}
static bool applyMove(const Move move)
{
const uint64_t fromMask = squareMask(move.from);
const uint64_t toMask = squareMask(move.to);
const bool king = board.kings & fromMask;
if (currentPlayer == BLACK_PIECE)
{
board.black &= ~fromMask;
board.black |= toMask;
}
else
{
board.white &= ~fromMask;
board.white |= toMask;
}
board.kings &= ~fromMask;
if (king)
board.kings |= toMask;
if (move.capture)
{
const uint64_t capturedMask = squareMask(move.captured);
board.black &= ~capturedMask;
board.white &= ~capturedMask;
board.kings &= ~capturedMask;
}
const uint8_t destinationRow = move.to / 8;
const bool promoted = !king &&
((currentPlayer == BLACK_PIECE && destinationRow == 7) ||
(currentPlayer == WHITE_PIECE && destinationRow == 0));
if (promoted)
board.kings |= toMask;
if (move.capture || promoted)
noProgressMoves = 0;
else if (noProgressMoves < UINT8_MAX)
noProgressMoves++;
return promoted;
}
static void refreshLegalMoves(void)
{
legalMoveCount = generateMoves(currentPlayer, legalMoves, forcedFrom);
selectedMove = 0;
stickCentred = false;
heldDirection = 0;
}
static void incrementScore(uint32_t *value)
{
if (*value < UINT32_MAX) ++*value;
}
static void finishGame(uint8_t winner)
{
gameState = STATE_FINISHED;
stateStarted = clockNow;
boardMessage = winner == EMPTY ? "DRAW" : winner == BLACK_PIECE ? "BLACK WINS" : "WHITE WINS";
if (autoMode) {
if (winner == goldEarningPiece && !addAIGold(CHECKERS_WIN_GOLD)) goldSaveFailed = true;
} else {
if (winner == humanPiece) {
incrementScore(&scores.wins);
incrementScore(&scores.streak);
if (!addUserGoldWon(CHECKERS_WIN_GOLD)) goldSaveFailed = true;
boardMessage = "YOU WIN";
} else {
scores.streak = 0;
if (winner == EMPTY) incrementScore(&scores.draws);
else { incrementScore(&scores.losses); boardMessage = "CPU WINS"; }
}
if (scoreStatus == SCORES_OK || scoreStatus == SCORES_NOT_FOUND) {
scoreSaveFailed = !game_scores_save(GAME_CHECKERS, &scores);
if (!scoreSaveFailed) scoreStatus = SCORES_OK;
} else scoreSaveFailed = true;
}
ESP_LOGI("checkers", "%s; score save %s; Gold %s", boardMessage,
scoreSaveFailed ? "ERROR" : autoMode ? "NOT REQUIRED" : "OK",
goldSaveFailed ? "ERROR" : "OK");
}
static void finishTurn(void)
{
if (noProgressMoves >= NO_PROGRESS_DRAW_LIMIT) { finishGame(EMPTY); return; }
const uint8_t nextPlayer = opponent(currentPlayer);
if (countPieces(nextPlayer) == 0) { finishGame(currentPlayer); return; }
currentPlayer = nextPlayer;
forcedFrom = NO_SQUARE;
refreshLegalMoves();
if (legalMoveCount == 0) { finishGame(opponent(currentPlayer)); return; }
boardMessage = autoMode || currentPlayer != humanPiece ? "CPU TURN" : "YOUR TURN";
computerMoveDue = clockNow + aiSpeedDelay(aiSpeedIndex);
}
static void performMove(uint8_t index)
{
if (index >= legalMoveCount) return;
const Move move = legalMoves[index];
const bool promoted = applyMove(move);
audioPlay(move.capture ? AUDIO_SFX_BOARD_CAPTURE : AUDIO_SFX_BOARD_PLACE);
if (move.capture && !promoted) {
forcedFrom = move.to;
refreshLegalMoves();
if (legalMoveCount > 0) {
boardMessage = "JUMP AGAIN";
computerMoveDue = clockNow + aiSpeedDelay(aiSpeedIndex);
return;
}
}
finishTurn();
}
static uint8_t chooseComputerMove(void)
{
uint8_t best = 0;
int bestScore = -10000;
for (uint8_t i = 0; i < legalMoveCount; ++i) {
const Move move = legalMoves[i];
const bool king = isKing(move.from);
const uint8_t row = move.to / 8, column = move.to % 8;
int score = (int)(esp_random() % 12);
if (move.capture) score += 100;
if (!king && ((currentPlayer == BLACK_PIECE && row == 7) ||
(currentPlayer == WHITE_PIECE && row == 0))) score += 35;
if (king) score += 8;
if (column >= 2 && column <= 5) score += 5;
if (column == 0 || column == 7) score += 3;
if (score > bestScore) { bestScore = score; best = i; }
}
return best;
}
static void choosePiece(void)
{
gameState = STATE_CHOOSE_PIECE;
autoMode = false;
humanPiece = BLACK_PIECE;
stickCentred = false;
heldDirection = 0;
}
static void beginGame(void)
{
resetBoard();
gameState = STATE_PLAYING;
if (autoMode) goldEarningPiece = esp_random() % 2 == 0 ? BLACK_PIECE : WHITE_PIECE;
refreshLegalMoves();
boardMessage = autoMode || humanPiece != BLACK_PIECE ? "CPU TURN" : "YOUR TURN";
computerMoveDue = clockNow + aiSpeedDelay(aiSpeedIndex);
}
void startCheckers(uint32_t now_ms)
{
clockNow = stateStarted = now_ms;
running = true;
scoreSaveFailed = goldSaveFailed = false;
scores = (game_scores_t){0};
scoreStatus = game_scores_load(GAME_CHECKERS, &scores);
aiSpeedIndex = AI_SPEED_DEFAULT_INDEX;
resetBoard();
choosePiece();
gameState = STATE_SHOW_SCORES;
ESP_LOGI("checkers", "START: %s", game_scores_status_text(scoreStatus));
}
void stopCheckers(void)
{
running = false;
ESP_LOGI("checkers", "STOP");
}
void checkersAction(bool select, bool speed, bool choose, uint32_t now_ms)
{
if (!running) return;
clockNow = now_ms;
if (choose) { choosePiece(); return; }
if (gameState == STATE_SHOW_SCORES) {
if (select) choosePiece();
return;
}
if (gameState == STATE_CHOOSE_PIECE) {
if (select) beginGame();
return;
}
if (autoMode) {
if (speed) {
cycleAISpeed(&aiSpeedIndex);
computerMoveDue = now_ms + aiSpeedDelay(aiSpeedIndex);
}
return;
}
if (select && gameState == STATE_FINISHED) { choosePiece(); return; }
if (select && gameState == STATE_PLAYING && currentPlayer == humanPiece) performMove(selectedMove);
}
void updateCheckers(int x_offset, int y_offset, bool valid, bool left, bool right,
uint32_t now_ms)
{
if (!running) return;
clockNow = now_ms;
if (gameState == STATE_SHOW_SCORES && now_ms - stateStarted >= 2000U) choosePiece();
if (gameState == STATE_FINISHED && autoMode) {
if (now_ms - stateStarted >= aiRestartDelay()) beginGame();
return;
}
if (gameState == STATE_PLAYING && (autoMode || currentPlayer != humanPiece)) {
if ((int32_t)(now_ms - computerMoveDue) >= 0) {
if (legalMoveCount == 0) finishGame(opponent(currentPlayer));
else performMove(chooseComputerMove());
}
return;
}
if (!valid) stickCentred = false;
const int ax = x_offset < 0 ? -x_offset : x_offset;
const int ay = y_offset < 0 ? -y_offset : y_offset;
if (valid && ax <= 350 && ay <= 350) stickCentred = true;
int direction = 0;
if (left || right) {
direction = (int)right - (int)left;
if (direction == 0) { heldDirection = 0; repeating = false; return; }
} else {
if (!valid) { heldDirection = 0; return; }
if (ax <= 350 && ay <= 350) {
heldDirection = 0; repeating = false; return;
}
if (!stickCentred) return;
if (ax >= ay && ax >= 600) direction = x_offset > 0 ? 1 : -1;
else if (ay >= 600) direction = y_offset < 0 ? 1 : -1;
}
if (direction == 0) return;
if (heldDirection == direction && now_ms - lastStickMove < (repeating ? 150U : 400U)) return;
repeating = heldDirection == direction;
heldDirection = direction;
lastStickMove = now_ms;
if (gameState == STATE_CHOOSE_PIECE) {
int choice = autoMode ? 2 : humanPiece == BLACK_PIECE ? 0 : 1;
choice = (choice + direction + 3) % 3;
autoMode = choice == 2;
humanPiece = choice == 1 ? WHITE_PIECE : BLACK_PIECE;
} else if (gameState == STATE_PLAYING && legalMoveCount > 0) {
selectedMove = (uint8_t)((selectedMove + direction + legalMoveCount) % legalMoveCount);
}
}
static void drawPiece(uint16_t *frame, uint8_t square, uint8_t piece)
{
const int x = BOARD_X + (square % 8) * CELL_SIZE + 10;
const int y = BOARD_Y + (square / 8) * CELL_SIZE + 10;
fill_circle(frame, x, y, 8, piece == BLACK_PIECE ? 0 : 0xffff);
draw_ring(frame, x, y, 8, piece == BLACK_PIECE ? 0xffff : 0);
if (isKing(square)) {
draw_ring(frame, x, y, 4, 0xfea0);
draw_ring(frame, x, y, 5, 0xfea0);
}
}
void drawCheckers(uint16_t *frame)
{
if (!running) return;
clear(frame, 0);
char line[64];
if (gameState == STATE_SHOW_SCORES || gameState == STATE_CHOOSE_PIECE) {
drawPet2001TextCentredTransparent(frame, 160, 8, "CHECKERS", 0x07ff, 3);
if (gameState == STATE_SHOW_SCORES) {
snprintf(line, sizeof(line), "WINS %u LOSSES %u", (unsigned)scores.wins, (unsigned)scores.losses);
drawPet2001TextCentredTransparent(frame, 160, 55, line, 0xffff, 1);
snprintf(line, sizeof(line), "DRAWS %u STREAK %u", (unsigned)scores.draws, (unsigned)scores.streak);
drawPet2001TextCentredTransparent(frame, 160, 78, line, 0xffff, 1);
} else {
const char *labels[] = {"BLACK", "WHITE", "AUTO"};
const int selected = autoMode ? 2 : humanPiece == BLACK_PIECE ? 0 : 1;
for (int i = 0; i < 3; ++i) {
draw_rect(frame, 8 + i * 104, 58, 96, 40, i == selected ? 0xffe0 : 0x7bef);
drawPet2001TextCentredTransparent(frame, 56 + i * 104, 72, labels[i], 0xffff, 2);
}
drawPet2001TextCentredTransparent(frame, 160, 116, "D/B/STICK CHOOSE A/K START", 0xffff, 1);
}
drawPet2001TextCentredTransparent(frame, 160, 139,
scoreSaveFailed ? "SCORE SAVE ERROR" : game_scores_status_text(scoreStatus),
0xffe0, 1);
drawPet2001TextCentredTransparent(frame, 160, 157, "BOOT BACK", 0x7bef, 1);
return;
}
for (uint8_t square = 0; square < 64; ++square) {
const int row = square / 8, column = square % 8;
const bool selected = gameState == STATE_PLAYING && legalMoveCount > 0 && square == legalMoves[selectedMove].from;
fill_rect(frame, BOARD_X + column * 20, BOARD_Y + row * 20, 20, 20,
selected ? 0xffe0 : ((row + column) & 1) ? 0x4208 : 0xad55);
if (board.black & squareMask(square)) drawPiece(frame, square, BLACK_PIECE);
else if (board.white & squareMask(square)) drawPiece(frame, square, WHITE_PIECE);
}
if (gameState == STATE_PLAYING && legalMoveCount > 0) {
const Move move = legalMoves[selectedMove];
draw_rect(frame, BOARD_X + (move.to % 8) * 20 + 2, BOARD_Y + (move.to / 8) * 20 + 2, 16, 16, 0x07e0);
}
drawGameGold(frame, 236, 3, 82, true);
drawPet2001TextTransparent(frame, 168, 4, "TURN", 0x07ff, 1);
drawPet2001TextTransparent(frame, 168, 16, currentPlayer == BLACK_PIECE ? "BLACK" : "WHITE", 0xffff, 1);
snprintf(line, sizeof(line), "B %u W %u", (unsigned)countPieces(BLACK_PIECE), (unsigned)countPieces(WHITE_PIECE));
drawPet2001TextTransparent(frame, 168, 36, line, 0xffff, 1);
drawPet2001TextTransparent(frame, 168, 53, boardMessage, 0xffe0, 1);
if (gameState == STATE_PLAYING && legalMoveCount > 0) {
const Move move = legalMoves[selectedMove];
snprintf(line, sizeof(line), "%c%u / %c%u", 'A' + move.from % 8, (unsigned)(move.from / 8 + 1),
'A' + move.to % 8, (unsigned)(move.to / 8 + 1));
drawPet2001TextTransparent(frame, 168, 70, line, 0xffff, 1);
}
if (autoMode) {
snprintf(line, sizeof(line), "AI %s WINNER %s", aiSpeedShortText(aiSpeedIndex), goldEarningPiece == BLACK_PIECE ? "B" : "W");
drawPet2001TextTransparent(frame, 168, 87, line, 0x07ff, 1);
if (gameState == STATE_FINISHED) {
const uint32_t elapsed = clockNow - stateStarted;
const uint32_t delay = aiRestartDelay();
const unsigned seconds = elapsed >= delay ? 0 : (unsigned)((delay - elapsed + 999) / 1000);
snprintf(line, sizeof(line), "NEXT GAME %u", seconds);
drawPet2001TextTransparent(frame, 168, 102, line, 0xffff, 1);
}
drawPet2001TextTransparent(frame, 168, 121, "E SPEED C STOP", 0xffff, 1);
} else {
drawPet2001TextTransparent(frame, 168, 87, "WIN 35 GOLD", 0xfea0, 1);
drawPet2001TextTransparent(frame, 168, 104, "D/B/STICK NEXT/PREV", 0xffff, 1);
drawPet2001TextTransparent(frame, 168, 121,
gameState == STATE_FINISHED ? "A/K NEW GAME" : "A/K PLAY MOVE", 0xffff, 1);
}
const char *status = goldSaveFailed ? "GOLD SAVE ERROR" : scoreSaveFailed ? "SCORE SAVE ERROR" :
scoreStatus != SCORES_OK && scoreStatus != SCORES_NOT_FOUND ? game_scores_status_text(scoreStatus) :
!aiTimingStorageHealthy() ? "AI TIMING ERROR" : "C CHOOSE BOOT BACK";
drawPet2001TextTransparent(frame, 168, 151, status, 0xffe0, 1);
}