// © 2025 A.M. Rowsell // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // This Source Code Form is "Incompatible With Secondary Licenses", as // defined by the Mozilla Public License, v. 2.0. #include "inc/Piece.hpp" #include "inc/Board.hpp" Piece::~Piece() { return; } // TODO: Add operator overload for << to allow easy printing // TODO: Test getPieceAt() function to see if it works as intended // because the Piece class is instantiated in another class using // unique_ptr, non-pure virtual functions apparently *must* // have definitions, or the linker freaks out // so this is just a stub that does nothing. it doesn't matter // because only the derived class versions should be called. std::vector Piece::getLegalMoves(const Square &from, Board &board) const { std::vector moveList; return moveList; } bool Piece::finalMoveChecks(std::vector *moveList, Board &board) { // This function needs to do the final checks before declaring a move legal // Check to see if the piece that is being moves is currently blocking check // Check to see if a castle is trying to go through check or out of check // Check if it's a piece trying to capture the King (though this shouldn't happen normally) return true; } // *INDENT-OFF* // // ## # // # // # ## ## # ## ## # // # # # # # # # // ## # # # # # // # # # # # # # // ## ## ##### ### ## ### // # // ### // *INDENT-ON* bool King::checkForCheck(Board &board) const { std::vector> kingVulnerable = { {-1, 0}, // Up {-1, -1}, // up-left {-1, 1}, // up-right {1, 0}, // Down {1, -1}, // down-left {1, 1}, // down-right {0, -1}, // Left {0, 1}, // Right {-1, -2}, // 1 {-1, 2}, // 2 {-2, -1}, // 3 {-2, 1}, // 4 {1, -2}, // 5 {1, 2}, // 6 {2, -1}, // 7 {2, 1} // 8 }; // locate our King return inCheck; } std::vector King::getLegalMoves(const Square &from, Board &board) const { std::vector moveList; std::vector> directions = { {-1, 0}, // Up {-1, -1}, // up-left {-1, 1}, // up-right {1, 0}, // Down {1, -1}, // down-left {1, 1}, // down-right {0, -1}, // Left {0, 1} // Right }; for(auto &dir : directions) { // establish r/f for square to check int r = from.rank + dir[0]; int f = from.file + dir[1]; Square targetSquare{static_cast(r), static_cast(f)}; if(targetSquare.isValid()) { const Piece *target = board.getPieceAt(targetSquare); // examine the target square if(!target) { // if square is empty (nullptr) moveList.push_back({from, targetSquare}); // then it's potentially a legal move } else if(target && target->getColour() != this->getColour()) { // if it's occupied with a piece of opposite colour moveList.push_back({from, targetSquare}); // then again it's potentially legal break; } else { // otherwise it's one of our pieces break; } } } // How to check for check: // go through moveList.to, then check all directions to see if an enemy piece is threatening // If a piece of opposite colour is detected: // - Determine the piece type // - Determine the direction we are looking at (diagonal or not) // - If that piece can attack in that direction, it would be check, so remove this move from moveList // We need to add knight attack to the directions to check /* * __3_4___ * _1___2__ * ___K____ * _5___6__ * __7_8___ * */ std::vector> knightAttacks = { {-1, -2}, // 1 {-1, 2}, // 2 {-2, -1}, // 3 {-2, 1}, // 4 {1, -2}, // 5 {1, 2}, // 6 {2, -1}, // 7 {2, 1} // 8 }; directions.insert(directions.end(), knightAttacks.begin(), knightAttacks.end()); // concatenate the knight attacks for(auto &moves : moveList) { Square startSquare = moves.to; // get the potential moveto square for(auto &dir : directions) { // now go through all directions int r = startSquare.rank + dir[0]; int f = startSquare.file + dir[1]; bool diagonal = (abs(dir[0]) + abs(dir[1]) == 2) ? 1 : 0; // check if diagonal bool knight = (abs(dir[0]) + abs(dir[1]) == 3) ? 1 : 0; // check if knight attack while(r >= 0 && r < 8 && f >= 0 && f < 8) { Square targetSquare{static_cast(r), static_cast(f)}; const Piece *target = board.getPieceAt(targetSquare); // access the square we're examining if(!target) { // empty square, continue continue; } if(target && (target->getColour() != this->getColour())) { // is it occupied & an enemy? if((target->getPieceType() == QUEEN || target->getPieceType() == BISHOP) && diagonal) { // we are being attacked diagonally on this square, so remove it moves.to.rank = INVALID_RANK; moves.to.file = INVALID_FILE; } else if(target->getPieceType() == KNIGHT && knight) { // we are being attacked by a knight, so remove it moves.to.rank = INVALID_RANK; moves.to.file = INVALID_FILE; } else if(target->getPieceType() == ROOK || target->getPieceType() == QUEEN && (!diagonal && !knight)) { // again, being attacked, remove it moves.to.rank = INVALID_RANK; moves.to.file = INVALID_FILE; } if(target->getPieceType() == PAWN && (abs(r - startSquare.rank) == 1 && abs(f - startSquare.file) == 1)) { moves.to.rank = INVALID_RANK; moves.to.file = INVALID_FILE; } } if(target && (target->getColour() == this->getColour())) { // we've hit a friendly, stop break; } if(!knight) { r += dir[0]; f += dir[1]; } else break; } } } // will this actually work? moveList.erase( std::remove_if(moveList.begin(), moveList.end(), [&](const auto & x) { return !x.to.isValid(); })); return moveList; } // *INDENT-OFF* // // ## // # // # ### ## ## # ## // # # # # # # # // # # # # # ## // # # # # # # # // #### ## ## ## ## // // *INDENT-OM* std::vector Rook::getLegalMoves(const Square &from, Board &board) const { std::vector moveList; const int directions[4][2] = { {-1, 0}, // Up {1, 0}, // Down {0, -1}, // Left {0, 1} // Right }; for(auto &dir : directions) { int r = from.rank + dir[0]; int f = from.file + dir[1]; while(r >= 0 && r < 8 && f >= 0 && f < 8) { Square targetSquare{static_cast(r), static_cast(f)}; const Piece *target = board.getPieceAt(targetSquare); if(!target) { moveList.push_back({from, targetSquare}); } else if(target && target->getColour() != this->getColour()) { moveList.push_back({from, targetSquare}); break; } else break; r += dir[0]; f += dir[1]; } } return moveList; } // *INDENT-OFF* // ## # ## ## ## ## # ## // # # # # # # # # # # // # # # # ### ### # # // # # # # # # # # // ### ## # ### ### ### ## // # // ### // *INDENT-ON* std::vector Queen::getLegalMoves(const Square &from, Board &board) const { std::vector moveList; std::vector> directions = { {-1, 0}, // Up {-1, -1}, // up-left {-1, 1}, // up-right {1, 0}, // Down {1, -1}, // down-left {1, 1}, // down-right {0, -1}, // Left {0, 1} // Right }; for(auto &dir : directions) { // establish r/f for square to check int r = from.rank + dir[0]; int f = from.file + dir[1]; while(r >= 0 && r < 8 && f >= 0 && f < 8) { Square targetSquare{static_cast(r), static_cast(f)}; const Piece *target = board.getPieceAt(targetSquare);// examine the target square if(!target) { // if square is empty (NULL) moveList.push_back({from, targetSquare}); // then it's potentially a legal move } else if(target && target->getColour() != this->getColour()) { // if it's occupied with a piece of opposite colour moveList.push_back({from, targetSquare}); // then again it's potentially legal break; } else { // otherwise it's one of our pieces break; } r += dir[0]; f += dir[1]; } } return moveList; } // *INDENT-OFF* // ## # ## # // # # # // # ## # ## ## ## # ### #### // # # # # # # # # # # // ## # # # # # # # # // # # # # # # # # # # # // ## ## ### ## ##### ### ### ## ## // # // ### // *INDENT-ON* std::vector Knight::getLegalMoves(const Square &from, Board &board) const { std::vector moveList; std::vector> directions = { {-1, -2}, {-1, 2}, {-2, -1}, {-2, 1}, {1, -2}, {1, 2}, {2, -1}, {2, 1} }; return moveList; } // *INDENT-OFF* // ## # ## // # # // ### ## ### ### ## # ## // # # # # # # # # # # // # # # ## # # # # # # // # # # # # # # # # # // #### ##### ### ### ## ## ### // # // ### // *INDENT-ON* std::vector Bishop::getLegalMoves(const Square &from, Board &board) const { std::vector moveList; std::vector> directions = { {-1, -1}, // up-left {-1, 1}, // up-right {1, -1}, // down-left {1, 1}, // down-right }; return moveList; } // *INDENT-OFF* // // # ## ## # # ## # ## // # # # # # # # # // # # ### # # # # # // # # # # # # # # // ### ## # # # ### ## // # // ### // *INDENT-ON* std::vector Pawn::getLegalMoves(const Square &from, Board &board) const { std::vector moveList; const int directions[2][4][2] = { { // black {-1, 0}, // Up {-2, 0}, // 2up first move {-1, 1}, // attack to right {-1, -1} // attack to left }, { // white {1, 0}, // down {2, 0}, // 2down first move {1, 1}, // attack to right {1, -1} // attack to left } }; if(this->getColour() == PIECE_BLACK) { // go through black options for(auto &dir : directions[0]) { int r = from.rank + dir[0]; int f = from.file + dir[1]; if(r >= 0 && r < 8 && f >= 0 && f < 8) { // no need for a while loop as we only have finite moves in limited directions Square targetSquare{static_cast(r), static_cast(f)}; const Piece *target = board.getPieceAt(targetSquare); if(dir[0] == -2 && !this->checkIfMoved()) { // then 2 is potentially legal if(!target && board.isSquareEmpty(Square{static_cast(r + 1), static_cast(f)})) // check both squares for pieces of any colour moveList.push_back({from, targetSquare}); else continue; } else if(abs(dir[1]) == 1) { // attempted capture (diagonal) if(target && target->getColour() != this->getColour()) { // legal capture moveList.push_back({from, targetSquare}); } else continue; } else { // normal one square forward if(!target) moveList.push_back({from, targetSquare}); } // now check if we are on 8th rank, for promotion } } } else if(this->getColour() == PIECE_WHITE) { for(auto &dir : directions[1]) { // go through white options int r = from.rank + dir[0]; int f = from.file + dir[1]; if(r >= 0 && r < 8 && f >= 0 && f < 8) { // no need for a while loop as we only have finite moves in limited directions Square targetSquare{static_cast(r), static_cast(f)}; const Piece *target = board.getPieceAt(targetSquare); if(dir[0] == 2 && !this->checkIfMoved()) { // then 2 is potentially legal if(!target && board.isSquareEmpty(Square{static_cast(r + 1), static_cast(f)})) moveList.push_back({from, targetSquare}); else continue; } else if(abs(dir[1]) == 1) { // attempted capture (diagonal) if(target && target->getColour() != this->getColour()) { // can only move there if it's a capture // legal capture moveList.push_back({from, targetSquare}); } else continue; } else { // normal one square forward if(!target) moveList.push_back({from, targetSquare}); } // now check if we are on 8th rank, for promotion } } } return moveList; }