From 64efd86e0a5e5a6b1559c676b13631bfacfa4b6e Mon Sep 17 00:00:00 2001 From: spiroskou Date: Mon, 2 Sep 2024 19:17:43 +0300 Subject: [PATCH] Major refactoring and introduction of Enscripten Loop for web --- Board/Board.cpp | 227 +++++++++++++++++++--------------------------- Board/Board.h | 31 ++++++- CMakeLists.txt | 14 +-- ChessSDL.cpp | 204 ++++++++++++++++++++++++----------------- ChessSDL.h | 11 +-- Config/Config.cpp | 16 ---- Config/Config.h | 9 -- Pieces/Bishop.cpp | 4 +- Pieces/King.cpp | 4 +- Pieces/Knight.cpp | 4 +- Pieces/Pawn.cpp | 36 +++++++- Pieces/Pawn.h | 1 + Pieces/Piece.cpp | 1 + Pieces/Piece.h | 18 +--- Pieces/Queen.cpp | 4 +- Pieces/Rook.cpp | 4 +- main.cpp | 16 +++- 17 files changed, 313 insertions(+), 291 deletions(-) delete mode 100644 Config/Config.cpp delete mode 100644 Config/Config.h diff --git a/Board/Board.cpp b/Board/Board.cpp index f46b782..515b1bd 100644 --- a/Board/Board.cpp +++ b/Board/Board.cpp @@ -3,7 +3,6 @@ #include "Piece.h" #include #include -#include "SDL.h" #undef min #undef max @@ -23,14 +22,14 @@ void Board::initializePieceRow(int row, PieceColor color) { } void Board::initializePawnRow(int row, PieceColor color) { - for (int col = 0; col < 8; ++col) { + for (int col = 0; col < COLS; ++col) { m_layout[row][col] = std::make_shared(color); } } void Board::initializeEmptyRows() { for (int row = 2; row <= 5; ++row) { - for (int col = 0; col < 8; ++col) { + for (int col = 0; col < COLS; ++col) { m_layout[row][col] = nullptr; } } @@ -59,33 +58,11 @@ int getTurnCounter() return turn_counter; } -void IncrementTurnCounter() -{ - turn_counter++; -} - std::shared_ptr getBoard() { return board; } -std::shared_ptr Board::replace(int src_row, int src_col, int trg_row, int trg_col) -{ - std::shared_ptr src_piece = getPiece(src_row, src_col); - std::shared_ptr dest_piece = getPiece(trg_row, trg_col); - setPiece(trg_row, trg_col, src_piece); - setPiece(src_row, src_col, nullptr); - - return dest_piece; -} - -void Board::restore(int src_row, int src_col, int trg_row, int trg_col, std::shared_ptr tmp_piece) -{ - std::shared_ptr dest_piece = getPiece(trg_row, trg_col); - setPiece(trg_row, trg_col, tmp_piece); - setPiece(src_row, src_col, dest_piece); -} - Move Board::getLastMove() const { if (moveHistory.empty()) { @@ -94,54 +71,26 @@ Move Board::getLastMove() const return moveHistory.back(); } -static bool isDoubleStep(Move& move) -{ - int rowDiff = abs(move.dest_row - move.src_row); - if (rowDiff == 2) { - return true; - } - return false; -} - -bool Board::isEnPassant(int src_row, int src_col, int trg_row, int trg_col) const -{ - auto pawn = getPiece(src_row, src_col); - if (pawn->getType() != PieceType::Pawn) { - return false; - } - - // Check if the move is a valid en passant capture - int direction = (pawn->getColor() == PieceColor::White) ? 1 : -1; - if (abs(trg_col - src_col) == 1 && trg_row == src_row + direction) { - auto target_pawn = getPiece(src_row, trg_col); - if (target_pawn && target_pawn->getType() == PieceType::Pawn && target_pawn->getColor() != pawn->getColor()) { - // Ensure the target pawn just moved two squares in the last turn - Move move = getLastMove(); - if (isDoubleStep(move)) { - return true; - } - } - } - - return false; -} - void Board::removePiece(int row, int col) { m_layout[row][col] = nullptr; } -void Board::performEnPassant(int src_row, int src_col, int trg_row, int trg_col) +std::shared_ptr Board::replace(int src_row, int src_col, int trg_row, int trg_col) { - auto pawn = getPiece(src_row, src_col); - int direction = (pawn->getColor() == PieceColor::White) ? 1 : -1; + std::shared_ptr src_piece = getPiece(src_row, src_col); + std::shared_ptr dest_piece = getPiece(trg_row, trg_col); + setPiece(trg_row, trg_col, src_piece); + setPiece(src_row, src_col, nullptr); - // Capture the target pawn - auto target_pawn = getPiece(src_row, trg_col); - removePiece(src_row, trg_col); + return dest_piece; +} - // Move the capturing pawn to the target square - replace(src_row, src_col, trg_row, trg_col); +void Board::restore(int src_row, int src_col, int trg_row, int trg_col, std::shared_ptr tmp_piece) +{ + std::shared_ptr dest_piece = getPiece(trg_row, trg_col); + setPiece(trg_row, trg_col, tmp_piece); + setPiece(src_row, src_col, dest_piece); } bool Board::isSquareAttacked(int row, int col, PieceColor color) const @@ -149,8 +98,8 @@ bool Board::isSquareAttacked(int row, int col, PieceColor color) const PieceColor opponentColor = getOpponentColor(); // Iterate over all squares on the board - for (int i = 0; i < 8; ++i) { - for (int j = 0; j < 8; ++j) { + for (int i = 0; i < ROWS; ++i) { + for (int j = 0; j < COLS; ++j) { auto piece = getPiece(i, j); // If the piece is null or not an opponent's piece, continue @@ -178,8 +127,8 @@ bool Board::isSquareAttacked(int row, int col, PieceColor color) const std::shared_ptr Board::getKing(PieceColor color, int &king_row, int& king_col) const { - for (int row = 0; row < 8; row++) { - for (int col = 0; col < 8; col++) { + for (int row = 0; row < ROWS; row++) { + for (int col = 0; col < COLS; col++) { std::shared_ptr piece = m_layout[row][col]; if (!piece) continue; @@ -206,8 +155,8 @@ bool Board::isKingInCheck(PieceColor color) const // Check if any opponent's pieces threaten the king - for (int row = 0; row < 8; row++) { - for (int col = 0; col < 8; col++) { + for (int row = 0; row < ROWS; row++) { + for (int col = 0; col < COLS; col++) { std::shared_ptr piece = getPiece(row,col); if (!piece) continue; @@ -256,7 +205,7 @@ bool Board::isCheckmate() // Check if the king has any legal moves to escape check for (int row = king_row - 1; row <= king_row + 1; ++row) { for (int col = king_col - 1; col <= king_col + 1; ++col) { - if (row >= 0 && row < 8 && col >= 0 && col < 8 && !(row == king_row && col == king_col)) { + if (row >= 0 && row < ROWS && col >= 0 && col < COLS && !(row == king_row && col == king_col)) { std::shared_ptr piece = getPiece(row, col); if (piece && piece->getColor() == opp_color) continue; int dum_king_row = -1, dum_king_col = -1; @@ -277,16 +226,16 @@ bool Board::isCheckmate() } // Check if any piece can block the check or capture the attacking piece - for (int row = 0; row < 8; ++row) { - for (int col = 0; col < 8; ++col) { + for (int row = 0; row < ROWS; ++row) { + for (int col = 0; col < COLS; ++col) { if (row == king_row && col == king_col) continue; std::shared_ptr piece = getPiece(row, col); if (!piece) continue; if (piece->getColor() == opp_color) { // Check all possible moves for this piece - for (int target_row = 0; target_row < 8; ++target_row) { - for (int target_col = 0; target_col < 8; ++target_col) { + for (int target_row = 0; target_row < ROWS; ++target_row) { + for (int target_col = 0; target_col < COLS; ++target_col) { if (piece->isValidMove(row, col, target_row, target_col)) { std::shared_ptr tmp_piece = replace(row, col, target_row, target_col); bool still_in_check = isKingInCheck(opp_color); @@ -320,13 +269,13 @@ bool Board::isStalemate() } // Iterate through all pieces of the current player - for (int row = 0; row < 8; ++row) { - for (int col = 0; col < 8; ++col) { + for (int row = 0; row < ROWS; ++row) { + for (int col = 0; col < COLS; ++col) { std::shared_ptr piece = getPiece(row, col); if (piece && piece->getColor() == getCurrentPlayerColor()) { // Check all possible moves for this piece - for (int destRow = 0; destRow < 8; ++destRow) { - for (int destCol = 0; destCol < 8; ++destCol) { + for (int destRow = 0; destRow < ROWS; ++destRow) { + for (int destCol = 0; destCol < COLS; ++destCol) { if (piece->isValidMove(row, col, destRow, destCol)) { // Simulate the move std::shared_ptr tmpPiece = replace(row, col, destRow, destCol); @@ -348,6 +297,19 @@ bool Board::isStalemate() return true; } +void Board::performEnPassant(int src_row, int src_col, int trg_row, int trg_col) +{ + auto pawn = getPiece(src_row, src_col); + int direction = (pawn->getColor() == PieceColor::White) ? 1 : -1; + + // Capture the target pawn + auto target_pawn = getPiece(src_row, trg_col); + removePiece(src_row, trg_col); + + // Move the capturing pawn to the target square + replace(src_row, src_col, trg_row, trg_col); +} + void Board::performCastling(int src_row, int src_col, int trg_row, int trg_col) { // Perform the castling move @@ -361,82 +323,83 @@ void Board::performCastling(int src_row, int src_col, int trg_row, int trg_col) replace(src_row, rook_col, trg_row, new_rook_col); // Move the rook } -MoveResult Board::move(int src_row, int src_col, int trg_row, int trg_col) +void Board::setMove(const Move &move) { - auto src_piece = getPiece(src_row, src_col); + moveHistory.push_back(move); +} + +static void IncrementTurnCounter() +{ + turn_counter++; +} + +MoveResult Board::evaluateGameState(const Move &move) +{ + if (isKingInCheck(getCurrentPlayerColor())) { + restore(move.src_row, move.src_col, move.dest_row, move.dest_col, move.captured_piece); + return MoveResult::KingInCheck; + } else if (isCheckmate()) { + return MoveResult::Checkmate; + } else if (isStalemate()) { + return MoveResult::Stalemate; + } else if (checkForPromotion(move.dest_row, move.dest_col)) { + if (isCheckmate()) { + return MoveResult::Checkmate; + } else if (isStalemate()) { + return MoveResult::Stalemate; + } + } + + IncrementTurnCounter(); + setMove(move); + move.src_piece->setMoved(true); + return MoveResult::ValidMove; +} + +MoveResult Board::move(Move &move) +{ + move.src_piece = getPiece(move.src_row, move.src_col); // Check if the player chose a piece - if (!src_piece) { + if (!move.src_piece) { return MoveResult::InvalidPiece; } // Check if the player chose opponent's piece PieceColor opp_color = getOpponentColor(); - if (src_piece->getColor() == opp_color) { + if (move.src_piece->getColor() == opp_color) { return MoveResult::OpponentPiece; } // Handle castling move - if (src_piece->getType() == PieceType::King) { - std::shared_ptr king = std::static_pointer_cast (src_piece); - if (king->canCastle(src_row, src_col, trg_row, trg_col)) { - performCastling(src_row, src_col, trg_row, trg_col); + if (move.src_piece->getType() == PieceType::King) { + std::shared_ptr king = std::static_pointer_cast (move.src_piece); + if (king->canCastle(move.src_row, move.src_col, move.dest_row, move.dest_col)) { + performCastling(move.src_row, move.src_col, move.dest_row, move.dest_col); king->setMoved(true); - Move move{ src_row, src_col, trg_row, trg_col }; - moveHistory.push_back(move); return MoveResult::ValidMove; } } // Handle EnPassant move - if (src_piece->getType() == PieceType::Pawn) { - if (isEnPassant(src_row, src_col, trg_row, trg_col)) { - performEnPassant(src_row, src_col, trg_row, trg_col); - src_piece->setMoved(true); - Move move{ src_row, src_col, trg_row, trg_col }; - moveHistory.push_back(move); + if (move.src_piece->getType() == PieceType::Pawn) { + std::shared_ptr pawn = std::static_pointer_cast (move.src_piece); + if (pawn->isEnPassant(move.src_row, move.src_col, move.dest_row, move.dest_col)) { + performEnPassant(move.src_row, move.src_col, move.dest_row, move.dest_col); + pawn->setMoved(true); return MoveResult::ValidMove; } } // Check if the player chose a valid move for the corresponding Piece - if (!getPiece(src_row, src_col)->isValidMove(src_row, src_col, trg_row, trg_col)) { + if (!move.src_piece->isValidMove(move.src_row, move.src_col, move.dest_row, move.dest_col)) { return MoveResult::InvalidMove; } - std::shared_ptr tmp_piece = replace(src_row, src_col, trg_row, trg_col); - - if (isKingInCheck(getCurrentPlayerColor())) { - restore(src_row, src_col, trg_row, trg_col, tmp_piece); - return MoveResult::KingInCheck; - } - - if (isCheckmate()) { - return MoveResult::Checkmate; - } - - if (isStalemate()) { - return MoveResult::Stalemate; - } - - if (checkForPromotion(trg_row, trg_col)) { - if (isCheckmate()) { - return MoveResult::Checkmate; - } - } - - src_piece->setMoved(true); - Move move{ src_row, src_col, trg_row, trg_col }; - moveHistory.push_back(move); - + move.captured_piece = replace(move.src_row, move.src_col, move.dest_row, move.dest_col); return MoveResult::ValidMove; } -MoveResult makeTheMove(int src_row, int src_col, int trg_row, int trg_col) -{ - return board->move(src_row, src_col, trg_row, trg_col); -} - void Board::makeMove(const Move& move) { moveHistory.push_back(move); @@ -445,7 +408,6 @@ void Board::makeMove(const Move& move) void Board::undoMove(const Move& move, std::shared_ptr capturedPiece) { - // Revert the move restore(move.src_row, move.src_col, move.dest_row, move.dest_col, capturedPiece); moveHistory.pop_back(); } @@ -453,8 +415,8 @@ void Board::undoMove(const Move& move, std::shared_ptr capturedPiece) std::vector Board::getPossibleMoves(PieceColor color) const { std::vector moves; - for (int row = 0; row < 8; ++row) { - for (int col = 0; col < 8; ++col) { + for (int row = 0; row < ROWS; ++row) { + for (int col = 0; col < COLS; ++col) { auto piece = getPiece(row, col); if (piece && piece->getColor() == color) { @@ -467,10 +429,9 @@ std::vector Board::getPossibleMoves(PieceColor color) const int Board::evaluate() const { - // A simple evaluation function: positive for White, negative for Black int score = 0; - for (int row = 0; row < 8; ++row) { - for (int col = 0; col < 8; ++col) { + for (int row = 0; row < ROWS; ++row) { + for (int col = 0; col < COLS; ++col) { auto piece = getPiece(row, col); if (piece) { if (piece->getColor() == getCurrentPlayerColor()) { diff --git a/Board/Board.h b/Board/Board.h index 2011c47..e17b17f 100644 --- a/Board/Board.h +++ b/Board/Board.h @@ -10,10 +10,32 @@ #include "Queen.h" #include "King.h" +enum class MoveResult { + InvalidPiece = 0, + OpponentPiece, + InvalidMove, + KingInCheck, + Checkmate, + Stalemate, + ValidMove +}; + +struct Move { + int src_row, src_col; + int dest_row, dest_col; + std::shared_ptr src_piece, captured_piece; +}; + +constexpr int ROWS = 8; +constexpr int COLS = 8; +constexpr int SCREEN_WIDTH = 640; +constexpr int SCREEN_HEIGHT = 640; +constexpr int TILE_SIZE = SCREEN_WIDTH / COLS; + class Board { private: - std::array, 8>, 8> m_layout; + std::array, COLS>, ROWS> m_layout; std::vector moveHistory; void initializePieceRow(int row, PieceColor color); @@ -29,7 +51,7 @@ class Board moveHistory.clear(); } - MoveResult move(int src_row, int src_col, int trg_row, int trg_col); + MoveResult move(Move &move); std::shared_ptr replace(int src_row, int src_col, int trg_row, int trg_col); void restore(int src_row, int src_col, int trg_row, int trg_col, std::shared_ptr tmp_piece); std::shared_ptr getPiece(int row, int col) const { return m_layout[row][col]; }; @@ -43,17 +65,16 @@ class Board void performEnPassant(int src_row, int src_col, int trg_row, int trg_col); void removePiece(int row, int col); Move getLastMove() const; - bool isEnPassant(int src_row, int src_col, int trg_row, int trg_col) const; bool isStalemate(); void makeMove(const Move& move); void undoMove(const Move& move, std::shared_ptr capturedPiece); std::vector getPossibleMoves(PieceColor color) const; int evaluate() const; + void setMove(const Move &move); + MoveResult evaluateGameState(const Move& move); }; std::shared_ptr getBoard(); -void IncrementTurnCounter(); int getTurnCounter(); -MoveResult makeTheMove(int src_row, int src_col, int trg_row, int trg_col); Move findBestMove(int depth); PieceColor getCurrentPlayerColor(); diff --git a/CMakeLists.txt b/CMakeLists.txt index 5589b31..8b2ff2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,10 +11,10 @@ find_package(SDL2_image REQUIRED) # Print the variables to see their values include_directories(${SDL2_INCLUDE_DIRS} ${SDL2_IMAGE_INCLUDE_DIRS}) -include_directories(Board Pieces Config) +include_directories(Board Pieces) # Add source to this project's executable. -add_executable (OpenChess "main.cpp" "Pieces/Piece.h" "Pieces/King.h" "Pieces/King.cpp" "Pieces/Rook.h" "Pieces/Rook.cpp" "Pieces/Queen.h" "Pieces/Queen.cpp" "Pieces/Pawn.h" "Pieces/Pawn.cpp" "Pieces/Bishop.h" "Pieces/Bishop.cpp" "Pieces/Knight.h" "Pieces/Knight.cpp" "Board/Board.cpp" "Board/Board.h" "ChessSDL.cpp" "ChessSDL.h" "Pieces/Piece.cpp" "Config/Config.cpp" "Config/Config.h" ) +add_executable (OpenChess "main.cpp" "Pieces/Piece.h" "Pieces/King.h" "Pieces/King.cpp" "Pieces/Rook.h" "Pieces/Rook.cpp" "Pieces/Queen.h" "Pieces/Queen.cpp" "Pieces/Pawn.h" "Pieces/Pawn.cpp" "Pieces/Bishop.h" "Pieces/Bishop.cpp" "Pieces/Knight.h" "Pieces/Knight.cpp" "Board/Board.cpp" "Board/Board.h" "ChessSDL.cpp" "ChessSDL.h" "Pieces/Piece.cpp" ) target_link_libraries(OpenChess SDL2::SDL2 SDL2::SDL2main SDL2_image::SDL2_image) @@ -22,10 +22,10 @@ if (CMAKE_VERSION VERSION_GREATER 3.12) set_property(TARGET OpenChess PROPERTY CXX_STANDARD 20) endif() -if(WIN32) - set_target_properties(OpenChess PROPERTIES - WIN32_EXECUTABLE TRUE - ) -endif() +#if(WIN32) + # set_target_properties(OpenChess PROPERTIES + # WIN32_EXECUTABLE TRUE + # ) +#endif() # TODO: Add tests and install targets if needed. diff --git a/ChessSDL.cpp b/ChessSDL.cpp index 792eac1..e7d1ba5 100644 --- a/ChessSDL.cpp +++ b/ChessSDL.cpp @@ -1,3 +1,6 @@ +#ifdef __EMSCRIPTEN__ +#include +#endif #include #include #include @@ -8,12 +11,20 @@ #include "ChessSDL.h" #include "Board.h" -#include "Config.h" + +constexpr int depth = 1; static std::map textures; static SDL_Renderer* renderer; static SDL_Window* window; +static bool QUIT = false; + +bool ChessSDL_NeedToQuit() +{ + return QUIT; +} + static SDL_Texture* getTexture(std::string imagePath) { return textures[imagePath]; @@ -79,18 +90,18 @@ static SDL_Renderer* create_SDL_Renderer(SDL_Window *window) static bool loadMedia(SDL_Renderer* renderer) { // Load images for all piece types std::vector Files = { - "images/white-pawn.png", - "images/black-pawn.png", - "images/white-bishop.png", - "images/black-bishop.png", - "images/white-rook.png", - "images/black-rook.png", - "images/white-king.png", - "images/black-king.png", - "images/white-knight.png", - "images/black-knight.png", - "images/white-queen.png", - "images/black-queen.png" + "../../../images/white-pawn.png", + "../../../images/black-pawn.png", + "../../../images/white-bishop.png", + "../../../images/black-bishop.png", + "../../../images/white-rook.png", + "../../../images/black-rook.png", + "../../../images/white-king.png", + "../../../images/black-king.png", + "../../../images/white-knight.png", + "../../../images/black-knight.png", + "../../../images/white-queen.png", + "../../../images/black-queen.png" }; for (const auto& file : Files) { SDL_Surface* loadedSurface = IMG_Load(file.c_str()); @@ -155,14 +166,6 @@ static void ChessSDL_HighlightSelectedTile(SDL_Renderer *renderer, int selectedR ChessSDL_RenderPiece(selectedRow, selectedCol); } -static void ChessSDL_HighlightSelection(int selectedRow, int selectedCol, bool revert) -{ - SDL_Renderer* renderer = getRenderer(); - - ChessSDL_HighlightSelectedTile(renderer, selectedRow, selectedCol, revert); - SDL_RenderPresent(renderer); -} - static void ChessSDL_RenderChessBoard() { SDL_Renderer* renderer = getRenderer(); @@ -193,6 +196,15 @@ static void ChessSDL_RenderChessBoard() SDL_RenderPresent(renderer); } +void ChessSDL_HighlightSelection(int selectedRow, int selectedCol, bool revert) +{ + ChessSDL_RenderChessBoard(); + + SDL_Renderer* renderer = getRenderer(); + ChessSDL_HighlightSelectedTile(renderer, selectedRow, selectedCol, revert); + SDL_RenderPresent(renderer); +} + static void ChessSDL_HighlightLastMove() { ChessSDL_RenderChessBoard(); @@ -218,7 +230,7 @@ int ChessSDL_MakePreparations() return 1; } - window = create_SDL_Window("ChessGame"); + window = create_SDL_Window("OpenChess"); if (!window) { return 1; } @@ -270,7 +282,7 @@ static void showKingInCheckMessage() SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Warning", message.c_str(), window); } -void ChessSDL_ShowMoveMessage(MoveResult res) +static void ChessSDL_ShowMoveMessage(MoveResult res) { switch (res) { @@ -298,82 +310,106 @@ void ChessSDL_ShowMoveMessage(MoveResult res) } } -static void ChessSDL_HandleMoveResult(const MoveResult& res, bool &quit, int row = -1, int col = -1) +static bool ChessSDL_HandleMoveResult(const MoveResult& res, const Move& move) { - if (res == MoveResult::Checkmate || res == MoveResult::Stalemate) { + bool quit = false; + + if (res == MoveResult::InvalidMove || res == MoveResult::KingInCheck) { + ChessSDL_HighlightSelection(move.src_row, move.src_col, true); + } else if (res == MoveResult::Checkmate || res == MoveResult::Stalemate) { ChessSDL_HighlightLastMove(); - ChessSDL_ShowMoveMessage(res); quit = true; - } - if (res == MoveResult::ValidMove) { + } else if (res == MoveResult::ValidMove) { ChessSDL_HighlightLastMove(); - IncrementTurnCounter(); } - if (res == MoveResult::InvalidMove) { - ChessSDL_HighlightSelection(row, col, true); - } + ChessSDL_ShowMoveMessage(res); + return quit; } -void ChessSDL_HandleGameLoop() +static bool ChessSDL_MakeTheMove(Move &move) { - const Config& config = getConfigurations(); - bool quit = false; - SDL_Event e; std::shared_ptr board = getBoard(); - static bool isPieceSelected = false; - static int selectedRow = -1; - static int selectedCol = -1; - - while (!quit) { - while (SDL_PollEvent(&e) != 0) { - if (e.type == SDL_QUIT) { - quit = true; - } else if (e.type == SDL_MOUSEBUTTONDOWN) { - int x, y; - SDL_GetMouseState(&x, &y); - int row = y / TILE_SIZE; - int col = x / TILE_SIZE; - - if (!isPieceSelected) { - auto selectedPiece = board->getPiece(row, col); - if (selectedPiece && selectedPiece->getColor() == getCurrentPlayerColor()) { - isPieceSelected = true; - selectedRow = row; - selectedCol = col; - - // Highlight the selected piece - ChessSDL_HighlightSelection(selectedRow, selectedCol, false); - } - } else { - int src_row = selectedRow; - int src_col = selectedCol; - int dest_row = row; - int dest_col = col; - isPieceSelected = false; + MoveResult res = board->move(move); - MoveResult res = makeTheMove(src_row, src_col, dest_row, dest_col); - ChessSDL_HandleMoveResult(res, quit, selectedRow, selectedCol); + if (res == MoveResult::ValidMove) { + res = board->evaluateGameState(move); + } - if (quit == true) break; - } - } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) { - if (isPieceSelected) { - ChessSDL_HighlightSelection(selectedRow, selectedCol, true); - isPieceSelected = false; - selectedRow = -1; - selectedCol = -1; + return ChessSDL_HandleMoveResult(res, move); +} + +static int handleFirstClick(Move &move, int row, int col, bool &isPieceSelected) +{ + std::shared_ptr board = getBoard(); + + if (!isPieceSelected) { + auto selectedPiece = board->getPiece(row, col); + if (selectedPiece && selectedPiece->getColor() == getCurrentPlayerColor()) { + isPieceSelected = true; + move.src_row = row; + move.src_col = col; + ChessSDL_HighlightSelection(row, col, false); + return 1; + } + } + return 0; +} + +static int handleSecondClick(Move &move, int row, int col, bool &isPieceSelected) +{ + if (isPieceSelected) { + isPieceSelected = false; + move.dest_row = row; + move.dest_col = col; + return 1; + } + return 0; +} + +void ChessSDL_GameLoopIteration() +{ + SDL_Event e; + static bool isPieceSelected = false; + static Move move{ 0 }; + + while (SDL_PollEvent(&e) != 0) { + if (e.type == SDL_QUIT) { + QUIT = true; +#ifdef __EMSCRIPTEN__ + emscripten_cancel_main_loop(); +#endif + } else if (e.type == SDL_MOUSEBUTTONDOWN) { + int x, y; + SDL_GetMouseState(&x, &y); + int row = y / TILE_SIZE; + int col = x / TILE_SIZE; + + if (handleFirstClick(move, row, col, isPieceSelected) == 0) { + if (handleSecondClick(move, row, col, isPieceSelected)) { + QUIT = ChessSDL_MakeTheMove(move); + if (QUIT) { +#ifdef __EMSCRIPTEN__ + emscripten_cancel_main_loop(); +#endif + } } } - } - - if (config.AI_OPPONENT) { - // AI's turn (Player 2) - if (getTurnCounter() % 2 == 0 && !quit) { // Assuming AI plays as Black - Move aiMove = findBestMove(config.depth); - MoveResult aiRes = makeTheMove(aiMove.src_row, aiMove.src_col, aiMove.dest_row, aiMove.dest_col); - ChessSDL_HandleMoveResult(aiRes, quit); + } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) { + if (isPieceSelected) { + ChessSDL_HighlightSelection(move.src_row, move.src_col, true); + isPieceSelected = false; } } } + + if (getTurnCounter() % 2 == 0 && !QUIT) { + Move aiMove = findBestMove(depth); + QUIT = ChessSDL_MakeTheMove(aiMove); + if (QUIT) { +#ifdef __EMSCRIPTEN__ + emscripten_cancel_main_loop(); +#endif + } + } } diff --git a/ChessSDL.h b/ChessSDL.h index c64fbc8..2427aeb 100644 --- a/ChessSDL.h +++ b/ChessSDL.h @@ -1,13 +1,6 @@ #pragma once -enum class MoveResult; - -const int ROWS = 8; -const int COLS = 8; -const int SCREEN_WIDTH = 640; -const int SCREEN_HEIGHT = 640; -const int TILE_SIZE = SCREEN_WIDTH / COLS; - int ChessSDL_MakePreparations(); void ChessSDL_Close(); -void ChessSDL_HandleGameLoop(); +void ChessSDL_GameLoopIteration(); +bool ChessSDL_NeedToQuit(); diff --git a/Config/Config.cpp b/Config/Config.cpp deleted file mode 100644 index 700f93e..0000000 --- a/Config/Config.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "Config.h" - -static Config config; - -static void init_config() -{ - config.AI_OPPONENT = true; - config.depth = 4; -} - -const Config & getConfigurations() -{ - init_config(); - - return config; -} \ No newline at end of file diff --git a/Config/Config.h b/Config/Config.h deleted file mode 100644 index 6aa30fc..0000000 --- a/Config/Config.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -struct Config -{ - bool AI_OPPONENT; - int depth; -}; - -const Config & getConfigurations(); diff --git a/Pieces/Bishop.cpp b/Pieces/Bishop.cpp index 1df4fb9..779e6a4 100644 --- a/Pieces/Bishop.cpp +++ b/Pieces/Bishop.cpp @@ -54,10 +54,10 @@ std::string Bishop::getImagePath() const switch (getColor()) { case (PieceColor::White): - str1 = "images/white"; + str1 = "../../../images/white"; break; case (PieceColor::Black): - str1 = "images/black"; + str1 = "../../../images/black"; break; case (PieceColor::Blank): default: diff --git a/Pieces/King.cpp b/Pieces/King.cpp index 4afd7f9..8ec4088 100644 --- a/Pieces/King.cpp +++ b/Pieces/King.cpp @@ -61,10 +61,10 @@ std::string King::getImagePath() const switch (getColor()) { case (PieceColor::White): - str1 = "images/white"; + str1 = "../../../images/white"; break; case (PieceColor::Black): - str1 = "images/black"; + str1 = "../../../images/black"; break; case (PieceColor::Blank): default: diff --git a/Pieces/Knight.cpp b/Pieces/Knight.cpp index 345e372..281b3e3 100644 --- a/Pieces/Knight.cpp +++ b/Pieces/Knight.cpp @@ -33,10 +33,10 @@ std::string Knight::getImagePath() const switch (getColor()) { case (PieceColor::White): - str1 = "images/white"; + str1 = "../../../images/white"; break; case (PieceColor::Black): - str1 = "images/black"; + str1 = "../../../images/black"; break; case (PieceColor::Blank): default: diff --git a/Pieces/Pawn.cpp b/Pieces/Pawn.cpp index 4c82f75..337a7cd 100644 --- a/Pieces/Pawn.cpp +++ b/Pieces/Pawn.cpp @@ -39,16 +39,48 @@ bool Pawn::isValidMove(int src_row, int src_col, int trg_row, int trg_col) const return false; } +static bool isDoubleStep(const Move& move) +{ + int rowDiff = abs(move.dest_row - move.src_row); + if (rowDiff == 2) { + return true; + } + return false; +} + +bool Pawn::isEnPassant(int src_row, int src_col, int trg_row, int trg_col) const +{ + if (getType() != PieceType::Pawn) { + return false; + } + + // Check if the move is a valid en passant capture + std::shared_ptr board = getBoard(); + int direction = (getColor() == PieceColor::White) ? 1 : -1; + if (abs(trg_col - src_col) == 1 && trg_row == src_row + direction) { + auto target_pawn = board->getPiece(src_row, trg_col); + if (target_pawn && target_pawn->getType() == PieceType::Pawn && target_pawn->getColor() != getColor()) { + // Ensure the target pawn just moved two squares in the last turn + Move move = board->getLastMove(); + if (isDoubleStep(move)) { + return true; + } + } + } + + return false; +} + std::string Pawn::getImagePath() const { std::string str1, str2 = "-pawn.png"; switch (getColor()) { case (PieceColor::White): - str1 = "images/white"; + str1 = "../../../images/white"; break; case (PieceColor::Black): - str1 = "images/black"; + str1 = "../../../images/black"; break; case (PieceColor::Blank): default: diff --git a/Pieces/Pawn.h b/Pieces/Pawn.h index 9db7677..9bbdd32 100644 --- a/Pieces/Pawn.h +++ b/Pieces/Pawn.h @@ -9,6 +9,7 @@ class Pawn : public Piece }; bool isValidMove(int src_row, int src_col, int trg_row, int trg_col) const override; + bool isEnPassant(int src_row, int src_col, int trg_row, int trg_col) const; std::string getImagePath() const override; }; diff --git a/Pieces/Piece.cpp b/Pieces/Piece.cpp index d77f995..a18f961 100644 --- a/Pieces/Piece.cpp +++ b/Pieces/Piece.cpp @@ -1,4 +1,5 @@ #include "Piece.h" +#include "Board.h" void Piece::getValidMoves(int row, int col, std::vector &moves) { diff --git a/Pieces/Piece.h b/Pieces/Piece.h index f3ef110..ae62470 100644 --- a/Pieces/Piece.h +++ b/Pieces/Piece.h @@ -3,21 +3,9 @@ #include #include #include +#include -enum class MoveResult { - InvalidPiece = 0, - OpponentPiece, - InvalidMove, - KingInCheck, - Checkmate, - Stalemate, - ValidMove -}; - -struct Move { - int src_row, src_col; - int dest_row, dest_col; -}; +struct Move; enum class PieceType {Empty, Pawn, Knight, Bishop, Rook, Queen, King}; enum class PieceColor {Blank, White, Black}; @@ -46,3 +34,5 @@ class Piece PieceColor m_color; bool m_hasMoved; }; + + diff --git a/Pieces/Queen.cpp b/Pieces/Queen.cpp index d95741e..b4f058d 100644 --- a/Pieces/Queen.cpp +++ b/Pieces/Queen.cpp @@ -58,10 +58,10 @@ std::string Queen::getImagePath() const switch (getColor()) { case (PieceColor::White): - str1 = "images/white"; + str1 = "../../../images/white"; break; case (PieceColor::Black): - str1 = "images/black"; + str1 = "../../../images/black"; break; case (PieceColor::Blank): default: diff --git a/Pieces/Rook.cpp b/Pieces/Rook.cpp index 5ad2d82..97aa8f0 100644 --- a/Pieces/Rook.cpp +++ b/Pieces/Rook.cpp @@ -59,10 +59,10 @@ std::string Rook::getImagePath() const switch (getColor()) { case (PieceColor::White): - str1 = "images/white"; + str1 = "../../../images/white"; break; case (PieceColor::Black): - str1 = "images/black"; + str1 = "../../../images/black"; break; case (PieceColor::Blank): default: diff --git a/main.cpp b/main.cpp index f229359..fea6734 100644 --- a/main.cpp +++ b/main.cpp @@ -1,13 +1,25 @@ +#ifdef __EMSCRIPTEN__ +#include +#endif #include "ChessSDL.h" #include // for linking error +#include "Board.h" -int main(int argc, char* args[]) +int main(int argc, char* args[]) { if (ChessSDL_MakePreparations()) { return 1; } - ChessSDL_HandleGameLoop(); +#ifdef __EMSCRIPTEN__ + // Use emscripten_set_main_loop to call GameLoopIteration repeatedly + emscripten_set_main_loop(ChessSDL_GameLoopIteration, 0, 1); +#else + // Call GameLoopIteration in a loop for non-web environments + while (!ChessSDL_NeedToQuit()) { + ChessSDL_GameLoopIteration(); + } +#endif ChessSDL_Close(); return 0;