Skip to content

Commit

Permalink
Improved PGN output with variations
Browse files Browse the repository at this point in the history
  • Loading branch information
jean-bovet committed May 4, 2021
1 parent e0a8af4 commit 1777ca2
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 56 deletions.
24 changes: 18 additions & 6 deletions BChessTests/PGNTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand All @@ -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));
Expand Down
7 changes: 3 additions & 4 deletions Shared/Engine/Engine/ChessGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,9 @@ std::vector<Move> ChessGame::movesAt(File file, Rank rank) {
std::vector<Move> ChessGame::allMoves() {
std::vector<Move> all;
MoveNode currentNode = root;
for (int index=0; index<getNumberOfMoves(); index++) {
currentNode = currentNode.variations[0]; // return only the main variation
all.push_back(currentNode.move);
}
root.visit(0, moveIndexes, [&all](auto & node) {
all.push_back(node.move);
});
return all;
}

Expand Down
11 changes: 10 additions & 1 deletion Shared/Engine/Engine/ChessGame.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ class ChessGame {
variations[varIndex].lookupNode(cursor+1, atIndex, indexes, callback);
}
}

void visit(int cursor, MoveIndexes indexes, NodeCallback callback) {
if (cursor < indexes.moveCursor) {
int varIndex = indexes.moves[cursor];
callback(variations[varIndex]);
variations[varIndex].visit(cursor+1, indexes, callback);
}
}
};

ChessGame();
Expand All @@ -84,8 +92,9 @@ class ChessGame {

// Always replay the moves to update the internal
// board state when the move indexes change,
// because that mean we are starting another
// because that means we are starting at another
// location in the tree of moves.
// TODO: consider never doing that and replaying the game only when needed?
replayMoves();
}

Expand Down
129 changes: 84 additions & 45 deletions Shared/Engine/Helpers/FPGN.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -695,11 +695,21 @@ bool FPGN::setGames(std::string pgn, std::vector<ChessGame> & 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 = "";
}

Expand All @@ -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) {
Expand All @@ -772,15 +787,39 @@ static void getPGN(ChessBoard board, FPGN::Formatting formatting, ChessGame::Mov
pgn += "+";
}
}

for (int vindex=0; vindex<node.variations.size(); vindex++) {
auto & vnode = node.variations[vindex];
if (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<node.variations.size(); vindex++) {
auto & vnode = node.variations[vindex];
pgn += " (";
getPGN(board, formatting, vnode, pgn, moveIndex+1, fromMoveIndex, fullMoveIndex, /*mainline*/false, /*recursive*/true, /*skip*/false);
pgn += ")";
}

// And finally resume the main line output, recursively, but skipping this time the next move
// because it was already output above
// For example: 1. e4 e5 (1... d5) 2. Nf3 *
getPGN(board, formatting, main, pgn, moveIndex+1, fromMoveIndex, fullMoveIndex, /*mainline*/true, /*recursive*/true, /*skip*/true);
}
}
}
Expand All @@ -794,13 +833,13 @@ std::string FPGN::getGame(ChessGame game, Formatting formatting, int fromIndex)
unsigned fullMoveIndex = 0;

auto rootNode = game.getRoot();
for (int index=0; index<rootNode.variations.size(); index++) {
auto & node = rootNode.variations[index];
if (index > 0) {
for (int vindex=0; vindex<rootNode.variations.size(); vindex++) {
auto & node = rootNode.variations[vindex];
if (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 += ")";
}
}
Expand Down

0 comments on commit 1777ca2

Please sign in to comment.