From 5e78a4172da975ac227779456e670492217de206 Mon Sep 17 00:00:00 2001 From: Loic Guegan Date: Sat, 19 Feb 2022 18:23:06 +0100 Subject: [PATCH] Now SAN moves can be parsed --- src/ChessArbiter.cpp | 109 +++++++++++++++++++++++++++++++++++++++++ src/ChessArbiter.hpp | 1 + tests/chessarbiter.cpp | 33 +++++++++++++ 3 files changed, 143 insertions(+) diff --git a/src/ChessArbiter.cpp b/src/ChessArbiter.cpp index 18e1e1c..e747397 100644 --- a/src/ChessArbiter.cpp +++ b/src/ChessArbiter.cpp @@ -400,4 +400,113 @@ bool ChessArbiter::IsCheckMate() { std::string ChessArbiter::GetSAN() { return (SAN); } char ChessArbiter::GetCapture() { return (capture); } + +std::string ChessArbiter::ParseSAN(std::string SANMove) { + std::string src, dst; + char piece = ' '; + char hint = ' '; + bool isHintRank = false; + + // First castling + if (SANMove[0] == 'O' || SANMove[0] == '0') { + char c3 = (SANMove.size() >= 3) ? SANMove[3] : '?'; + // Long castle + if (c3 == '-') { + if (fen.player && IsCastlePossible(fen.player, true)) { + return ("e8c8"); + } else if (IsCastlePossible(fen.player, true)) { + return ("e1c1"); + } + } else { + if (fen.player && IsCastlePossible(fen.player, false)) { + return ("e8g8"); + } else if (IsCastlePossible(fen.player, false)) { + return ("e1g1"); + } + } + } + + // First deduce dst square in the move + if (SANMove.size() > 0) { + // Pawn moves + if (std::islower(SANMove[0])) { + if (fen.player) { + piece = 'p'; + } else { + piece = 'P'; + } + // Not a capture + if (SANMove[1] != 'x') { + dst = SANMove.substr(0, 2); + } else { + dst = SANMove.substr(2, 2); + } + } else { + piece = SANMove[0]; + char c1 = (SANMove.size() >= 2) ? SANMove[1] : '?'; + char c2 = (SANMove.size() >= 3) ? SANMove[2] : '?'; + char c3 = (SANMove.size() >= 4) ? SANMove[3] : '?'; + if (c1 == 'x') { + dst = SANMove.substr(2, 2); + } else if (c2 == 'x') { + hint = c1; + dst = SANMove.substr(3, 2); + } else if (IS_DIGIT(c2)) { + dst = SANMove.substr(1, 2); + } else { + hint = c1; + dst = SANMove.substr(2, 2); + } + } + } + isHintRank = IS_DIGIT(hint); + + // Now find src thanks to legal moves + std::vector src_candidates; + for (std::string &move : ListLegalMoves(fen.player)) { + std::string current_src = move.substr(0, 2); + std::string current_dst = move.substr(2, 2); + if (current_dst == dst) { + src_candidates.push_back(current_src); + } + } + + // Now filter the legals move + if (src_candidates.size() > 0) { + if (src_candidates.size() > 1) { + std::vector src_candidates_filtered; + // Filter according to pieces: + for (std::string &cand : src_candidates) { + Piece p = board.GetPieceAt(cand); // This call never fails + if (std::toupper(p.piece) == piece) { + src_candidates_filtered.push_back(cand); + } + } + src_candidates = src_candidates_filtered; + src_candidates_filtered.clear(); + // Last Filtering + if (src_candidates.size() > 1) { + for (std::string &cand : src_candidates) { + char cand_hint = cand[0]; + if (isHintRank) { + cand_hint = cand[1]; + } + if (hint == cand_hint) { + src_candidates_filtered.push_back(cand); + } + } + } + src_candidates = src_candidates_filtered; + } + src = src_candidates[0]; + } + + // Ensure that we return empty string if no matches + if(src.size()<=0){ + return(""); + } + // Else return "srcdst" string + return (src + dst); +} + } // namespace chessarbiter \ No newline at end of file diff --git a/src/ChessArbiter.hpp b/src/ChessArbiter.hpp index 6d31073..e012b4a 100644 --- a/src/ChessArbiter.hpp +++ b/src/ChessArbiter.hpp @@ -65,5 +65,6 @@ public: bool IsDrawByNoMoves(); bool IsDrawByRepetitions(); bool IsDraw(); + std::string ParseSAN(std::string SANMove); }; } // namespace chessarbiter diff --git a/tests/chessarbiter.cpp b/tests/chessarbiter.cpp index 9c1407d..8687d4e 100644 --- a/tests/chessarbiter.cpp +++ b/tests/chessarbiter.cpp @@ -413,3 +413,36 @@ TEST_CASE("SimpleEnPassant", "[SimpleEnPassant]") { CHECK(a.GetFEN() == "rnbqkbnr/ppppp1pp/8/8/8/4p3/PPPP1PPP/RNBQKBNR w KQkq - 0 2"); } + +TEST_CASE("ParseSAN", "[ParseSAN]") { + ChessArbiter a; + + // Initial position test + a.Setup("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + CHECK(a.ParseSAN("d4") == "d2d4"); + CHECK(a.ParseSAN("e3") == "e2e3"); + CHECK(a.ParseSAN("e4") == "e2e4"); + CHECK(a.ParseSAN("Nc3") == "b1c3"); + CHECK(a.ParseSAN("Nf3") == "g1f3"); + + // Check when two similar piece can go to the same square + a.Setup("rnbqkbnr/pppppppp/8/8/P6P/7R/1PPPPPP1/RNBQKBN1 w Qkq - 0 1"); + CHECK(a.ParseSAN("Raa3") == "a1a3"); + CHECK(a.ParseSAN("Rha3") == "h3a3"); + a.Setup("rnbqkbnr/pppppppp/8/8/P6P/R7/1PPPPPP1/RNBQKBN1 w Qkq - 0 1"); + CHECK(a.ParseSAN("R1a2") == "a1a2"); + CHECK(a.ParseSAN("R3a2") == "a3a2"); + a.Setup("r1bqkb1r/pppppppp/5n2/8/P6P/R1N5/1PPPPnP1/R1BQKBN1 b Qkq - 0 1"); + CHECK(a.ParseSAN("N6e4") == "f6e4"); + CHECK(a.ParseSAN("N2e4") == "f2e4"); + + // Castling + a.Setup("rnbqkbnr/pppppppp/8/8/8/4NB2/PPPPPPPP/RNBQK2R w KQkq - 0 1"); + CHECK(a.ParseSAN("O-O") == "e1g1"); + a.Setup("rnbqkbnr/pppppppp/8/8/8/2NBQ3/PPPPPPPP/R3KBNR w KQkq - 0 1"); + CHECK(a.ParseSAN("O-O-O") == "e1c1"); + a.Setup("rnbqk2r/pppppppp/4bn2/8/8/2NBQ3/PPPPPPPP/R3KBNR b KQkq - 0 1"); + CHECK(a.ParseSAN("O-O") == "e8g8"); + a.Setup("r3kb1r/pppppppp/2qnbn2/8/8/2NBQ3/PPPPPPPP/R3KBNR b KQkq - 0 1"); + CHECK(a.ParseSAN("O-O-O") == "e8c8"); +}