#include "Bot.h" #include "Board.h" #include "Move.h" #include "../Utilities/IntToStr.h" #include "../Utilities/StrToInt.h" #include #include #include Bot::Bot(std::string name, int difficulty) { this -> name = name; this -> difficulty = difficulty; } Move Bot::suggestMove(Board& board, char color) // the fucntion called by GameMaster when it's bot's turn to move // this function should be called when prepareBoardForMove is called // so bot will work as expected { if (difficulty == 1) // easy { return suggestEasyMove(board, color); } else if (difficulty == 2) // medium { return suggestMediumMove(board, color); } else if (difficulty == 3) // hard { return suggestHardMove(board, color); } else { return suggestMediumMove(board, color); // fallback } } Move Bot::suggestEasyMove(Board& board, char color) // the decision a bot would make in easy mode // the act is based on a random choice { int validMovesCount = board.countValidMoves(); std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution distrib(0, validMovesCount-1); int randomNumber = distrib(gen); int moveNumber = 0; for (int y = 0; y < board.BoardSize; y++) { for (int x = 0; x < board.BoardSize; x++) { if (board.validMovesGrid[y][x] == 'O') { if (moveNumber == randomNumber) { Move move{x, y, color}; return move; } else { moveNumber++; } } } } } Move Bot::suggestMediumMove(Board& board, char color) // the decision a bot would make in medium mode // the act is based on how much gain each move will have // the move with the most gain is made { int moveX, moveY; int maxGain = 0; for (int y = 0; y < board.BoardSize; y++) { for (int x = 0; x < board.BoardSize; x++) { if (board.validMovesGrid[y][x] == 'O') { int gain = board.isValid(x, y, color); if (gain > maxGain) { moveY = y; moveX = x; maxGain = gain; } } } } Move move{moveX, moveY, color}; return move; } Move Bot::suggestHardMove(Board& board, char color) // the decision a bot would make in hard mode { char oppColor = (color == 'W' ? 'B':'W'); int index = 0; int validMovesCount = board.countValidMoves(); Move moves[validMovesCount]; for (int y = 0; y < board.BoardSize; y++) { for (int x = 0; x < board.BoardSize; x++) { if (board.validMovesGrid[y][x] == 'O') { Move move {x, y, color}; moves[index] = move; index++; } } } Move bestMove = moves[0]; int bestScore = INT_MIN; for (int i = 0; i < validMovesCount; i++) { Move move = moves[i]; Board dummyBoard {board.BoardSize}; deepCopy(board, dummyBoard); dummyBoard.putPiece(move.x, move.y, move.color); dummyBoard.prepareBoardForMove(oppColor); int score = minimax(oppColor, dummyBoard, false, color); // start minimizing since its user's turn if (score > bestScore) { bestScore = score; bestMove = move; } } return bestMove; } std::string Bot::retrieveBot() // turns Bot structure into a string to be saved inside database // this string will be loaded using loadBot method { return name + "|" + intToStr(difficulty); } void Bot::loadBot(std::string bot) // fills Bot's properties according to the given string // this string is bot's informating saved inside database // this string was made using retrieveBot method { int seperatorIndex = bot.find("|"); name = bot.substr(0, seperatorIndex); difficulty = strToInt(bot.substr(seperatorIndex + 1, bot.size())); } //----------------------------------------- minimax algorithm ---------------------------------------------------- int Bot::ratePlacement(int x, int y, int boardSize) // Board is passed by refrence for time optimization // if we put a piece with these information, what score would the piece get // for the coordinates itselves { // corners if ((x == boardSize - 1 || x == 0) && (y == boardSize - 1 || y == 0)) return 100; // leading to corner moves: else if ( (x == boardSize - 2 && y == 1) || (x == boardSize - 2 && y == boardSize - 2) || (x == 1 && y == 1) || (x == 1 && y == boardSize - 2) ) return -50; else if ( (x == boardSize - 2 && y == boardSize - 1) || (x == boardSize - 2 && y == 0) || (x == 1 && y == 0) || (x == 1 && y == boardSize - 1) || (x == boardSize - 1 && y == 1) || (x == boardSize - 1 && y == boardSize - 2) || (x == 0 && y == 1) || (x == 0 && y == boardSize - 2) ) return -30; // edges (corners and corner neighbour already handled) else if (x == 0 || x == boardSize - 1 || y == 0 || y == boardSize - 1) return 3; // center else if ( (x == boardSize/2 && y == boardSize/2) || (x == boardSize/2 && y == boardSize/2 - 1) || (x == boardSize/2 - 1 && y == boardSize/2) || (x == boardSize/2 - 1 && y == boardSize/2 - 1) ) return 3; // other places have no value on their own return 0; } int Bot::rateBoard(Board& board, char botColor) { char oppColor = (botColor == 'W' ? 'B':'W'); Board dummyBoard {board.BoardSize}; deepCopy(board, dummyBoard); int botScore = 0, playerScore = 0; // possible moves rating dummyBoard.prepareBoardForMove(botColor); botScore += dummyBoard.countValidMoves(); dummyBoard.prepareBoardForMove(oppColor); botScore -= dummyBoard.countValidMoves(); // greedy strategy int blackCount = dummyBoard.countBlack(); int whiteCount = dummyBoard.countWhite(); if (botColor == 'B') { botScore += blackCount; botScore -= whiteCount; } else { botScore -= blackCount; botScore += whiteCount; } // placement score for (int y = 0; y < board.BoardSize; y++) { for (int x =0; x < board.BoardSize; x++) { char element = board.grid[y][x]; if (element == 'W' || element == 'B') { int score = ratePlacement(x, y, board.BoardSize); if (element == botColor) { botScore += score; } else { botScore -= score; } } } } return botScore; } int Bot::minimax(char color, Board dummyBoard, bool isMaxAgent, char botColor, int depth, int upFloor, int downFloor) { int index = 0; int validMovesCount = dummyBoard.countValidMoves(); if (depth == 3 || validMovesCount == 0) { return rateBoard(dummyBoard, botColor); } Move moves[validMovesCount]; for (int y = 0; y < dummyBoard.BoardSize; y++) { for (int x = 0; x < dummyBoard.BoardSize; x++) { if (dummyBoard.validMovesGrid[y][x] == 'O') { Move move {x, y, color}; moves[index] = move; index++; } } } if (isMaxAgent) // maximizing { int curMax = INT_MIN; // for each valid move in this turn for (int i = 0; i < validMovesCount; i++) { Move move = moves[i]; Board newBoard{dummyBoard.BoardSize}; deepCopy(dummyBoard, newBoard); newBoard.putPiece(move.x, move.y, move.color); newBoard.prepareBoardForMove(move.color == 'B' ? 'W':'B'); int minAgentAnswer = minimax((move.color == 'B' ? 'W':'B'), newBoard, false, botColor, depth + 1, upFloor, downFloor); curMax = std::max(curMax, minAgentAnswer); upFloor = std::max(upFloor, curMax); if (upFloor >= downFloor) break; } return curMax; } else // minimizing { int curMin = INT_MAX; // for each valid move in this turn for (int i = 0; i < validMovesCount; i++) { Move move = moves[i]; Board newBoard{dummyBoard.BoardSize}; deepCopy(dummyBoard, newBoard); newBoard.putPiece(move.x, move.y, move.color); newBoard.prepareBoardForMove(move.color == 'B' ? 'W':'B'); int maxAgentAnswer = minimax((move.color == 'B' ? 'W':'B'), newBoard, true, botColor, depth + 1, upFloor, downFloor); curMin = std::min(curMin, maxAgentAnswer); downFloor = std::min(downFloor, curMin); if (upFloor >= downFloor) break; } return curMin; } } void Bot::deepCopy(Board& board, Board& newBoard) { for (int y = 0; y < board.BoardSize; y++) { for (int x = 0; x < board.BoardSize; x++) { newBoard.grid[y][x] = board.grid[y][x]; } } }