372 lines
9.2 KiB
C++
372 lines
9.2 KiB
C++
#include "Bot.h"
|
|
#include "Board.h"
|
|
#include "Move.h"
|
|
#include "../Utilities/IntToStr.h"
|
|
#include "../Utilities/StrToInt.h"
|
|
#include <string>
|
|
#include <random>
|
|
#include <climits>
|
|
|
|
|
|
|
|
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<int> 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];
|
|
}
|
|
}
|
|
} |