Why does the bot play with illegal moves and sometimes double moves?

#include <SFML/Graphics.hpp>
#include <array>
#include <vector>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <random>
#include <set>
#include <ranges>

constexpr int boardSize = 8;
constexpr float tileSize = 75.f;
constexpr int blackOffset = 0;
constexpr int whiteOffset = 6;
const sf::Color lightColor(245, 222, 179);
const sf::Color darkColor(139, 69, 19);

// Maximum recursion depth for checkmate check
constexpr int MAX_DEPTH = 3;

enum class PieceType { Pawn, Knight, Bishop, Rook, Queen, King };
enum class PieceColor { Black, White };

struct Piece {
    PieceType type;
    PieceColor color;
    sf::Sprite sprite;
    Piece(PieceType t, PieceColor c) : type(t), color(c) {}
};

struct GameState {
    std::vector<Piece> pieces;
    PieceColor currentTurn = PieceColor::White;
    int fiftyMoveCounter = 0;
    int moveCount = 0;
    std::vector<GameState> history;
};

bool loadTextures(std::array<sf::Texture, 12>& textures)
{
    const std::array<std::string, 12> fileNames = {
        "images/bpawn.png", "images/bknight.png", "images/bbishop.png",
        "images/brook.png", "images/bqueen.png", "images/bking.png",
        "images/wpawn.png", "images/wknight.png", "images/wbishop.png",
        "images/wrook.png", "images/wqueen.png", "images/wking.png"
    };

    for (size_t i = 0; i < textures.size(); ++i)
    {
        if (!textures[i].loadFromFile(fileNames[i]))
        {
            std::cerr << "Failed to load image: " << fileNames[i] << std::endl;
            return false;
        }
    }
    return true;
}

void drawBoard(sf::RenderWindow& window)
{
    for (int row = 0; row < boardSize; ++row)
    {
        for (int col = 0; col < boardSize; ++col)
        {
            sf::RectangleShape tile(sf::Vector2f(tileSize, tileSize));
            tile.setPosition(static_cast<float>(col) * tileSize, static_cast<float>(row) * tileSize);
            tile.setFillColor((row + col) % 2 == 0 ? lightColor : darkColor);
            window.draw(tile);
        }
    }
}

void placePieces(GameState& gameState, const std::array<sf::Texture, 12>& textures)
{
    gameState.pieces.clear();

    auto addPiece = [&](PieceType type, PieceColor color, int row, int col) {
        Piece piece(type, color);
        int textureIndex = static_cast<int>(type) + (color == PieceColor::White ? whiteOffset : blackOffset);
        piece.sprite.setTexture(textures[textureIndex]);
        piece.sprite.setPosition(static_cast<float>(col) * tileSize, static_cast<float>(row) * tileSize);
        piece.sprite.setScale(tileSize / static_cast<float>(textures[textureIndex].getSize().x),
                      tileSize / static_cast<float>(textures[textureIndex].getSize().y));
        gameState.pieces.push_back(piece);
    };

    for (int i = 0; i < 8; ++i)
    {
        addPiece(PieceType::Pawn, PieceColor::Black, 1, i);
        addPiece(PieceType::Pawn, PieceColor::White, 6, i);
    }

    const std::array<PieceType, 8> backRowOrder = {
        PieceType::Rook, PieceType::Knight, PieceType::Bishop, PieceType::Queen,
        PieceType::King, PieceType::Bishop, PieceType::Knight, PieceType::Rook
    };

    for (int i = 0; i < 8; ++i)
    {
        addPiece(backRowOrder[i], PieceColor::Black, 0, i);
        addPiece(backRowOrder[i], PieceColor::White, 7, i);
    }
}

bool isCellEmpty(int row, int col, const GameState& gameState)
{
    return std::none_of(gameState.pieces.begin(), gameState.pieces.end(),
        [row, col](const Piece& piece) {
            return static_cast<int>(piece.sprite.getPosition().x / tileSize) == col &&
                   static_cast<int>(piece.sprite.getPosition().y / tileSize) == row;
        });
}

bool isPathBlocked(int startRow, int startCol, int endRow, int endCol, const GameState& gameState)
{
    int dRow = endRow - startRow;
    int dCol = endCol - startCol;
    int steps = std::max(std::abs(dRow), std::abs(dCol));

    int rowStep = (dRow != 0) ? dRow / std::abs(dRow) : 0;
    int colStep = (dCol != 0) ? dCol / std::abs(dCol) : 0;

    for (int i = 1; i < steps; ++i)
    {
        int checkRow = startRow + i * rowStep;
        int checkCol = startCol + i * colStep;
        if (!isCellEmpty(checkRow, checkCol, gameState))
            return true;
    }
    return false;
}

bool isLegalMove(const Piece& piece, int startRow, int startCol, int endRow, int endCol, const GameState& gameState)
{
    // Check if the end position is within the board
    if (endRow < 0 || endRow >= boardSize || endCol < 0 || endCol >= boardSize)
        return false;

    int dRow = endRow - startRow;
    int dCol = endCol - startCol;

    // Check if the destination is occupied by a piece of the same color
    for (const auto& otherPiece : gameState.pieces)
    {
        if (static_cast<int>(otherPiece.sprite.getPosition().x / tileSize) == endCol &&
            static_cast<int>(otherPiece.sprite.getPosition().y / tileSize) == endRow &&
            otherPiece.color == piece.color)
            return false;
    }

    switch (piece.type)
    {
        case PieceType::Pawn:
        {
            int direction = (piece.color == PieceColor::White) ? -1 : 1;
            bool isFirstMove = (piece.color == PieceColor::White && startRow == 6) || (piece.color == PieceColor::Black && startRow == 1);

            // Move forward
            if (dCol == 0 && dRow == direction && isCellEmpty(endRow, endCol, gameState))
                return true;
            // First move can be two squares
            if (dCol == 0 && dRow == 2 * direction && isFirstMove &&
                isCellEmpty(startRow + direction, startCol, gameState) &&
                isCellEmpty(endRow, endCol, gameState))
                return true;
            // Capture diagonally
            if (std::abs(dCol) == 1 && dRow == direction)
            {
                for (const auto& otherPiece : gameState.pieces)
                {
                    if (static_cast<int>(otherPiece.sprite.getPosition().x / tileSize) == endCol &&
                        static_cast<int>(otherPiece.sprite.getPosition().y / tileSize) == endRow &&
                        otherPiece.color != piece.color)
                        return true;
                }
            }
            return false;
        }
        case PieceType::Knight:
            return (std::abs(dRow) == 2 && std::abs(dCol) == 1) || (std::abs(dRow) == 1 && std::abs(dCol) == 2);
        case PieceType::Bishop:
            if (std::abs(dRow) == std::abs(dCol))
                return !isPathBlocked(startRow, startCol, endRow, endCol, gameState);
            return false;
        case PieceType::Rook:
            if (dRow == 0 || dCol == 0)
                return !isPathBlocked(startRow, startCol, endRow, endCol, gameState);
            return false;
        case PieceType::Queen:
            if (dRow == 0 || dCol == 0 || std::abs(dRow) == std::abs(dCol))
                return !isPathBlocked(startRow, startCol, endRow, endCol, gameState);
            return false;
        case PieceType::King:
            // Normal move
            if (std::abs(dRow) <= 1 && std::abs(dCol) <= 1)
                return true;
            // Castling
            if (std::abs(dCol) == 2 && dRow == 0)
            {
                // Check if it's the king's first move
                if ((piece.color == PieceColor::White && startRow != 7) || (piece.color == PieceColor::Black && startRow != 0))
                    return false;

                // Check if the path is clear
                int rookCol = (dCol > 0) ? 7 : 0;
                return !isPathBlocked(startRow, startCol, startRow, rookCol, gameState);
            }
            return false;
        default:
            return false;
    }
}

bool isCheck(const GameState& gameState, PieceColor kingColor)
{
    int kingRow = -1, kingCol = -1;
    for (const auto& piece : gameState.pieces)
    {
        if (piece.type == PieceType::King && piece.color == kingColor)
        {
            kingRow = static_cast<int>(piece.sprite.getPosition().y / tileSize);
            kingCol = static_cast<int>(piece.sprite.getPosition().x / tileSize);
            break;
        }
    }

    if (kingRow == -1 || kingCol == -1)
        return false;

    return std::ranges::any_of(gameState.pieces, [kingRow, kingCol, kingColor, &gameState](const Piece& piece) {
        if (piece.color != kingColor)
        {
            int startRow = static_cast<int>(piece.sprite.getPosition().y / tileSize);
            int startCol = static_cast<int>(piece.sprite.getPosition().x / tileSize);
            return isLegalMove(piece, startRow, startCol, kingRow, kingCol, gameState);
        }
        return false;
    });
}

bool isCheckmate(GameState& gameState, PieceColor kingColor, int depth = 0)
{
    if (depth > MAX_DEPTH) {
        return false;
    }

    if (!isCheck(gameState, kingColor))
        return false;

    for (auto& piece : gameState.pieces)
    {
        if (piece.color == kingColor)
        {
            int startRow = static_cast<int>(piece.sprite.getPosition().y / tileSize);
            int startCol = static_cast<int>(piece.sprite.getPosition().x / tileSize);

            for (int row = 0; row < boardSize; ++row)
            {
                for (int col = 0; col < boardSize; ++col)
                {
                    GameState tempState = gameState;
                    if (isLegalMove(piece, startRow, startCol, row, col, tempState))
                    {
                        Piece* tempPiece = nullptr;
                        for (auto& p : tempState.pieces)
                        {
                            if (p.sprite.getPosition() == piece.sprite.getPosition())
                            {
                                tempPiece = &p;
                                break;
                            }
                        }
                        if (tempPiece) {
                            tempPiece->sprite.setPosition(static_cast<float>(col) * tileSize, static_cast<float>(row) * tileSize);
                            if (!isCheck(tempState, kingColor))
                                return false;
                        }
                    }
                }
            }
        }
    }
    return true;
}

bool isStalemate(GameState& gameState, PieceColor playerColor) {
    // Check if the player cannot make any moves
    for (auto& piece : gameState.pieces) {
        if (piece.color == playerColor) {
            int startRow = static_cast<int>(piece.sprite.getPosition().y / tileSize);
            int startCol = static_cast<int>(piece.sprite.getPosition().x / tileSize);

            for (int endRow = 0; endRow < boardSize; ++endRow) {
                for (int endCol = 0; endCol < boardSize; ++endCol) {
                    if (isLegalMove(piece, startRow, startCol, endRow, endCol, gameState)) {
                        return false; // Found at least one legal move
                    }
                }
            }
        }
    }

    // If no legal moves, but the king is not in check, it's a stalemate
    return !isCheck(gameState, playerColor);
}

void validateBoardState(GameState& gameState) {
    for (const auto& piece : gameState.pieces) {
        int row = static_cast<int>(piece.sprite.getPosition().y / tileSize);
        int col = static_cast<int>(piece.sprite.getPosition().x / tileSize);
        if (row < 0 || row >= boardSize || col < 0 || col >= boardSize) {
            std::cout << "Error: Piece out of bound" << std::endl;
            // Maybe revert the game to the previous state or fix the position
        }
    }

    // Additional check for duplicate positions
    std::set<std::pair<int, int>> positions;
    for (const auto& piece : gameState.pieces) {
        int row = static_cast<int>(piece.sprite.getPosition().y / tileSize);
        int col = static_cast<int>(piece.sprite.getPosition().x / tileSize);
        auto [it, inserted] = positions.insert({row, col});
        if (!inserted) {
            std::cout << "ErrOr: Multiple pieces at the same position" << std::endl;
            // Maybe fix this situation
        }
    }
}

std::string coordsToNotation(int col, int row) {
    char letter = 'a' + col;
    char number = '1' + row;
    return std::string(1, letter) + std::string(1, number);
}

void makeBotMove(GameState& gameState) {
    if (gameState.currentTurn != PieceColor::Black) {
        std::cout << "It's not Black's turn" << std::endl;
        return;
    }

    std::vector<std::tuple<Piece*, sf::Vector2i, sf::Vector2i>> legalMoves;

    // Find all legal moves for the bot
    for (auto& piece : gameState.pieces) {
        if (piece.color == PieceColor::Black) {
            int startRow = static_cast<int>(piece.sprite.getPosition().y / tileSize);
            int startCol = static_cast<int>(piece.sprite.getPosition().x / tileSize);

            for (int endRow = 0; endRow < boardSize; ++endRow) {
                for (int endCol = 0; endCol < boardSize; ++endCol) {
                    if (isLegalMove(piece, startRow, startCol, endRow, endCol, gameState)) {
                        legalMoves.push_back(std::make_tuple(&piece, sf::Vector2i(startCol, startRow), sf::Vector2i(endCol, endRow)));
                    }
                }
            }
        }
    }

    if (!legalMoves.empty()) {
        // Randomly select a legal move
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_int_distribution<> distrib(0, legalMoves.size() - 1);
        auto [selectedPiece, startPos, endPos] = legalMoves[distrib(gen)];

        // Save the game state before the move
        GameState previousState = gameState;

        // Remove the captured piece (if any)
        auto it = std::remove_if(gameState.pieces.begin(), gameState.pieces.end(),
            [endPos](const Piece& piece) {
                return static_cast<int>(piece.sprite.getPosition().x / tileSize) == endPos.x &&
                       static_cast<int>(piece.sprite.getPosition().y / tileSize) == endPos.y &&
                       piece.color != PieceColor::Black;
            });
        gameState.pieces.erase(it, gameState.pieces.end());

        // Move the selected piece
        selectedPiece->sprite.setPosition(static_cast<float>(endPos.x) * tileSize, static_cast<float>(endPos.y) * tileSize);

        // Print the move
        std::cout << "Black: " << coordsToNotation(startPos.x, startPos.y)
                  << " " << coordsToNotation(endPos.x, endPos.y) << std::endl;

        // Check if the move puts the bot's king in check
        if (isCheck(gameState, PieceColor::Black)) {
            // Revert the move
            gameState = previousState;
            std::cout << "Bot made an illegal move. Reverting." << std::endl;
            makeBotMove(gameState);  // Try again
        } else {
            // Change turn and check for game end conditions
            gameState.currentTurn = PieceColor::White;
            if (isCheckmate(gameState, PieceColor::White)) {
                std::cout << "Checkmate. Black wins." << std::endl;
            } else if (isStalemate(gameState, PieceColor::White)) {
                std::cout << "Stalemate." << std::endl;
            }
        }
    } else {
        std::cout << "No legal moves for Black." << std::endl;
    }
    validateBoardState(gameState);
}

void handleClick(float mouseX, float mouseY, GameState& gameState, bool& selecting,
                 sf::Vector2i& startPos, Piece*& selectedPiece) {
    int col = static_cast<int>(mouseX / tileSize);
    int row = static_cast<int>(mouseY / tileSize);

    if (gameState.currentTurn == PieceColor::White) {
        if (selecting && selectedPiece) {
            int startRow = static_cast<int>(selectedPiece->sprite.getPosition().y / tileSize);
            int startCol = static_cast<int>(selectedPiece->sprite.getPosition().x / tileSize);

            if (isLegalMove(*selectedPiece, startRow, startCol, row, col, gameState)) {
                // Save the game state before the move
                GameState previousState = gameState;

                // Remove the captured piece (if any)
                auto it = std::remove_if(gameState.pieces.begin(), gameState.pieces.end(),
                    [row, col](const Piece& piece) {
                        return static_cast<int>(piece.sprite.getPosition().x / tileSize) == col &&
                               static_cast<int>(piece.sprite.getPosition().y / tileSize) == row &&
                               piece.color != PieceColor::White;
                    });
                gameState.pieces.erase(it, gameState.pieces.end());

                // Move the selected piece
                selectedPiece->sprite.setPosition(static_cast<float>(col) * tileSize, static_cast<float>(row) * tileSize);

                // Print the move
                std::cout << "White: " << coordsToNotation(startCol, startRow)
                          << " " << coordsToNotation(col, row) << std::endl;

                // Check if the move puts the player's king in check
                if (isCheck(gameState, PieceColor::White)) {
                    // Revert the move
                    gameState = previousState;
                    std::cout << "Illegal move: King would be in check." << std::endl;
                } else {
                    // Change turn and check for game end conditions
                    gameState.currentTurn = PieceColor::Black;
                    if (isCheckmate(gameState, PieceColor::Black)) {
                        std::cout << "Checkmate. White wins." << std::endl;
                    } else if (isStalemate(gameState, PieceColor::Black)) {
                        std::cout << "Stalemate." << std::endl;
                    } else {
                        // Bot's move
                        makeBotMove(gameState);
                    }
                }
            }
            selecting = false;
            selectedPiece = nullptr;
        } else {
            // Select a piece
            for (auto& piece : gameState.pieces) {
                sf::FloatRect bounds = piece.sprite.getGlobalBounds();
                if (bounds.contains(mouseX, mouseY) && piece.color == PieceColor::White) {
                    selectedPiece = &piece;
                    startPos = {col, row};
                    selecting = true;
                    break;
                }
            }
        }
    }
    validateBoardState(gameState);
}

int main()
{
    sf::RenderWindow window(sf::VideoMode(boardSize * tileSize, boardSize * tileSize), "Chess");

    std::array<sf::Texture, 12> textures;
    if (!loadTextures(textures))
    {
        return 1;
    }

    GameState gameState;
    placePieces(gameState, textures);

    bool selecting = false;
    sf::Vector2i startPos;
    Piece* selectedPiece = nullptr;

    while (window.isOpen())
    {
        sf::Event event; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
            else if (event.type == sf::Event::MouseButtonPressed)
            {
                if (event.mouseButton.button == sf::Mouse::Left)
                {
                    handleClick(static_cast<float>(event.mouseButton.x),
                               static_cast<float>(event.mouseButton.y),
                               gameState, selecting, startPos, selectedPiece);
                }
            }
        }

        window.clear();
        drawBoard(window);
        for (const auto& piece : gameState.pieces)
        {
            window.draw(piece.sprite);
        }
        window.display();
    }

    return 0;
}

I’m experiencing two main issues with my chess game, which is developed using C++ and SFML:

Bot Making Illegal Moves:

Description: After the player captures one of the bot’s pieces, I observe that the bot occasionally makes moves that are not legal according to the rules of chess. This issue appears to happen specifically after a piece is removed from the board. It seems like the bot sometimes makes moves that would normally be disallowed, such as moving to squares that are occupied or making moves that do not comply with the piece’s movement rules.
Player Piece Not Moving to Captured Bot’s Position:

Description: When a bot’s piece is captured by the player, the player’s piece is not positioned correctly on the square where the bot’s piece was. Instead of the player’s piece moving to the captured piece’s position, the piece either stays in its original location or moves incorrectly. This issue occurs only after a bot’s piece is captured, leading to discrepancies in the game state and potentially affecting subsequent moves.
These problems suggest that there might be issues with how the game state is updated and validated, particularly after a capture. The bot’s move generation and validation logic may also be flawed, allowing illegal moves to be executed.

  1. I added debug statements to track the positions of pieces before and after moves, especially after a capture

  2. I reviewed the logic within the makeBotMove function to ensure it does not allow the bot to make moves that would place its own king in check.

  3. I tested the bot’s behavior with different scenarios to ensure it adheres to the rules, but this doesn’t help.

  4. I expected that after capturing a bot’s piece, the bot should only make moves that are legal and conform to the rules of chess. The bot’s moves should not place its own king in check, and it should not move to occupied squares or violate movement rules.

  5. I expected that after capturing a bot’s piece, the player’s piece should move to the exact position where the captured piece was located.

New contributor

Назар Клецький is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật