From 159d5334128ff0bb90a8e4e85739dc8bc44baad8 Mon Sep 17 00:00:00 2001 From: Loic Guegan Date: Sun, 30 Jan 2022 10:00:28 +0100 Subject: [PATCH] - Add drawing methods to the API - Improve tests --- src/Board.cpp | 16 ++++++------ src/ChessArbiter.cpp | 49 ++++++++++++++++++++++++++++++++--- src/ChessArbiter.hpp | 15 ++++++++--- tests/chessarbiter.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 15 deletions(-) diff --git a/src/Board.cpp b/src/Board.cpp index afd32e3..0e88423 100644 --- a/src/Board.cpp +++ b/src/Board.cpp @@ -74,14 +74,11 @@ std::string Board::GetKingLocation(bool isBlack) { void Board::Move(std::string move) { std::string src(move.substr(0, 2)); std::string dst(move.substr(2, 2)); - if (!IsEmpty(src)) { - if (!IsEmpty(dst)) { - RemovePiece(dst); - } - for (Piece &p : pieces) { - if (p.coord == src) { - p.coord = dst; - } + for (Piece &p : pieces) { + if (p.coord == src) { + RemovePiece(dst); // Remove piece on dst if exists + p.coord = dst; + break; } } } @@ -116,6 +113,9 @@ bool Board::IsMovePossible(std::string move) { } // Check colors on dst square + // Seems that checking that is empty + // instead of catching NoPieceFound exception is + // more performant if (!IsEmpty(dst)) { Piece dstp = GetPieceAt(dst); if (srcp.isBlack == dstp.isBlack) diff --git a/src/ChessArbiter.cpp b/src/ChessArbiter.cpp index 37dd5e2..1d1379d 100644 --- a/src/ChessArbiter.cpp +++ b/src/ChessArbiter.cpp @@ -5,8 +5,10 @@ ChessArbiter::ChessArbiter() : wPawn(1), wRook(5), wKnight(3), wBishop(3), wQueen(9), wKing(0) {} void ChessArbiter::Setup(std::string fen) { + positions.clear(); SetFEN(fen); fen_last = this->fen; + positions[this->fen.board] = 1; } void ChessArbiter::SetFEN(FEN fen) { SetFEN(FENParser::Serialize(fen)); } @@ -114,6 +116,13 @@ bool ChessArbiter::Play(std::string move) { return (false); } + // Update position map (repetitions draw) + if (positions.count(fen.board) == 0) { + positions[fen.board] = 1; + } else { + positions[fen.board] += 1; + } + return (true); } return (false); @@ -292,16 +301,48 @@ bool ChessArbiter::IsPlayable() { return (false); } +bool ChessArbiter::IsDrawByFiftyMoveRule() { return (fen.halfmove >= 100); } + +bool ChessArbiter::IsDrawByNoMoves() { + if (!IsCheck(fen.player)) { + std::vector moves = ListLegalMoves(fen.player); + for (std::string &move : moves) { + if (Play(move)) { + positions[fen.board]--; // If move work, remove its position + SetFEN(fen_last); + return (false); + } + } + return (true); + } + return (false); +} + +bool ChessArbiter::IsDrawByRepetitions() { + for (auto const &[key, val] : positions) { + if (val >= 3) { + return (true); + } + } + return (false); +} + +bool ChessArbiter::IsDraw() { + return (IsDrawByFiftyMoveRule() || IsDrawByNoMoves() || + IsDrawByRepetitions()); +} + bool ChessArbiter::IsCheckMate() { if (IsCheck(fen.player)) { std::vector moves = ListLegalMoves(fen.player); - for(std::string &move: moves){ - if(Play(move)){ + for (std::string &move : moves) { + if (Play(move)) { + positions[fen.board]--; // If move work, remove its position SetFEN(fen_last); - return(false); + return (false); } } - return(true); + return (true); } return (false); } diff --git a/src/ChessArbiter.hpp b/src/ChessArbiter.hpp index 5739904..488ca7c 100644 --- a/src/ChessArbiter.hpp +++ b/src/ChessArbiter.hpp @@ -1,6 +1,7 @@ #include "Board.hpp" #include "Fen.hpp" #include +#include namespace chessarbiter { class ChessArbiter { @@ -8,12 +9,15 @@ class ChessArbiter { FEN fen; FEN fen_last; // To undo a move int wPawn, wRook, wKnight, wBishop, wQueen, wKing; + /// @brief Use to compute occurences of positions + std::unordered_map positions; + /// @brief FEN methods used internally + void SetFEN(std::string); + void SetFEN(FEN); public: ChessArbiter(); void Setup(std::string); - void SetFEN(std::string); - void SetFEN(FEN); std::string GetFEN(); /// @brief Check which player is going to play bool IsBlackTurn(); @@ -27,7 +31,7 @@ public: std::string GetBoard(); /// @brief Get current position evaluation according to player's material int GetMaterialScore(); - /// @brief Check if position is legal + /// @brief Check if position is legal to be played bool IsPlayable(); /// @brief Get pieces captures by a player std::string GetCaptures(bool); @@ -36,5 +40,10 @@ public: /// @brief Check if a specific castle is possible by a player bool IsCastlePossible(bool, bool); bool IsCheckMate(); + /// @brief Draws check + bool IsDrawByFiftyMoveRule(); + bool IsDrawByNoMoves(); + bool IsDrawByRepetitions(); + bool IsDraw(); }; } // namespace chessarbiter \ No newline at end of file diff --git a/tests/chessarbiter.cpp b/tests/chessarbiter.cpp index 7c2bffe..edee7f0 100644 --- a/tests/chessarbiter.cpp +++ b/tests/chessarbiter.cpp @@ -300,3 +300,61 @@ TEST_CASE("IsCheckmate", "[chessarbiter/IsCheckmate]") { CHECK(a.GetFEN() == "r3qbr1/p1p1pkp1/1p2p1p1/8/8/8/PPPPP1PP/RNBQ1RK1 b - - 1 1"); } + +TEST_CASE("IsDrawByFiftyMoveRule", "[chessarbiter/IsDrawByFiftyMoveRule]") { + ChessArbiter a; + a.Setup("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + CHECK_FALSE(a.IsDrawByFiftyMoveRule()); + + a.Setup("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 99 1"); + CHECK_FALSE(a.IsDrawByFiftyMoveRule()); + a.Play("b1c3"); + CHECK(a.IsDrawByFiftyMoveRule()); + + a.Setup("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 100 1"); + CHECK(a.IsDrawByFiftyMoveRule()); +} + +TEST_CASE("IsDrawByNoMoves", "[chessarbiter/IsDrawByNoMoves]") { + ChessArbiter a; + + // No move for black + a.Setup("8/8/8/8/8/8/5K1p/7k b - - 0 1"); + CHECK(a.IsDrawByNoMoves()); + + // But move for white + a.Setup("8/8/8/8/8/8/5K1p/7k w - - 0 1"); + CHECK_FALSE(a.IsDrawByNoMoves()); + + // No move for white + a.Setup("8/8/7r/2K5/b7/2k5/6q1/8 w - - 0 1"); + CHECK(a.IsDrawByNoMoves()); + + // But move for black + a.Setup("8/8/7r/2K5/b7/2k5/6q1/8 b - - 0 1"); + CHECK_FALSE(a.IsDrawByNoMoves()); +} + +TEST_CASE("IsDrawByRepetitions", "[chessarbiter/IsDrawByRepetitions]") { + ChessArbiter a; + // One time + a.Setup("8/3kp3/8/8/4P3/3K4/8/8 w - - 0 1"); + + a.Play("d3d4"); + a.Play("d7d6"); + CHECK_FALSE(a.IsDrawByRepetitions()); + + // Two time + a.Play("d4d3"); + a.Play("d6d7"); + CHECK_FALSE(a.IsDrawByRepetitions()); + + a.Play("d3d4"); + a.Play("d7d6"); + CHECK_FALSE(a.IsDrawByRepetitions()); + + // Three time + a.Play("d4d3"); + a.Play("d6d7"); + CHECK(a.IsDrawByRepetitions()); +}