From 10a4dcb60b2786561f0f7113d3690105cf06011d Mon Sep 17 00:00:00 2001 From: "A.M. Rowsell" Date: Tue, 22 Sep 2026 01:58:46 -0400 Subject: [PATCH] feat: en passant generally implemented --- Board.cpp | 24 +++++++++++++++++++++++- Piece.cpp | 8 ++++++++ inc/Board.hpp | 32 ++++++++++++++++++++++---------- inc/Piece.hpp | 1 - 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/Board.cpp b/Board.cpp index d39c327..b78970c 100644 --- a/Board.cpp +++ b/Board.cpp @@ -288,7 +288,29 @@ void Board::nextTurn() { } void Board::movePiece(Square from, Square to) { - return; + Piece *movingPiece = getPieceAt(from); + if(!movingPiece) return; + + Square newEnPassantTarget = {INVALID_RANK, INVALID_FILE}; + + if(movingPiece->getPieceType() == PAWN) { + // Check if it's a 2-square move + if(abs(to.rank - from.rank) == 2) { + int dir = (to.rank > from.rank) ? -1 : 1; + newEnPassantTarget = {static_cast(to.rank + dir), from.file}; + } + // Check if it's an en passant capture (diagonal move to an empty square) + else if(to.file != from.file && isSquareEmpty(to)) { + // The captured pawn is on the same rank as 'from', but the file of 'to' + clearSquare(from.rank, to.file); + } + } + + // Move the piece + setPieceAt(to, std::move(boardGrid[from.rank][from.file])); + clearSquare(from); + + enPassantTargetSquare = newEnPassantTarget; } void Board::deserializeBoard(uint64_t incomingBoard) { diff --git a/Piece.cpp b/Piece.cpp index 6f5260e..e459ec3 100644 --- a/Piece.cpp +++ b/Piece.cpp @@ -392,6 +392,10 @@ std::vector Pawn::getLegalMoves(const Square &from, Board &board) const { if(target && target->getColour() != this->getColour()) { // legal capture moveList.push_back({from, targetSquare}); + } else if(targetSquare.rank == board.getEnPassantTargetSquare().rank && + targetSquare.file == board.getEnPassantTargetSquare().file) { + // en passant capture + moveList.push_back({from, targetSquare}); } else continue; } else { // normal one square forward @@ -419,6 +423,10 @@ std::vector Pawn::getLegalMoves(const Square &from, Board &board) const { if(target && target->getColour() != this->getColour()) { // can only move there if it's a capture // legal capture moveList.push_back({from, targetSquare}); + } else if(targetSquare.rank == board.getEnPassantTargetSquare().rank && + targetSquare.file == board.getEnPassantTargetSquare().file) { + // en passant capture + moveList.push_back({from, targetSquare}); } else continue; } else { // normal one square forward diff --git a/inc/Board.hpp b/inc/Board.hpp index a7c49d4..045049e 100644 --- a/inc/Board.hpp +++ b/inc/Board.hpp @@ -48,10 +48,12 @@ class Board { * It is a 2D vector of Piece types, or nullptr for empty squares * @image html boardGrid.svg "Logical diagram of boardGrid vector" */ Players playerTurn; + Square enPassantTargetSquare = {INVALID_RANK, INVALID_FILE}; + // let's get super object-oriented, baby // these help the getters and setters access the boardGrid // and also make them shorter and less duplicative - std::unique_ptr& at(int r, int f) { + std::unique_ptr &at(int r, int f) { return boardGrid[r][f]; } @@ -59,11 +61,11 @@ class Board { return boardGrid[r][f]; } - std::unique_ptr &at(const Square& sq) { + std::unique_ptr &at(const Square &sq) { return boardGrid[static_cast(sq.rank)][static_cast(sq.file)]; } - const std::unique_ptr &at(const Square& sq) const { + const std::unique_ptr &at(const Square &sq) const { return boardGrid[static_cast(sq.rank)][static_cast(sq.file)]; } public: @@ -73,40 +75,50 @@ class Board { // These are to allow Piece to access Board in a controlled way // instead of adding a friend declaration for every subclass // ----- Getters ----- - Piece* getPieceAt(int r, int f) { + Piece *getPieceAt(int r, int f) { return at(r, f).get(); } - const Piece* getPieceAt(int r, int f) const { + const Piece *getPieceAt(int r, int f) const { return at(r, f).get(); } - Piece* getPieceAt(const Square& sq) { + Piece *getPieceAt(const Square &sq) { return at(sq).get(); } - const Piece* getPieceAt(const Square& sq) const { + const Piece *getPieceAt(const Square &sq) const { return at(sq).get(); } + Square getEnPassantTargetSquare() const { + return enPassantTargetSquare; + } // ----- Setters ----- void setPieceAt(int r, int f, std::unique_ptr piece) { at(r, f) = std::move(piece); } - void setPieceAt(const Square& sq, std::unique_ptr piece) { + void setPieceAt(const Square &sq, std::unique_ptr piece) { at(sq) = std::move(piece); } void clearSquare(int r, int f) { at(r, f).reset(); } - void clearSquare(const Square& sq) { + void clearSquare(const Square &sq) { at(sq).reset(); } + void setEnPassantTargetSquare(Square sq) { + enPassantTargetSquare = sq; + } + void clearEnPassantTargetSquare() { + enPassantTargetSquare = {INVALID_RANK, INVALID_FILE}; + } + // ----- Utility ----- bool isSquareEmpty(int r, int f) const { return at(r, f) == nullptr; } - bool isSquareEmpty(const Square& sq) const { + bool isSquareEmpty(const Square &sq) const { return at(sq) == nullptr; } diff --git a/inc/Piece.hpp b/inc/Piece.hpp index 7d97f70..21dc240 100644 --- a/inc/Piece.hpp +++ b/inc/Piece.hpp @@ -228,7 +228,6 @@ class Pawn : public Piece { virtual std::vector getLegalMoves(const Square &from, Board &board) const override; void promote(const Square &promotionSquare, Board &board, PieceType promoteTo); protected: - bool vulnEnPassant = false; bool firstMove = true; }; #endif // PIECE_HPP