|
@@ -1,12 +1,13 @@
|
|
#include "CheckerBoard.h"
|
|
#include "CheckerBoard.h"
|
|
#include <iostream>
|
|
#include <iostream>
|
|
|
|
|
|
-
|
|
|
|
-CheckerBoard::CheckerBoard() {
|
|
|
|
|
|
+CheckerBoard::CheckerBoard()
|
|
|
|
+{
|
|
lastMove = "NONE";
|
|
lastMove = "NONE";
|
|
}
|
|
}
|
|
|
|
|
|
-CheckerBoard CheckerBoard::loadFromFile(string filename) {
|
|
|
|
|
|
+CheckerBoard CheckerBoard::loadFromFile(string filename)
|
|
|
|
+{
|
|
CheckerBoard board = CheckerBoard();
|
|
CheckerBoard board = CheckerBoard();
|
|
|
|
|
|
ifstream file(filename);
|
|
ifstream file(filename);
|
|
@@ -14,68 +15,100 @@ CheckerBoard CheckerBoard::loadFromFile(string filename) {
|
|
|
|
|
|
int color = WHITE;
|
|
int color = WHITE;
|
|
|
|
|
|
- while (getline(file, line)) {
|
|
|
|
- if (line.find("White:") != string::npos) {
|
|
|
|
|
|
+ while (getline(file, line))
|
|
|
|
+ {
|
|
|
|
+ // Toggle color mode if color flag is found
|
|
|
|
+ if (line.find("White:") != string::npos)
|
|
|
|
+ {
|
|
color = WHITE;
|
|
color = WHITE;
|
|
}
|
|
}
|
|
|
|
|
|
- else if (line.find("Black:") != string::npos) {
|
|
|
|
|
|
+ else if (line.find("Black:") != string::npos)
|
|
|
|
+ {
|
|
color = BLACK;
|
|
color = BLACK;
|
|
}
|
|
}
|
|
|
|
|
|
- else if (!line.empty()) {
|
|
|
|
- if (line[0] == 'M') board.placeChecker(CheckerPosition(line.substr(1, 2)), color == WHITE ? CheckerPiece::WHITE_KING : CheckerPiece::BLACK_KING);
|
|
|
|
- else board.placeChecker(CheckerPosition(line), color == WHITE ? CheckerPiece::WHITE : CheckerPiece::BLACK);
|
|
|
|
|
|
+ else if (!line.empty())
|
|
|
|
+ {
|
|
|
|
+ // If the checker position starts with M (which marks a king), skip the first character of the position and create a king
|
|
|
|
+ if (line[0] == 'M')
|
|
|
|
+ board.placeChecker(CheckerPosition(line.substr(1, 2)), color == WHITE ? CheckerPiece::WHITE_KING : CheckerPiece::BLACK_KING);
|
|
|
|
+ // otherwise, use full position string and create a regular checker
|
|
|
|
+ else
|
|
|
|
+ board.placeChecker(CheckerPosition(line), color == WHITE ? CheckerPiece::WHITE : CheckerPiece::BLACK);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
file.close();
|
|
file.close();
|
|
|
|
|
|
|
|
+ // Iterate over whole board and assign CheckerPiece::EMPTY where no checker is present
|
|
for (int i = 0; i < BOARD_SIZE; i++)
|
|
for (int i = 0; i < BOARD_SIZE; i++)
|
|
{
|
|
{
|
|
for (int j = 0; j < BOARD_SIZE; j++)
|
|
for (int j = 0; j < BOARD_SIZE; j++)
|
|
{
|
|
{
|
|
CheckerPosition pos(i, j);
|
|
CheckerPosition pos(i, j);
|
|
CheckerPiece piece = board.getCheckerAt(pos);
|
|
CheckerPiece piece = board.getCheckerAt(pos);
|
|
- if (piece != WHITE && piece != WHITE_KING && piece != BLACK && piece != BLACK_KING) board.setCheckerAt(pos, CheckerPiece::NONE);
|
|
|
|
|
|
+ if (piece != WHITE && piece != WHITE_KING && piece != BLACK && piece != BLACK_KING)
|
|
|
|
+ board.setCheckerAt(pos, CheckerPiece::NONE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return board;
|
|
return board;
|
|
}
|
|
}
|
|
|
|
|
|
-CheckerPiece CheckerBoard::getCheckerAt(CheckerPosition pos) const {
|
|
|
|
- if (!pos.isValid()) return CheckerPiece::NONE;
|
|
|
|
|
|
+CheckerPiece CheckerBoard::getCheckerAt(CheckerPosition pos) const
|
|
|
|
+{
|
|
|
|
+ // Always return empty space when the position is not valid
|
|
|
|
+ if (!pos.isValid())
|
|
|
|
+ return CheckerPiece::NONE;
|
|
|
|
|
|
return board[pos.x][pos.y];
|
|
return board[pos.x][pos.y];
|
|
}
|
|
}
|
|
|
|
|
|
void CheckerBoard::setCheckerAt(CheckerPosition pos, CheckerPiece checker)
|
|
void CheckerBoard::setCheckerAt(CheckerPosition pos, CheckerPiece checker)
|
|
{
|
|
{
|
|
|
|
+ // Skip if position is invalid
|
|
|
|
+ if (!pos.isValid())
|
|
|
|
+ return;
|
|
|
|
+
|
|
board[pos.x][pos.y] = checker;
|
|
board[pos.x][pos.y] = checker;
|
|
}
|
|
}
|
|
|
|
|
|
-void CheckerBoard::moveChecker(CheckerPosition from, CheckerPosition to) {
|
|
|
|
|
|
+void CheckerBoard::moveChecker(CheckerPosition from, CheckerPosition to)
|
|
|
|
+{
|
|
CheckerPiece checker = getCheckerAt(from);
|
|
CheckerPiece checker = getCheckerAt(from);
|
|
|
|
+
|
|
|
|
+ // Remove the checker from its original position
|
|
setCheckerAt(from, CheckerPiece::NONE);
|
|
setCheckerAt(from, CheckerPiece::NONE);
|
|
|
|
+
|
|
|
|
+ // Calculate movement direction
|
|
int cx = to.x - from.x > 0 ? 1 : -1;
|
|
int cx = to.x - from.x > 0 ? 1 : -1;
|
|
int cy = to.y - from.y > 0 ? 1 : -1;
|
|
int cy = to.y - from.y > 0 ? 1 : -1;
|
|
|
|
+
|
|
|
|
+ // Current position for iterating
|
|
int sx = from.x + cx;
|
|
int sx = from.x + cx;
|
|
int sy = from.y + cy;
|
|
int sy = from.y + cy;
|
|
|
|
|
|
|
|
+ // Remove all pieces between the starting and ending point
|
|
while (sx != to.x)
|
|
while (sx != to.x)
|
|
{
|
|
{
|
|
setCheckerAt(CheckerPosition(sx, sy), CheckerPiece::NONE);
|
|
setCheckerAt(CheckerPosition(sx, sy), CheckerPiece::NONE);
|
|
sx += cx;
|
|
sx += cx;
|
|
sy += cy;
|
|
sy += cy;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // Insert the checker into new position
|
|
setCheckerAt(to, checker);
|
|
setCheckerAt(to, checker);
|
|
|
|
|
|
- if (checker == CheckerPiece::WHITE && to.y == BOARD_SIZE - 1) setCheckerAt(to, CheckerPiece::WHITE_KING);
|
|
|
|
- else if (checker == CheckerPiece::BLACK && to.y == 0) setCheckerAt(to, CheckerPiece::BLACK_KING);
|
|
|
|
|
|
+ // If reached opposite edge of the board, promote checker to king
|
|
|
|
+ if (checker == CheckerPiece::WHITE && to.y == BOARD_SIZE - 1)
|
|
|
|
+ setCheckerAt(to, CheckerPiece::WHITE_KING);
|
|
|
|
+ else if (checker == CheckerPiece::BLACK && to.y == 0)
|
|
|
|
+ setCheckerAt(to, CheckerPiece::BLACK_KING);
|
|
}
|
|
}
|
|
|
|
|
|
void CheckerBoard::print() const
|
|
void CheckerBoard::print() const
|
|
{
|
|
{
|
|
|
|
+ // Legend at the top
|
|
wcout << "--A---B---C---D---E---F---G---H---" << endl;
|
|
wcout << "--A---B---C---D---E---F---G---H---" << endl;
|
|
for (int y = BOARD_SIZE - 1; y >= 0; y--)
|
|
for (int y = BOARD_SIZE - 1; y >= 0; y--)
|
|
{
|
|
{
|
|
@@ -102,17 +135,27 @@ void CheckerBoard::print() const
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- wcout << "| " << y+1 << endl << "-------------------------------- " << endl;
|
|
|
|
|
|
+ // Print out the last border, the Y-axis legend and the separator row
|
|
|
|
+ wcout << "| " << y + 1 << endl
|
|
|
|
+ << "-------------------------------- " << endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-std::vector<CheckerBoard> CheckerBoard::generateLegalMoves(bool isWhite) const {
|
|
|
|
|
|
+std::vector<CheckerBoard> CheckerBoard::generateLegalMoves(bool isWhite) const
|
|
|
|
+{
|
|
std::vector<CheckerBoard> moves;
|
|
std::vector<CheckerBoard> moves;
|
|
- for (int i = 0; i < BOARD_SIZE; ++i) {
|
|
|
|
- for (int j = 0; j < BOARD_SIZE; ++j) {
|
|
|
|
|
|
+
|
|
|
|
+ // Iterate over all board cells
|
|
|
|
+ for (int i = 0; i < BOARD_SIZE; ++i)
|
|
|
|
+ {
|
|
|
|
+ for (int j = 0; j < BOARD_SIZE; ++j)
|
|
|
|
+ {
|
|
CheckerPiece checker = getCheckerAt(CheckerPosition(i, j));
|
|
CheckerPiece checker = getCheckerAt(CheckerPosition(i, j));
|
|
|
|
+
|
|
|
|
+ // If the checker is of corresponding color, generate all possible moves for it
|
|
if ((isWhite && (checker == WHITE || checker == WHITE_KING)) ||
|
|
if ((isWhite && (checker == WHITE || checker == WHITE_KING)) ||
|
|
- (!isWhite && (checker == BLACK || checker == BLACK_KING))) {
|
|
|
|
|
|
+ (!isWhite && (checker == BLACK || checker == BLACK_KING)))
|
|
|
|
+ {
|
|
generateMovesForPiece(CheckerPosition(i, j), moves);
|
|
generateMovesForPiece(CheckerPosition(i, j), moves);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -121,62 +164,101 @@ std::vector<CheckerBoard> CheckerBoard::generateLegalMoves(bool isWhite) const {
|
|
return moves;
|
|
return moves;
|
|
}
|
|
}
|
|
|
|
|
|
-void CheckerBoard::applyMove(const CheckerBoard& move) {
|
|
|
|
- for (int i = 0; i < BOARD_SIZE; ++i) {
|
|
|
|
- for (int j = 0; j < BOARD_SIZE; ++j) {
|
|
|
|
|
|
+void CheckerBoard::applyMove(const CheckerBoard &move)
|
|
|
|
+{
|
|
|
|
+ // Iterate over all board cells and copy values from the alternative board
|
|
|
|
+ for (int i = 0; i < BOARD_SIZE; ++i)
|
|
|
|
+ {
|
|
|
|
+ for (int j = 0; j < BOARD_SIZE; ++j)
|
|
|
|
+ {
|
|
setCheckerAt(CheckerPosition(i, j), move.getCheckerAt(CheckerPosition(i, j)));
|
|
setCheckerAt(CheckerPosition(i, j), move.getCheckerAt(CheckerPosition(i, j)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-int CheckerBoard::evaluate() const {
|
|
|
|
|
|
+int CheckerBoard::evaluate() const
|
|
|
|
+{
|
|
int score = 0;
|
|
int score = 0;
|
|
- for (int i = 0; i < BOARD_SIZE; ++i) {
|
|
|
|
- for (int j = 0; j < BOARD_SIZE; ++j) {
|
|
|
|
|
|
+
|
|
|
|
+ // Iterate over all board cells and calculate the score of the board
|
|
|
|
+ for (int i = 0; i < BOARD_SIZE; ++i)
|
|
|
|
+ {
|
|
|
|
+ for (int j = 0; j < BOARD_SIZE; ++j)
|
|
|
|
+ {
|
|
CheckerPiece checker = getCheckerAt(CheckerPosition(i, j));
|
|
CheckerPiece checker = getCheckerAt(CheckerPosition(i, j));
|
|
- if (checker == WHITE) score += 1;
|
|
|
|
- else if (checker == WHITE_KING) score += 3;
|
|
|
|
- else if (checker == BLACK) score -= 1;
|
|
|
|
- else if (checker == BLACK_KING) score -= 3;
|
|
|
|
|
|
+
|
|
|
|
+ // Kings are more valuable
|
|
|
|
+ if (checker == WHITE)
|
|
|
|
+ score += 1;
|
|
|
|
+ else if (checker == WHITE_KING)
|
|
|
|
+ score += 3;
|
|
|
|
+ else if (checker == BLACK)
|
|
|
|
+ score -= 1;
|
|
|
|
+ else if (checker == BLACK_KING)
|
|
|
|
+ score -= 3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return score;
|
|
return score;
|
|
}
|
|
}
|
|
|
|
|
|
-bool CheckerBoard::isGameOver() const {
|
|
|
|
|
|
+bool CheckerBoard::isGameOver() const
|
|
|
|
+{
|
|
bool whiteExists = false, blackExists = false;
|
|
bool whiteExists = false, blackExists = false;
|
|
- for (int i = 0; i < BOARD_SIZE; ++i) {
|
|
|
|
- for (int j = 0; j < BOARD_SIZE; ++j) {
|
|
|
|
- if (board[i][j] == WHITE || board[i][j] == WHITE_KING) whiteExists = true;
|
|
|
|
- if (board[i][j] == BLACK || board[i][j] == BLACK_KING) blackExists = true;
|
|
|
|
|
|
+
|
|
|
|
+ // Iterate over all board cells and find if any checkers of each side still exist
|
|
|
|
+ for (int i = 0; i < BOARD_SIZE; ++i)
|
|
|
|
+ {
|
|
|
|
+ for (int j = 0; j < BOARD_SIZE; ++j)
|
|
|
|
+ {
|
|
|
|
+ if (board[i][j] == WHITE || board[i][j] == WHITE_KING)
|
|
|
|
+ whiteExists = true;
|
|
|
|
+ if (board[i][j] == BLACK || board[i][j] == BLACK_KING)
|
|
|
|
+ blackExists = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // The game is over when one of the sides is cleansed
|
|
return !whiteExists || !blackExists;
|
|
return !whiteExists || !blackExists;
|
|
}
|
|
}
|
|
|
|
|
|
-void CheckerBoard::generateMovesForPiece(CheckerPosition pos, std::vector<CheckerBoard>& moves) const {
|
|
|
|
|
|
+void CheckerBoard::generateMovesForPiece(CheckerPosition pos, std::vector<CheckerBoard> &moves) const
|
|
|
|
+{
|
|
CheckerPiece piece = getCheckerAt(pos);
|
|
CheckerPiece piece = getCheckerAt(pos);
|
|
- int directions[4][2] = { {1, 1}, {1, -1}, {-1, 1}, {-1, -1} };
|
|
|
|
|
|
|
|
- for (auto& dir : directions) {
|
|
|
|
|
|
+ // Possible directions are: bottom-left, top-left, top-right, bottom-right
|
|
|
|
+ int directions[4][2] = {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
|
|
|
|
+
|
|
|
|
+ for (auto &dir : directions)
|
|
|
|
+ {
|
|
|
|
+ // Position after the move
|
|
CheckerPosition newpos(pos.x + dir[0], pos.y + dir[1]);
|
|
CheckerPosition newpos(pos.x + dir[0], pos.y + dir[1]);
|
|
|
|
+
|
|
|
|
+ // Position after the move if an enemy's checker is captured
|
|
CheckerPosition cpos(pos.x + 2 * dir[0], pos.y + 2 * dir[1]);
|
|
CheckerPosition cpos(pos.x + 2 * dir[0], pos.y + 2 * dir[1]);
|
|
|
|
|
|
- if (isValidMove(pos, newpos, piece)) {
|
|
|
|
|
|
+ if (isValidMove(pos, newpos, piece))
|
|
|
|
+ {
|
|
|
|
+ // Copy the board and apply the move
|
|
CheckerBoard newBoard = *this;
|
|
CheckerBoard newBoard = *this;
|
|
newBoard.moveChecker(pos, newpos);
|
|
newBoard.moveChecker(pos, newpos);
|
|
|
|
+
|
|
|
|
+ // Save data about the move
|
|
newBoard.lastMove = pos.to_num_string() + " -> " + newpos.to_num_string();
|
|
newBoard.lastMove = pos.to_num_string() + " -> " + newpos.to_num_string();
|
|
newBoard.moveOldPos = pos;
|
|
newBoard.moveOldPos = pos;
|
|
newBoard.moveNewPos = newpos;
|
|
newBoard.moveNewPos = newpos;
|
|
|
|
|
|
- if ((piece == WHITE && newpos.y == BOARD_SIZE - 1) || (piece == BLACK && newpos.y == 0)) {
|
|
|
|
|
|
+ // Promote to king if the opposite edge is reached
|
|
|
|
+ if ((piece == WHITE && newpos.y == BOARD_SIZE - 1) || (piece == BLACK && newpos.y == 0))
|
|
|
|
+ {
|
|
newBoard.setCheckerAt(newpos, (piece == WHITE) ? WHITE_KING : BLACK_KING);
|
|
newBoard.setCheckerAt(newpos, (piece == WHITE) ? WHITE_KING : BLACK_KING);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Save the move
|
|
moves.push_back(newBoard);
|
|
moves.push_back(newBoard);
|
|
|
|
|
|
- CheckerPosition cpos(pos.x + 2 * dir[0], pos.y + 2 * dir[1]);
|
|
|
|
- if (isValidCapture(pos, newpos, cpos, piece)) {
|
|
|
|
|
|
+ // If a capture is possible, count it as a different move
|
|
|
|
+ if (isValidCapture(pos, newpos, cpos, piece))
|
|
|
|
+ {
|
|
CheckerBoard newBoard = *this;
|
|
CheckerBoard newBoard = *this;
|
|
CheckerPiece checker = newBoard.getCheckerAt(pos);
|
|
CheckerPiece checker = newBoard.getCheckerAt(pos);
|
|
|
|
|
|
@@ -184,19 +266,24 @@ void CheckerBoard::generateMovesForPiece(CheckerPosition pos, std::vector<Checke
|
|
newBoard.moveOldPos = pos;
|
|
newBoard.moveOldPos = pos;
|
|
newBoard.moveNewPos = newpos;
|
|
newBoard.moveNewPos = newpos;
|
|
|
|
|
|
|
|
+ // Move the checker and remove one that was captured
|
|
newBoard.setCheckerAt(newpos, CheckerPiece::NONE);
|
|
newBoard.setCheckerAt(newpos, CheckerPiece::NONE);
|
|
newBoard.setCheckerAt(pos, CheckerPiece::NONE);
|
|
newBoard.setCheckerAt(pos, CheckerPiece::NONE);
|
|
newBoard.setCheckerAt(cpos, checker);
|
|
newBoard.setCheckerAt(cpos, checker);
|
|
|
|
|
|
- if ((piece == WHITE && newpos.y == BOARD_SIZE - 1) || (piece == BLACK && cpos.y == 0)) {
|
|
|
|
|
|
+ // If the opposite edge of the board is reached, promote to king
|
|
|
|
+ if ((piece == WHITE && newpos.y == BOARD_SIZE - 1) || (piece == BLACK && cpos.y == 0))
|
|
|
|
+ {
|
|
newBoard.setCheckerAt(cpos, (piece == WHITE) ? WHITE_KING : BLACK_KING);
|
|
newBoard.setCheckerAt(cpos, (piece == WHITE) ? WHITE_KING : BLACK_KING);
|
|
}
|
|
}
|
|
|
|
|
|
moves.push_back(newBoard);
|
|
moves.push_back(newBoard);
|
|
- //generateAdditionalCaptures(cpos, newBoard, moves);
|
|
|
|
|
|
+ // generateAdditionalCaptures(cpos, newBoard, moves);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- else if (isValidCapture(pos, newpos, cpos, piece)) {
|
|
|
|
|
|
+ // If the move is not valid, the capture still might be
|
|
|
|
+ else if (isValidCapture(pos, newpos, cpos, piece))
|
|
|
|
+ {
|
|
CheckerBoard newBoard = *this;
|
|
CheckerBoard newBoard = *this;
|
|
CheckerPiece checker = newBoard.getCheckerAt(pos);
|
|
CheckerPiece checker = newBoard.getCheckerAt(pos);
|
|
|
|
|
|
@@ -208,15 +295,18 @@ void CheckerBoard::generateMovesForPiece(CheckerPosition pos, std::vector<Checke
|
|
newBoard.setCheckerAt(pos, CheckerPiece::NONE);
|
|
newBoard.setCheckerAt(pos, CheckerPiece::NONE);
|
|
newBoard.setCheckerAt(cpos, checker);
|
|
newBoard.setCheckerAt(cpos, checker);
|
|
|
|
|
|
- if ((piece == WHITE && newpos.y == BOARD_SIZE - 1) || (piece == BLACK && cpos.y == 0)) {
|
|
|
|
|
|
+ // Promote to king if the opposite edge is reached
|
|
|
|
+ if ((piece == WHITE && newpos.y == BOARD_SIZE - 1) || (piece == BLACK && cpos.y == 0))
|
|
|
|
+ {
|
|
newBoard.setCheckerAt(cpos, (piece == WHITE) ? WHITE_KING : BLACK_KING);
|
|
newBoard.setCheckerAt(cpos, (piece == WHITE) ? WHITE_KING : BLACK_KING);
|
|
}
|
|
}
|
|
|
|
|
|
moves.push_back(newBoard);
|
|
moves.push_back(newBoard);
|
|
- //generateAdditionalCaptures(cpos, newBoard, moves);
|
|
|
|
|
|
+ // generateAdditionalCaptures(cpos, newBoard, moves);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Print out moves for debug purposes
|
|
/*for (auto& move : moves)
|
|
/*for (auto& move : moves)
|
|
{
|
|
{
|
|
wcout << "Move: \n";
|
|
wcout << "Move: \n";
|
|
@@ -224,70 +314,120 @@ void CheckerBoard::generateMovesForPiece(CheckerPosition pos, std::vector<Checke
|
|
}*/
|
|
}*/
|
|
}
|
|
}
|
|
|
|
|
|
-bool CheckerBoard::isValidMove(CheckerPosition oldPos, CheckerPosition newPos, CheckerPiece piece) const {
|
|
|
|
- if (newPos.x < 0 || newPos.x >= BOARD_SIZE) return false;
|
|
|
|
- if (newPos.y < 0 || newPos.y >= BOARD_SIZE) return false;
|
|
|
|
|
|
+bool CheckerBoard::isValidMove(CheckerPosition oldPos, CheckerPosition newPos, CheckerPiece piece) const
|
|
|
|
+{
|
|
|
|
+ // If the new position is not valid, the move is not valid either
|
|
|
|
+ if (!newPos.isValid())
|
|
|
|
+ return false;
|
|
|
|
|
|
|
|
+ // Checker piece at the current position
|
|
CheckerPiece at = getCheckerAt(oldPos);
|
|
CheckerPiece at = getCheckerAt(oldPos);
|
|
|
|
+ // Checker piece at the new position
|
|
CheckerPiece atnew = getCheckerAt(newPos);
|
|
CheckerPiece atnew = getCheckerAt(newPos);
|
|
|
|
|
|
- if (at == CheckerPiece::NONE) return false;
|
|
|
|
- if (atnew != CheckerPiece::NONE) return false;
|
|
|
|
|
|
+ // There should be a checker in the current position (the one we're moving)
|
|
|
|
+ if (at == CheckerPiece::NONE)
|
|
|
|
+ return false;
|
|
|
|
+ // You can't move to a cell if it's occupied
|
|
|
|
+ if (atnew != CheckerPiece::NONE)
|
|
|
|
+ return false;
|
|
|
|
|
|
|
|
+ // Calculate position delta
|
|
int dx = newPos.x - oldPos.x, dy = newPos.y - oldPos.y;
|
|
int dx = newPos.x - oldPos.x, dy = newPos.y - oldPos.y;
|
|
|
|
+
|
|
|
|
+ // Calcule movement direction
|
|
int cx = dx > 0 ? 1 : -1, cy = dy > 0 ? 1 : -1;
|
|
int cx = dx > 0 ? 1 : -1, cy = dy > 0 ? 1 : -1;
|
|
|
|
+
|
|
bool isWhite = at == CheckerPiece::WHITE || at == CheckerPiece::WHITE_KING;
|
|
bool isWhite = at == CheckerPiece::WHITE || at == CheckerPiece::WHITE_KING;
|
|
|
|
|
|
- if (abs(dx) != abs(dy) || dx == 0) return false;
|
|
|
|
|
|
+ // Checkers may only move diagonally
|
|
|
|
+ if (abs(dx) != abs(dy) || dx == 0)
|
|
|
|
+ return false;
|
|
|
|
|
|
|
|
+ // King-specific conditions
|
|
if (piece == CheckerPiece::WHITE_KING || piece == CheckerPiece::BLACK_KING)
|
|
if (piece == CheckerPiece::WHITE_KING || piece == CheckerPiece::BLACK_KING)
|
|
{
|
|
{
|
|
int sx = oldPos.x + cx, sy = oldPos.y + cy;
|
|
int sx = oldPos.x + cx, sy = oldPos.y + cy;
|
|
|
|
|
|
|
|
+ // There shouldn't be an ally checker in the way
|
|
while (sx != newPos.x || sy != newPos.y)
|
|
while (sx != newPos.x || sy != newPos.y)
|
|
{
|
|
{
|
|
CheckerPiece between = getCheckerAt(CheckerPosition(sx, sy));
|
|
CheckerPiece between = getCheckerAt(CheckerPosition(sx, sy));
|
|
|
|
|
|
- if (isWhite && !(between == CheckerPiece::BLACK || between == CheckerPiece::BLACK_KING || between == CheckerPiece::NONE)) return false;
|
|
|
|
- if (!isWhite && !(between == CheckerPiece::WHITE || between == CheckerPiece::WHITE_KING || between == CheckerPiece::NONE)) return false;
|
|
|
|
|
|
+ if (isWhite && !(between == CheckerPiece::BLACK || between == CheckerPiece::BLACK_KING || between == CheckerPiece::NONE))
|
|
|
|
+ return false;
|
|
|
|
+ if (!isWhite && !(between == CheckerPiece::WHITE || between == CheckerPiece::WHITE_KING || between == CheckerPiece::NONE))
|
|
|
|
+ return false;
|
|
|
|
|
|
sx += cx;
|
|
sx += cx;
|
|
sy += cy;
|
|
sy += cy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ // Conditions for regular checkers
|
|
else
|
|
else
|
|
{
|
|
{
|
|
- if (isWhite && dy == -1) return false;
|
|
|
|
- else if (!isWhite && dy == 1) return false;
|
|
|
|
|
|
+ // Regular checkers may not go backwards
|
|
|
|
+ if (isWhite && dy == -1)
|
|
|
|
+ return false;
|
|
|
|
+ else if (!isWhite && dy == 1)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ // If moved 2 cells (capture scenario)
|
|
else if (abs(dx) == 2)
|
|
else if (abs(dx) == 2)
|
|
{
|
|
{
|
|
|
|
+ // Checker in-between the current and the new position
|
|
CheckerPiece between = getCheckerAt(CheckerPosition(oldPos.x + cx, oldPos.y + cy));
|
|
CheckerPiece between = getCheckerAt(CheckerPosition(oldPos.x + cx, oldPos.y + cy));
|
|
- if (between == CheckerPiece::NONE) return false;
|
|
|
|
- if (getCheckerAt(newPos) != CheckerPiece::NONE) return false;
|
|
|
|
- if (isWhite && !(between == CheckerPiece::BLACK || between == CheckerPiece::BLACK_KING)) return false;
|
|
|
|
- if (!isWhite && !(between == CheckerPiece::WHITE || between == CheckerPiece::WHITE_KING)) return false;
|
|
|
|
|
|
+
|
|
|
|
+ // There should be a checker to capture
|
|
|
|
+ if (between == CheckerPiece::NONE)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ // There shouldn't be any checker in the new position
|
|
|
|
+ if (getCheckerAt(newPos) != CheckerPiece::NONE)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ // The captured checker shall not be ally
|
|
|
|
+ if (isWhite && !(between == CheckerPiece::BLACK || between == CheckerPiece::BLACK_KING))
|
|
|
|
+ return false;
|
|
|
|
+ if (!isWhite && !(between == CheckerPiece::WHITE || between == CheckerPiece::WHITE_KING))
|
|
|
|
+ return false;
|
|
}
|
|
}
|
|
- else if (abs(dx) > 2) return false;
|
|
|
|
|
|
+ // Regular checkers may not move more than 2 cells in any situation
|
|
|
|
+ else if (abs(dx) > 2)
|
|
|
|
+ return false;
|
|
}
|
|
}
|
|
|
|
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
-void CheckerBoard::generateAdditionalCaptures(CheckerPosition pos, CheckerBoard& currentBoard, std::vector<CheckerBoard>& moves) const {
|
|
|
|
|
|
+void CheckerBoard::generateAdditionalCaptures(CheckerPosition pos, CheckerBoard ¤tBoard, std::vector<CheckerBoard> &moves) const
|
|
|
|
+{
|
|
CheckerPiece piece = currentBoard.getCheckerAt(pos);
|
|
CheckerPiece piece = currentBoard.getCheckerAt(pos);
|
|
- int directions[4][2] = { {1, 1}, {1, -1}, {-1, 1}, {-1, -1} };
|
|
|
|
|
|
|
|
- for (auto& dir : directions) {
|
|
|
|
|
|
+ // Top-left, top-right, bottom-right, bottom-left of the checker
|
|
|
|
+ int directions[4][2] = {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
|
|
|
|
+
|
|
|
|
+ // Analyze all directions
|
|
|
|
+ for (auto &dir : directions)
|
|
|
|
+ {
|
|
|
|
+ // The position of the captured checker
|
|
CheckerPosition npos(pos.x + dir[0], pos.y + dir[1]);
|
|
CheckerPosition npos(pos.x + dir[0], pos.y + dir[1]);
|
|
|
|
+ // Final position after capture
|
|
CheckerPosition cpos(pos.x + 2 * dir[0], pos.y + 2 * dir[0]);
|
|
CheckerPosition cpos(pos.x + 2 * dir[0], pos.y + 2 * dir[0]);
|
|
- if (isValidCapture(pos, npos, cpos, piece)) {
|
|
|
|
|
|
+
|
|
|
|
+ // If the capture is valid
|
|
|
|
+ if (isValidCapture(pos, npos, cpos, piece))
|
|
|
|
+ {
|
|
CheckerBoard newBoard = currentBoard;
|
|
CheckerBoard newBoard = currentBoard;
|
|
|
|
|
|
|
|
+ // Move the checker and remove captured
|
|
newBoard.setCheckerAt(cpos, newBoard.getCheckerAt(pos));
|
|
newBoard.setCheckerAt(cpos, newBoard.getCheckerAt(pos));
|
|
newBoard.setCheckerAt(pos, CheckerPiece::NONE);
|
|
newBoard.setCheckerAt(pos, CheckerPiece::NONE);
|
|
newBoard.setCheckerAt(npos, CheckerPiece::NONE);
|
|
newBoard.setCheckerAt(npos, CheckerPiece::NONE);
|
|
|
|
|
|
- if ((piece == WHITE && cpos.x == BOARD_SIZE-1) || (piece == BLACK && cpos.x == 0)) {
|
|
|
|
|
|
+ // Promote to king if the opposite edge is reached
|
|
|
|
+ if ((piece == WHITE && cpos.x == BOARD_SIZE - 1) || (piece == BLACK && cpos.x == 0))
|
|
|
|
+ {
|
|
newBoard.setCheckerAt(cpos, (piece == WHITE) ? WHITE_KING : BLACK_KING);
|
|
newBoard.setCheckerAt(cpos, (piece == WHITE) ? WHITE_KING : BLACK_KING);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -297,17 +437,36 @@ void CheckerBoard::generateAdditionalCaptures(CheckerPosition pos, CheckerBoard&
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-bool CheckerBoard::isValidCapture(CheckerPosition pos, CheckerPosition newPos, CheckerPosition cpos, CheckerPiece piece) const {
|
|
|
|
|
|
+bool CheckerBoard::isValidCapture(CheckerPosition pos, CheckerPosition newPos, CheckerPosition cpos, CheckerPiece piece) const
|
|
|
|
+{
|
|
|
|
+ // Current checker piece
|
|
CheckerPiece oldC = getCheckerAt(pos);
|
|
CheckerPiece oldC = getCheckerAt(pos);
|
|
|
|
+ // Checker piece at the end position
|
|
CheckerPiece newC = getCheckerAt(newPos);
|
|
CheckerPiece newC = getCheckerAt(newPos);
|
|
|
|
+ // Checker piece at the capture position
|
|
CheckerPiece cC = getCheckerAt(cpos);
|
|
CheckerPiece cC = getCheckerAt(cpos);
|
|
- if (!cpos.isValid()) return false;
|
|
|
|
- if (cC != CheckerPiece::NONE) return false;
|
|
|
|
- if (newC == CheckerPiece::NONE) return false;
|
|
|
|
|
|
|
|
- if (piece == WHITE && (newC == BLACK || newC == BLACK_KING)) return true;
|
|
|
|
- if (piece == BLACK && (newC == WHITE || newC == WHITE_KING)) return true;
|
|
|
|
- if ((piece == WHITE_KING || piece == BLACK_KING) && abs(cpos.y - pos.y) == 2 && abs(cpos.x - pos.x) == 2) return true;
|
|
|
|
|
|
+ // Capture position should be valid
|
|
|
|
+ if (!cpos.isValid())
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ // There should be a checker to capture
|
|
|
|
+ if (cC != CheckerPiece::NONE)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ // There should be a checker at the current position
|
|
|
|
+ if (newC == CheckerPiece::NONE)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ // Valid if we're capturing the enemy checker
|
|
|
|
+ if (piece == WHITE && (newC == BLACK || newC == BLACK_KING))
|
|
|
|
+ return true;
|
|
|
|
+ if (piece == BLACK && (newC == WHITE || newC == WHITE_KING))
|
|
|
|
+ return true;
|
|
|
|
+
|
|
|
|
+ // Not sure about this part
|
|
|
|
+ if ((piece == WHITE_KING || piece == BLACK_KING) && abs(cpos.y - pos.y) == 2 && abs(cpos.x - pos.x) == 2)
|
|
|
|
+ return true;
|
|
|
|
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|