Othello
Source: src/othello.c · 475 lines · 22 functions.
Open the existing readable game overview →
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
opponent() · source line 54
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 61
static bool insideBoard(int row, int column)
- Exit this function
return row >= 0 && row < 8 && column >= 0 && column < 8;
Read original function source
static bool insideBoard(int row, int column)
{
return row >= 0 &&
row < 8 &&
column >= 0 &&
column < 8;
}countPieces() · source line 69
static uint8_t countPieces(uint8_t piece)
- Execute
uint8_t total = 0; - FOR loop
uint8_t i = 0; i < 64; i++Repeat body while the loop condition permits; then continue below.
- Decision
board[i] == pieceTrue- Execute
total++;
FalseContinue after this decision.
- Exit this function
return total;
Read original function source
static uint8_t countPieces(uint8_t piece)
{
uint8_t total = 0;
for (uint8_t i = 0; i < 64; i++)
{
if (board[i] == piece)
total++;
}
return total;
}countFlips() · source line 82
static uint8_t countFlips(
uint8_t cell,
uint8_t piece,
int8_t rowDirection,
int8_t columnDirection
)Calls into this documentation:
- Decision
board[cell] != EMPTYTrue- Exit this function
return 0;
FalseContinue after this decision.
- Execute
const int startRow = cell / 8; - Execute
const int startColumn = cell % 8; - Execute
int row = startRow + rowDirection; - Execute
int column = startColumn + columnDirection; - Execute
uint8_t flips = 0; - Execute
const uint8_t otherPiece = opponent(piece); - WHILE loop
insideBoard(row, column) && board[row * 8 + column] == otherPieceRepeat body while the loop condition permits; then continue below.
- Execute
flips++; - Execute
row += rowDirection; - Execute
column += columnDirection;
- Decision
flips == 0 || !insideBoard(row, column)True- Exit this function
return 0;
FalseContinue after this decision.
- Decision
board[row * 8 + column] != pieceTrue- Exit this function
return 0;
FalseContinue after this decision.
- Exit this function
return flips;
Read original function source
static uint8_t countFlips(
uint8_t cell,
uint8_t piece,
int8_t rowDirection,
int8_t columnDirection
)
{
if (board[cell] != EMPTY)
return 0;
const int startRow = cell / 8;
const int startColumn = cell % 8;
int row = startRow + rowDirection;
int column = startColumn + columnDirection;
uint8_t flips = 0;
const uint8_t otherPiece = opponent(piece);
while (insideBoard(row, column) &&
board[row * 8 + column] == otherPiece)
{
flips++;
row += rowDirection;
column += columnDirection;
}
if (flips == 0 || !insideBoard(row, column))
return 0;
if (board[row * 8 + column] != piece)
return 0;
return flips;
}legalMove() · source line 118
static bool legalMove(uint8_t cell, uint8_t piece)
Calls into this documentation:
- Decision
cell >= 64 || board[cell] != EMPTYTrue- Exit this function
return false;
FalseContinue after this decision.
- FOR loop
uint8_t d = 0; d < 8; ++dRepeat body while the loop condition permits; then continue below.
- Decision
countFlips( cell, piece, DIRECTIONS[d][0], DIRECTIONS[d][1] ) > 0True- Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool legalMove(uint8_t cell, uint8_t piece)
{
if (cell >= 64 || board[cell] != EMPTY)
return false;
for (uint8_t d = 0; d < 8; ++d)
{
if (countFlips(
cell,
piece,
DIRECTIONS[d][0],
DIRECTIONS[d][1]
) > 0)
{
return true;
}
}
return false;
}hasLegalMove() · source line 139
static bool hasLegalMove(uint8_t piece)
Calls into this documentation:
- FOR loop
uint8_t i = 0; i < 64; i++Repeat body while the loop condition permits; then continue below.
- Decision
legalMove(i, piece)True- Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool hasLegalMove(uint8_t piece)
{
for (uint8_t i = 0; i < 64; i++)
{
if (legalMove(i, piece))
return true;
}
return false;
}makeMove() · source line 150
static void makeMove(uint8_t cell, uint8_t piece)
Calls into this documentation:
- Execute
const int startRow = cell / 8; - Execute
const int startColumn = cell % 8; - FOR loop
uint8_t d = 0; d < 8; ++dRepeat body while the loop condition permits; then continue below.
- Execute
const uint8_t flipCount = countFlips( cell, piece, DIRECTIONS[d][0], DIRECTIONS[d][1] ); - Execute
int row = startRow + DIRECTIONS[d][0]; - Execute
int column = startColumn + DIRECTIONS[d][1]; - FOR loop
uint8_t i = 0; i < flipCount; i++Repeat body while the loop condition permits; then continue below.
- Execute
board[row * 8 + column] = piece; - Execute
row += DIRECTIONS[d][0]; - Execute
column += DIRECTIONS[d][1];
- Execute
board[cell] = piece;
Read original function source
static void makeMove(uint8_t cell, uint8_t piece)
{
const int startRow = cell / 8;
const int startColumn = cell % 8;
for (uint8_t d = 0; d < 8; ++d)
{
const uint8_t flipCount = countFlips(
cell,
piece,
DIRECTIONS[d][0],
DIRECTIONS[d][1]
);
int row = startRow + DIRECTIONS[d][0];
int column = startColumn + DIRECTIONS[d][1];
for (uint8_t i = 0; i < flipCount; i++)
{
board[row * 8 + column] = piece;
row += DIRECTIONS[d][0];
column += DIRECTIONS[d][1];
}
}
board[cell] = piece;
}resetBoard() · source line 178
static void resetBoard(void)
- FOR loop
uint8_t i = 0; i < 64; i++Repeat body while the loop condition permits; then continue below.
- Execute
board[i] = EMPTY;
- Execute
board[3 * 8 + 3] = WHITE_PIECE; - Execute
board[3 * 8 + 4] = BLACK_PIECE; - Execute
board[4 * 8 + 3] = BLACK_PIECE; - Execute
board[4 * 8 + 4] = WHITE_PIECE; - Execute
currentPlayer = BLACK_PIECE; - Execute
selectedMove = 0;
Read original function source
static void resetBoard(void)
{
for (uint8_t i = 0; i < 64; i++)
board[i] = EMPTY;
board[3 * 8 + 3] = WHITE_PIECE;
board[3 * 8 + 4] = BLACK_PIECE;
board[4 * 8 + 3] = BLACK_PIECE;
board[4 * 8 + 4] = WHITE_PIECE;
currentPlayer = BLACK_PIECE;
selectedMove = 0;
}refreshLegalMoves() · source line 192
static void refreshLegalMoves(void)
Calls into this documentation:
- Execute
legalMoveCount = 0; - FOR loop
uint8_t cell = 0; cell < 64; ++cellRepeat body while the loop condition permits; then continue below.
- Decision
legalMove(cell, currentPlayer)True- Execute
legalMoves[legalMoveCount++] = cell;
FalseContinue after this decision.
- Execute
selectedMove = 0; - Execute
stickCentred = false; - Execute
heldDirection = 0;
Read original function source
static void refreshLegalMoves(void)
{
legalMoveCount = 0;
for (uint8_t cell = 0; cell < 64; ++cell) {
if (legalMove(cell, currentPlayer)) legalMoves[legalMoveCount++] = cell;
}
selectedMove = 0;
stickCentred = false;
heldDirection = 0;
}incrementScore() · source line 203
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 208
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(OTHELLO_WIN_GOLD)True- Execute
goldSaveFailed = true;
FalseContinue after this decision.
False- Decision
winner == humanPieceTrue- Execute
incrementScore(&scores.wins); - Execute
incrementScore(&scores.streak); - Decision
!addUserGoldWon(OTHELLO_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_OTHELLO, &scores); - Decision
!scoreSaveFailedTrue- Execute
scoreStatus = SCORES_OK;
FalseContinue after this decision.
False- Execute
scoreSaveFailed = true;
- Execute
ESP_LOGI("othello", "%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(OTHELLO_WIN_GOLD)) goldSaveFailed = true;
} else {
if (winner == humanPiece) {
incrementScore(&scores.wins);
incrementScore(&scores.streak);
if (!addUserGoldWon(OTHELLO_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_OTHELLO, &scores);
if (!scoreSaveFailed) scoreStatus = SCORES_OK;
} else scoreSaveFailed = true;
}
ESP_LOGI("othello", "%s; score save %s; Gold %s", boardMessage,
scoreSaveFailed ? "ERROR" : autoMode ? "NOT REQUIRED" : "OK",
goldSaveFailed ? "ERROR" : "OK");
}finishTurn() · source line 236
static void finishTurn(void)
Calls into this documentation:
- Execute
const uint8_t nextPlayer = opponent(currentPlayer); - Execute
const bool passed = !hasLegalMove(nextPlayer); - Decision
!passedTrue- Execute
currentPlayer = nextPlayer;
False- Decision
!hasLegalMove(currentPlayer)True- Execute
const uint8_t black = countPieces(BLACK_PIECE); - Execute
const uint8_t white = countPieces(WHITE_PIECE); - Execute
finishGame(black > white ? BLACK_PIECE : white > black ? WHITE_PIECE : EMPTY); - Exit this function
return;
FalseContinue after this decision.
- Execute
refreshLegalMoves(); - Execute
boardMessage = passed ? (autoMode ? "TURN PASSED" : currentPlayer == humanPiece ? "CPU PASS" : "YOU PASS") : autoMode || currentPlayer != humanPiece ? "CPU TURN" : "YOUR TURN"; - Execute
computerMoveDue = clockNow + (autoMode ? aiSpeedDelay(aiSpeedIndex) : 800U);
Read original function source
static void finishTurn(void)
{
const uint8_t nextPlayer = opponent(currentPlayer);
const bool passed = !hasLegalMove(nextPlayer);
if (!passed) currentPlayer = nextPlayer;
else if (!hasLegalMove(currentPlayer)) {
const uint8_t black = countPieces(BLACK_PIECE);
const uint8_t white = countPieces(WHITE_PIECE);
finishGame(black > white ? BLACK_PIECE : white > black ? WHITE_PIECE : EMPTY);
return;
}
refreshLegalMoves();
boardMessage = passed ? (autoMode ? "TURN PASSED" : currentPlayer == humanPiece ? "CPU PASS" : "YOU PASS") :
autoMode || currentPlayer != humanPiece ? "CPU TURN" : "YOUR TURN";
computerMoveDue = clockNow + (autoMode ? aiSpeedDelay(aiSpeedIndex) : 800U);
}performMove() · source line 253
static void performMove(uint8_t index)
Calls into this documentation:
- Decision
index >= legalMoveCount || !legalMove(legalMoves[index], currentPlayer)True- Exit this function
return;
FalseContinue after this decision.
- Execute
makeMove(legalMoves[index], currentPlayer); - Execute
audioPlay(AUDIO_SFX_BOARD_CAPTURE); - Execute
finishTurn();
Read original function source
static void performMove(uint8_t index)
{
if (index >= legalMoveCount || !legalMove(legalMoves[index], currentPlayer)) return;
makeMove(legalMoves[index], currentPlayer);
audioPlay(AUDIO_SFX_BOARD_CAPTURE);
finishTurn();
}chooseComputerMove() · source line 261
static uint8_t chooseComputerMove(void)
Calls into this documentation:
- Execute
int moveScores[64]; - Execute
int bestScore = -10000; - FOR loop
uint8_t i = 0; i < legalMoveCount; ++iRepeat body while the loop condition permits; then continue below.
- Execute
const uint8_t cell = legalMoves[i]; - Execute
int flips = 0; - FOR loop
uint8_t d = 0; d < 8; ++dRepeat body while the loop condition permits; then continue below.
- Execute
flips += countFlips(cell, currentPlayer, DIRECTIONS[d][0], DIRECTIONS[d][1]);
- Execute
moveScores[i] = POSITION_WEIGHTS[cell] + flips * 3; - Decision
moveScores[i] > bestScoreTrue- Execute
bestScore = moveScores[i];
FalseContinue after this decision.
- Execute
uint8_t candidates[64], count = 0; - FOR loop
uint8_t i = 0; i < legalMoveCount; ++iRepeat body while the loop condition permits; then continue below.
- Decision
moveScores[i] >= bestScore - 8True- Execute
candidates[count++] = i;
FalseContinue after this decision.
- Exit this function
return count > 0 ? candidates[esp_random() % count] : 0;
Read original function source
static uint8_t chooseComputerMove(void)
{
int moveScores[64];
int bestScore = -10000;
for (uint8_t i = 0; i < legalMoveCount; ++i) {
const uint8_t cell = legalMoves[i];
int flips = 0;
for (uint8_t d = 0; d < 8; ++d)
flips += countFlips(cell, currentPlayer, DIRECTIONS[d][0], DIRECTIONS[d][1]);
moveScores[i] = POSITION_WEIGHTS[cell] + flips * 3;
if (moveScores[i] > bestScore) bestScore = moveScores[i];
}
uint8_t candidates[64], count = 0;
for (uint8_t i = 0; i < legalMoveCount; ++i) {
if (moveScores[i] >= bestScore - 8) candidates[count++] = i;
}
return count > 0 ? candidates[esp_random() % count] : 0;
}choosePiece() · source line 280
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 289
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 + (autoMode ? aiSpeedDelay(aiSpeedIndex) : 800U);
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 + (autoMode ? aiSpeedDelay(aiSpeedIndex) : 800U);
}startOthello() · source line 299
void startOthello(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_OTHELLO, &scores); - Execute
aiSpeedIndex = AI_SPEED_DEFAULT_INDEX; - Execute
resetBoard(); - Execute
choosePiece(); - Execute
gameState = STATE_SHOW_SCORES; - Execute
ESP_LOGI("othello", "START: %s", game_scores_status_text(scoreStatus));
Read original function source
void startOthello(uint32_t now_ms)
{
clockNow = stateStarted = now_ms;
running = true;
scoreSaveFailed = goldSaveFailed = false;
scores = (game_scores_t){0};
scoreStatus = game_scores_load(GAME_OTHELLO, &scores);
aiSpeedIndex = AI_SPEED_DEFAULT_INDEX;
resetBoard();
choosePiece();
gameState = STATE_SHOW_SCORES;
ESP_LOGI("othello", "START: %s", game_scores_status_text(scoreStatus));
}stopOthello() · source line 313
void stopOthello(void)
- Execute
running = false; - Execute
ESP_LOGI("othello", "STOP");
Read original function source
void stopOthello(void)
{
running = false;
ESP_LOGI("othello", "STOP");
}othelloAction() · source line 319
void othelloAction(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 othelloAction(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);
}updateOthello() · source line 343
void updateOthello(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
finishTurn();
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 updateOthello(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) finishTurn();
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 392
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);
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);
}drawOthello() · source line 401
void drawOthello(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, "OTHELLO", 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
fill_rect(frame, BOARD_X + column * CELL_SIZE, BOARD_Y + row * CELL_SIZE, CELL_SIZE - 1, CELL_SIZE - 1, 0x0320); - Decision
board[square] != EMPTYTrue- Execute
drawPiece(frame, square, board[square]);
False- Decision
gameState == STATE_PLAYING && legalMove(square, currentPlayer)True- Execute
fill_circle(frame, BOARD_X + column * CELL_SIZE + 10, BOARD_Y + row * CELL_SIZE + 10, 2, 0xc618);
FalseContinue after this decision.
- Decision
gameState == STATE_PLAYING && legalMoveCount > 0True- Execute
const uint8_t cell = legalMoves[selectedMove]; - Execute
draw_rect(frame, BOARD_X + (cell % 8) * CELL_SIZE + 2, BOARD_Y + (cell / 8) * CELL_SIZE + 2, 16, 16, 0xffe0);
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 uint8_t cell = legalMoves[selectedMove]; - Execute
snprintf(line, sizeof(line), "PLACE %c%u", 'A' + cell % 8, (unsigned)(cell / 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 drawOthello(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, "OTHELLO", 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;
fill_rect(frame, BOARD_X + column * CELL_SIZE, BOARD_Y + row * CELL_SIZE,
CELL_SIZE - 1, CELL_SIZE - 1, 0x0320);
if (board[square] != EMPTY) drawPiece(frame, square, board[square]);
else if (gameState == STATE_PLAYING && legalMove(square, currentPlayer))
fill_circle(frame, BOARD_X + column * CELL_SIZE + 10,
BOARD_Y + row * CELL_SIZE + 10, 2, 0xc618);
}
if (gameState == STATE_PLAYING && legalMoveCount > 0) {
const uint8_t cell = legalMoves[selectedMove];
draw_rect(frame, BOARD_X + (cell % 8) * CELL_SIZE + 2,
BOARD_Y + (cell / 8) * CELL_SIZE + 2, 16, 16, 0xffe0);
}
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 uint8_t cell = legalMoves[selectedMove];
snprintf(line, sizeof(line), "PLACE %c%u", 'A' + cell % 8, (unsigned)(cell / 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 "othello.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 and positional AI ported from the other LilyGO project's othello.cpp.
#define OTHELLO_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;
static uint8_t board[64];
static game_scores_t scores;
static game_score_result_t scoreStatus;
static GameState gameState;
static uint8_t legalMoves[64], legalMoveCount, selectedMove;
static uint8_t humanPiece, currentPlayer, goldEarningPiece, aiSpeedIndex;
static uint32_t stateStarted, computerMoveDue, clockNow, lastStickMove;
static bool running, autoMode, stickCentred, repeating, scoreSaveFailed, goldSaveFailed;
static int heldDirection;
static const char *boardMessage;
static const int8_t DIRECTIONS[8][2] =
{
{ -1, -1 },
{ -1, 0 },
{ -1, 1 },
{ 0, -1 },
{ 0, 1 },
{ 1, -1 },
{ 1, 0 },
{ 1, 1 }
};
static const int16_t POSITION_WEIGHTS[64] =
{
120, -25, 20, 5, 5, 20, -25, 120,
-25, -40, -5, -5, -5, -5, -40, -25,
20, -5, 15, 3, 3, 15, -5, 20,
5, -5, 3, 3, 3, 3, -5, 5,
5, -5, 3, 3, 3, 3, -5, 5,
20, -5, 15, 3, 3, 15, -5, 20,
-25, -40, -5, -5, -5, -5, -40, -25,
120, -25, 20, 5, 5, 20, -25, 120
};
static uint8_t opponent(uint8_t piece)
{
return piece == BLACK_PIECE
? WHITE_PIECE
: BLACK_PIECE;
}
static bool insideBoard(int row, int column)
{
return row >= 0 &&
row < 8 &&
column >= 0 &&
column < 8;
}
static uint8_t countPieces(uint8_t piece)
{
uint8_t total = 0;
for (uint8_t i = 0; i < 64; i++)
{
if (board[i] == piece)
total++;
}
return total;
}
static uint8_t countFlips(
uint8_t cell,
uint8_t piece,
int8_t rowDirection,
int8_t columnDirection
)
{
if (board[cell] != EMPTY)
return 0;
const int startRow = cell / 8;
const int startColumn = cell % 8;
int row = startRow + rowDirection;
int column = startColumn + columnDirection;
uint8_t flips = 0;
const uint8_t otherPiece = opponent(piece);
while (insideBoard(row, column) &&
board[row * 8 + column] == otherPiece)
{
flips++;
row += rowDirection;
column += columnDirection;
}
if (flips == 0 || !insideBoard(row, column))
return 0;
if (board[row * 8 + column] != piece)
return 0;
return flips;
}
static bool legalMove(uint8_t cell, uint8_t piece)
{
if (cell >= 64 || board[cell] != EMPTY)
return false;
for (uint8_t d = 0; d < 8; ++d)
{
if (countFlips(
cell,
piece,
DIRECTIONS[d][0],
DIRECTIONS[d][1]
) > 0)
{
return true;
}
}
return false;
}
static bool hasLegalMove(uint8_t piece)
{
for (uint8_t i = 0; i < 64; i++)
{
if (legalMove(i, piece))
return true;
}
return false;
}
static void makeMove(uint8_t cell, uint8_t piece)
{
const int startRow = cell / 8;
const int startColumn = cell % 8;
for (uint8_t d = 0; d < 8; ++d)
{
const uint8_t flipCount = countFlips(
cell,
piece,
DIRECTIONS[d][0],
DIRECTIONS[d][1]
);
int row = startRow + DIRECTIONS[d][0];
int column = startColumn + DIRECTIONS[d][1];
for (uint8_t i = 0; i < flipCount; i++)
{
board[row * 8 + column] = piece;
row += DIRECTIONS[d][0];
column += DIRECTIONS[d][1];
}
}
board[cell] = piece;
}
static void resetBoard(void)
{
for (uint8_t i = 0; i < 64; i++)
board[i] = EMPTY;
board[3 * 8 + 3] = WHITE_PIECE;
board[3 * 8 + 4] = BLACK_PIECE;
board[4 * 8 + 3] = BLACK_PIECE;
board[4 * 8 + 4] = WHITE_PIECE;
currentPlayer = BLACK_PIECE;
selectedMove = 0;
}
static void refreshLegalMoves(void)
{
legalMoveCount = 0;
for (uint8_t cell = 0; cell < 64; ++cell) {
if (legalMove(cell, currentPlayer)) legalMoves[legalMoveCount++] = cell;
}
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(OTHELLO_WIN_GOLD)) goldSaveFailed = true;
} else {
if (winner == humanPiece) {
incrementScore(&scores.wins);
incrementScore(&scores.streak);
if (!addUserGoldWon(OTHELLO_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_OTHELLO, &scores);
if (!scoreSaveFailed) scoreStatus = SCORES_OK;
} else scoreSaveFailed = true;
}
ESP_LOGI("othello", "%s; score save %s; Gold %s", boardMessage,
scoreSaveFailed ? "ERROR" : autoMode ? "NOT REQUIRED" : "OK",
goldSaveFailed ? "ERROR" : "OK");
}
static void finishTurn(void)
{
const uint8_t nextPlayer = opponent(currentPlayer);
const bool passed = !hasLegalMove(nextPlayer);
if (!passed) currentPlayer = nextPlayer;
else if (!hasLegalMove(currentPlayer)) {
const uint8_t black = countPieces(BLACK_PIECE);
const uint8_t white = countPieces(WHITE_PIECE);
finishGame(black > white ? BLACK_PIECE : white > black ? WHITE_PIECE : EMPTY);
return;
}
refreshLegalMoves();
boardMessage = passed ? (autoMode ? "TURN PASSED" : currentPlayer == humanPiece ? "CPU PASS" : "YOU PASS") :
autoMode || currentPlayer != humanPiece ? "CPU TURN" : "YOUR TURN";
computerMoveDue = clockNow + (autoMode ? aiSpeedDelay(aiSpeedIndex) : 800U);
}
static void performMove(uint8_t index)
{
if (index >= legalMoveCount || !legalMove(legalMoves[index], currentPlayer)) return;
makeMove(legalMoves[index], currentPlayer);
audioPlay(AUDIO_SFX_BOARD_CAPTURE);
finishTurn();
}
static uint8_t chooseComputerMove(void)
{
int moveScores[64];
int bestScore = -10000;
for (uint8_t i = 0; i < legalMoveCount; ++i) {
const uint8_t cell = legalMoves[i];
int flips = 0;
for (uint8_t d = 0; d < 8; ++d)
flips += countFlips(cell, currentPlayer, DIRECTIONS[d][0], DIRECTIONS[d][1]);
moveScores[i] = POSITION_WEIGHTS[cell] + flips * 3;
if (moveScores[i] > bestScore) bestScore = moveScores[i];
}
uint8_t candidates[64], count = 0;
for (uint8_t i = 0; i < legalMoveCount; ++i) {
if (moveScores[i] >= bestScore - 8) candidates[count++] = i;
}
return count > 0 ? candidates[esp_random() % count] : 0;
}
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 + (autoMode ? aiSpeedDelay(aiSpeedIndex) : 800U);
}
void startOthello(uint32_t now_ms)
{
clockNow = stateStarted = now_ms;
running = true;
scoreSaveFailed = goldSaveFailed = false;
scores = (game_scores_t){0};
scoreStatus = game_scores_load(GAME_OTHELLO, &scores);
aiSpeedIndex = AI_SPEED_DEFAULT_INDEX;
resetBoard();
choosePiece();
gameState = STATE_SHOW_SCORES;
ESP_LOGI("othello", "START: %s", game_scores_status_text(scoreStatus));
}
void stopOthello(void)
{
running = false;
ESP_LOGI("othello", "STOP");
}
void othelloAction(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 updateOthello(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) finishTurn();
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);
}
void drawOthello(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, "OTHELLO", 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;
fill_rect(frame, BOARD_X + column * CELL_SIZE, BOARD_Y + row * CELL_SIZE,
CELL_SIZE - 1, CELL_SIZE - 1, 0x0320);
if (board[square] != EMPTY) drawPiece(frame, square, board[square]);
else if (gameState == STATE_PLAYING && legalMove(square, currentPlayer))
fill_circle(frame, BOARD_X + column * CELL_SIZE + 10,
BOARD_Y + row * CELL_SIZE + 10, 2, 0xc618);
}
if (gameState == STATE_PLAYING && legalMoveCount > 0) {
const uint8_t cell = legalMoves[selectedMove];
draw_rect(frame, BOARD_X + (cell % 8) * CELL_SIZE + 2,
BOARD_Y + (cell / 8) * CELL_SIZE + 2, 16, 16, 0xffe0);
}
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 uint8_t cell = legalMoves[selectedMove];
snprintf(line, sizeof(line), "PLACE %c%u", 'A' + cell % 8, (unsigned)(cell / 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);
}