// Copyright (c) 2023 Dominic Masters // // This software is released under the MIT License. // https://opensource.org/licenses/MIT #include "TicTacToeLogic.hpp" using namespace Dawn; enum TicTacToeTileState Dawn::ticTacToeDetermineWinner( const std::map board, std::vector *winningCombo ) { uint8_t i; assertNotNull(winningCombo, "ticTacToeDetermineWinner: winningCombo cannot be null"); // Check rows for(i = 0; i < 9; i += 3) { if(board.at(i) == board.at(i + 1) && board.at(i) == board.at(i + 2) && board.at(i) != 0) { *winningCombo = { i, (uint8_t)(i + 0x01), (uint8_t)(i + 0x02) }; return board.at(i); } } // Check columns for(i = 0; i < 3; i++) { if(board.at(i) == board.at(i + 3) && board.at(i) == board.at(i + 6) && board.at(i) != 0) { *winningCombo = { i, (uint8_t)(i + 0x03), (uint8_t)(i + 0x06) }; return board.at(i); } } // Check diagonals if(board.at(0) == board.at(4) && board.at(0) == board.at(8) && board.at(0) != 0) { *winningCombo = { 0, 0x04, 0x08 }; return board.at(0); } if(board.at(2) == board.at(4) && board.at(2) == board.at(6) && board.at(2) != 0) { *winningCombo = { 0x02, 0x04, 0x06 }; return board.at(2); } return TIC_TAC_TOE_EMPTY; } bool_t Dawn::ticTacToeIsGameOver( const std::map board ) { auto it = board.begin(); while(it != board.end()) { if(it->second == TIC_TAC_TOE_EMPTY) return false; ++it; } return true; } int32_t Dawn::ticTacToeGetBoardScore( std::map board, enum TicTacToeTileState player ) { int32_t score = 0; uint8_t lines[8][3] = { {0, 1, 2}, {3, 4, 5}, {6, 7, 8}, {0, 3, 6}, {1, 4, 7}, {2, 5, 8}, {0, 4, 8}, {2, 4, 6} }; for (uint8_t i = 0; i < 8; i++) { uint8_t countPlayer = 0; uint8_t countEmpty = 0; for (uint8_t j = 0; j < 3; j++) { if(board[lines[i][j]] == player) { countPlayer++; } else if(board[lines[i][j]] == TIC_TAC_TOE_EMPTY) { countEmpty++; } } if(countPlayer == 2 && countEmpty == 1) { score += 10; } else if(countPlayer == 1 && countEmpty == 2) { score += 1; } } return score; } uint8_t Dawn::ticTacToeGetAiMove( std::map board, enum TicTacToeTileState player ) { std::vector winningCombo; // First, check if there's an immediate winning move for the AI for(uint8_t i = 0; i < 9; i++) { if(board[i] != TIC_TAC_TOE_EMPTY) continue; board[i] = player; if(ticTacToeDetermineWinner(board, &winningCombo) == player) { board[i] = TIC_TAC_TOE_EMPTY; return i; } board[i] = TIC_TAC_TOE_EMPTY; } // Next, check if the player has an immediate winning move and block it auto opponent = (player == TIC_TAC_TOE_NOUGHT) ? TIC_TAC_TOE_CROSS : TIC_TAC_TOE_NOUGHT; for(uint8_t i = 0; i < 9; i++) { if(board[i] != TIC_TAC_TOE_EMPTY) continue; board[i] = opponent; if(ticTacToeDetermineWinner(board, &winningCombo) == opponent) { board[i] = TIC_TAC_TOE_EMPTY; return i; } board[i] = TIC_TAC_TOE_EMPTY; } // If neither player has an immediate winning move, use the simple heuristic to choose a move uint8_t bestMove = -1; int32_t bestScore = -1000; for(uint8_t i = 0; i < 9; i++) { if(board[i] != TIC_TAC_TOE_EMPTY) continue; board[i] = player; auto score = ticTacToeGetBoardScore(board, player); board[i] = TIC_TAC_TOE_EMPTY; if(score > bestScore) { bestMove = i; bestScore = score; } } return bestMove; }