From 1777ca2c0ee0b2bf68a53865088c38a7aa503db7 Mon Sep 17 00:00:00 2001 From: Jean Bovet Date: Mon, 3 May 2021 21:28:11 -0700 Subject: [PATCH] Improved PGN output with variations --- BChessTests/PGNTests.cpp | 24 ++++-- Shared/Engine/Engine/ChessGame.cpp | 7 +- Shared/Engine/Engine/ChessGame.hpp | 11 ++- Shared/Engine/Helpers/FPGN.cpp | 129 +++++++++++++++++++---------- 4 files changed, 115 insertions(+), 56 deletions(-) diff --git a/BChessTests/PGNTests.cpp b/BChessTests/PGNTests.cpp index e2310b8..3a87092 100644 --- a/BChessTests/PGNTests.cpp +++ b/BChessTests/PGNTests.cpp @@ -33,10 +33,10 @@ static void assertOutputPGN(const char *pgnGame, std::string expectedFEN, std::s // Assert the FEN for the end position std::string actualFEN = FFEN::getFEN(game.board); - EXPECT_STREQ(actualFEN.c_str(), expectedFEN.c_str()); + EXPECT_STREQ(expectedFEN.c_str(), actualFEN.c_str()); auto generatedPGN = FPGN::getGame(game); - EXPECT_STREQ(generatedPGN.c_str(), outputPGN.c_str()); + EXPECT_STREQ(outputPGN.c_str(), generatedPGN.c_str()); // Now let's try again the assert the game using the generated PGN and the expected FEN of the game // in the final position. @@ -234,10 +234,6 @@ TEST_F(PGN, PGNWithSimpleWhiteVariation) { } TEST_F(PGN, PGNWithSimpleBlackVariation) { -// std::string pgn = "1. e4 e5 2. Nc3 Nf6 3. f4 d5 4. fxe5 Nxe4 5. Qf3 Nxc3 {We want them to take\ -// this knight so we will recapture with the b2 pawn and go for d2-d4, with a\ -// large center, and Bd3/Ne2 setup.} (5... Nc6 {A tricky and challenging move. We\ -// must prevent Nxe5.}) 6. bxc3 Be7"; std::string pgn = "1. e4 e5 (1... c5)"; ChessGame game; @@ -251,6 +247,22 @@ TEST_F(PGN, PGNWithSimpleBlackVariation) { ASSERT_EQ("1. e4 e5 (1... c5) *", pgnAgain); } +TEST_F(PGN, PGNWithVariation1) { +// std::string pgn = "1. e4 e5 2. Nc3 Nf6 3. f4 d5 4. fxe5 Nxe4 5. Qf3 Nxc3 {We want them to take\ +// this knight so we will recapture with the b2 pawn and go for d2-d4, with a\ +// large center, and Bd3/Ne2 setup.} (5... Nc6 {A tricky and challenging move. We\ +// must prevent Nxe5.}) 6. bxc3 Be7"; + std::string pgn = "1. e4 e5 2. Nc3 Nf6 3. f4 d5 4. fxe5 Nxe4 5. Qf3 Nxc3 (5... Nc6) 6. bxc3 Be7"; + + ChessGame game; + ASSERT_TRUE(FPGN::setGame(pgn, game)); + + ASSERT_EQ(12, game.getNumberOfMoves()); + + auto pgnAgain = FPGN::getGame(game); + ASSERT_EQ("1. e4 e5 2. Nc3 Nf6 3. f4 d5 4. fxe5 Nxe4 5. Qf3 Nxc3 (5... Nc6) 6. bxc3 Be7 *", pgnAgain); +} + TEST_F(PGN, LineFromCursor) { ChessGame game; ASSERT_TRUE(FPGN::setGame(move1to3, game)); diff --git a/Shared/Engine/Engine/ChessGame.cpp b/Shared/Engine/Engine/ChessGame.cpp index a381026..6b1a8c1 100644 --- a/Shared/Engine/Engine/ChessGame.cpp +++ b/Shared/Engine/Engine/ChessGame.cpp @@ -53,10 +53,9 @@ std::vector ChessGame::movesAt(File file, Rank rank) { std::vector ChessGame::allMoves() { std::vector all; MoveNode currentNode = root; - for (int index=0; index & games) { } // Returns a PGN representation of a particular node in the game, including all its descendant. -static void getPGN(ChessBoard board, FPGN::Formatting formatting, ChessGame::MoveNode & node, std::string & pgn, int index, int fromIndex, int fullMoveIndex, bool mainLine) { +static void getPGN(ChessBoard board, // The chess board representation which is used to determine the actual move and state of the board + FPGN::Formatting formatting, // The type of desired formatting + ChessGame::MoveNode & node, // The current node representing the move being output + std::string & pgn, // The PGN being constructed + int moveIndex, // The current move index + int fromMoveIndex, // The move index at which we starts to build up the PGN + int fullMoveIndex, // The number of full move that have been done so far + bool mainLine, // True if this node/move represents the main line, false if it represents a variation + bool recursive, // True if this method should continue to traverse the next move and all its variation, false to just output this move and return + bool skip // True if the PGN output should be skipped for this method executed +) { auto move = node.move; auto piece = MOVE_PIECE(move); - - if (formatting == FPGN::Formatting::line && index == fromIndex) { + + if (formatting == FPGN::Formatting::line && moveIndex == fromMoveIndex && !skip) { pgn = ""; } @@ -725,45 +735,50 @@ static void getPGN(ChessBoard board, FPGN::Formatting formatting, ChessGame::Mov } } - if (mainLine) { - // In the main line, only display the move number for white - if (index % 2 == 0) { - fullMoveIndex++; - if (formatting != FPGN::Formatting::line) { + if (moveIndex % 2 == 0) { + fullMoveIndex++; + } + + // Output the move number + if (!skip) { + if (mainLine) { + // In the main line, only display the move number for white + if (moveIndex % 2 == 0 && formatting != FPGN::Formatting::line) { if (pgn.size() > 0) { pgn += " "; } pgn += std::to_string(fullMoveIndex) + "."; } - } - } else { - // We are in a variation, which means we need to display - // the full move number and if it is white or black - // to play. For example: - // (1. d4) // variation for white - // (1... c5) // variation for black - if (index % 2 == 0) { - fullMoveIndex++; - } - if (formatting != FPGN::Formatting::line) { - pgn += std::to_string(fullMoveIndex); - } - if (index % 2 == 0) { - pgn += "."; } else { - pgn += "..."; + // We are in a variation, which means we need to display + // the full move number and if it is white or black + // to play. For example: + // (1. d4) // variation for white + // (1... c5) // variation for black + if (formatting != FPGN::Formatting::line) { + pgn += std::to_string(fullMoveIndex); + } + if (moveIndex % 2 == 0) { + pgn += "."; + } else { + pgn += "..."; + } } } - - if (pgn.size() > 0) { - pgn += " "; + + // Output the actual move + if (!skip) { + if (pgn.size() > 0) { + pgn += " "; + } + pgn += FPGN::to_string(move, sanType); } - pgn += FPGN::to_string(move, sanType); - - board.move(move); + // Execute the move on the board + board.move(move); + // Determine if the position is check or mate - if (board.isCheck(board.color)) { + if (board.isCheck(board.color) && !skip) { ChessMoveGenerator generator; auto moveList = generator.generateMoves(board); if (moveList.count == 0) { @@ -772,15 +787,39 @@ static void getPGN(ChessBoard board, FPGN::Formatting formatting, ChessGame::Mov pgn += "+"; } } - - for (int vindex=0; vindex 0) { - pgn += " ("; - } - getPGN(board, formatting, vnode, pgn, index+1, fromIndex, fullMoveIndex, vindex == 0); - if (vindex > 0) { - pgn += ")"; + + // If we are not doing any recursive output, return now. + if (!recursive) { + return; + } + + // Now deal with the variations + // Note: the first variation always represents the main line + if (node.variations.size() > 0) { + if (node.variations.size() == 1) { + // If there is only one variation, it is easy: it is the main line + // so continue recursively to traverse it. + auto & main = node.variations[0]; + getPGN(board, formatting, main, pgn, moveIndex+1, fromMoveIndex, fullMoveIndex, /*mainline*/true, /*recursive*/true, /*skip*/false); + } else { + // If there are more than one variation, we need to print first the main variation but for only one move + // For example: 1. e4 e5 + auto & main = node.variations[0]; + getPGN(board, formatting, main, pgn, moveIndex+1, fromMoveIndex, fullMoveIndex, /*mainline*/true, /*recursive*/false, /*skip*/false); + + // And then print all the other variations in full (recursively) + // For example: 1. e4 e5 (1... d5) + for (int vindex=1; vindex 0) { + for (int vindex=0; vindex 0) { pgn += " ("; } - getPGN(outputBoard, formatting, node, pgn, 0, fromIndex, fullMoveIndex, index == 0); - if (index > 0) { + getPGN(outputBoard, formatting, node, pgn, 0, fromIndex, fullMoveIndex, /*mainline*/vindex == 0, /*recursive*/true, /*skip*/false); + if (vindex > 0) { pgn += ")"; } }