Skip to content

Commit

Permalink
Added preliminary support for variation
Browse files Browse the repository at this point in the history
  • Loading branch information
jean-bovet committed May 3, 2021
1 parent ec6a05c commit 43e9cc4
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 67 deletions.
2 changes: 0 additions & 2 deletions BChessTests/Helper/GoogleTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,6 @@ + (void)load {
UnitTestHelper::pathToResources = std::string([[bundle resourcePath] cStringUsingEncoding:NSASCIIStringEncoding]);
[self registerTestClasses];
}
// if ([notification.userInfo[@"NSLoadedClasses"] count] > 0) {
// }
}];
}

Expand Down
18 changes: 9 additions & 9 deletions BChessTests/OpeningsTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ class OpeningsTests: public ::testing::Test {
}

OpeningTreeNode node;
bool result = openings.lookup(temp.moves, [&](auto & node) {
bool result = openings.lookup(temp.allMoves(), [&](auto & node) {
outNode = node;
});

return result;
}

Expand Down Expand Up @@ -92,37 +92,37 @@ TEST_F(OpeningsTests, Loading) {

TEST_F(OpeningsTests, KingsPawnOpening) {
Move m;
MoveList moves;
moves.push(m = createMove(e2, e4, WHITE, PAWN));
std::vector<Move> moves;
moves.push_back(m = createMove(e2, e4, WHITE, PAWN));

bool result = openings.lookup(moves, [&](auto & node) {
ASSERT_EQ(m, node.move);
});
ASSERT_TRUE(result);

moves.push(m = createMove(e7, e5, BLACK, PAWN));
moves.push_back(m = createMove(e7, e5, BLACK, PAWN));
result = openings.lookup(moves, [&](auto & node) {
ASSERT_EQ(m, node.move);
ASSERT_EQ("King's pawn game", node.name);
});
ASSERT_TRUE(result);

moves.push(createMove(e2, e4, WHITE, PAWN));
moves.push_back(createMove(e2, e4, WHITE, PAWN));
result = openings.lookup(moves, [&](auto & node) {
FAIL();
});
ASSERT_FALSE(result);
}

TEST_F(OpeningsTests, BestMove) {
MoveList moves;
std::vector<Move> moves;

bool result = openings.best(moves, [&](auto & node) {
ASSERT_EQ(createMove(d2, d4, WHITE, PAWN), node.move);
});
ASSERT_TRUE(result);

moves.push(createMove(e2, e4, WHITE, PAWN));
moves.push_back(createMove(e2, e4, WHITE, PAWN));

result = openings.best(moves, [&](auto & node) {
ASSERT_EQ(createMove(e7, e5, BLACK, PAWN), node.move);
Expand Down
54 changes: 39 additions & 15 deletions BChessTests/PGNTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@ static void assertMovetextSingle(std::string pgn, std::string expectedMove) {
}
}

TEST(PGN, SingleMove) {
class PGN : public testing::Test {
protected:
virtual void SetUp() {
ChessEngine::initialize();
}
};

TEST_F(PGN, SingleMove) {
ChessGame game;
unsigned cursor = 0;
bool end = false;
Expand All @@ -66,7 +73,7 @@ TEST(PGN, SingleMove) {
EXPECT_TRUE(end);
}

TEST(PGN, SingleMoveWithComments) {
TEST_F(PGN, SingleMoveWithComments) {
ChessGame game;
unsigned cursor = 0;
bool end = false;
Expand All @@ -75,7 +82,7 @@ TEST(PGN, SingleMoveWithComments) {
EXPECT_TRUE(end);
}

TEST(PGN, SingleMoveForBlack) {
TEST_F(PGN, SingleMoveForBlack) {
ChessGame game;
EXPECT_TRUE(FFEN::setFEN("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1", game.board));
unsigned cursor = 0;
Expand All @@ -85,7 +92,7 @@ TEST(PGN, SingleMoveForBlack) {
EXPECT_TRUE(end);
}

TEST(PGN, Moves) {
TEST_F(PGN, Moves) {
assertMovetextSingle("e4", "e2e4");
assertMovetextSingle("e5", "");

Expand Down Expand Up @@ -125,7 +132,7 @@ constexpr const static char *allMoves =
"Nf2 42. g4 Bd3 43. Re6 1/2-1/2"
;

TEST(PGN, GameInput) {
TEST_F(PGN, GameInput) {
assertPGNGame(move1to3, "r1bqkbnr/1ppp1ppp/p1n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 4");

assertPGNGame(move1to10, "r1bq1rk1/2pnbppp/p2p1n2/1p2p3/3PP3/1BP2N1P/PP3PP1/RNBQR1K1 w - - 1 11");
Expand All @@ -135,7 +142,7 @@ TEST(PGN, GameInput) {
assertPGNGame(allMoves, "8/8/4R1p1/2k3p1/1p4P1/1P1b1P2/3K1n2/8 b - - 2 43");
}

TEST(PGN, GameOutput) {
TEST_F(PGN, GameOutput) {
assertOutputPGN(move1to3, "r1bqkbnr/1ppp1ppp/p1n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 4", "1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 *");

assertOutputPGN(move1to10, "r1bq1rk1/2pnbppp/p2p1n2/1p2p3/3PP3/1BP2N1P/PP3PP1/RNBQR1K1 w - - 1 11", "1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3 O-O 9. h3 Nb8 10. d4 Nbd7 *");
Expand All @@ -145,7 +152,7 @@ TEST(PGN, GameOutput) {
assertOutputPGN(allMoves, "8/8/4R1p1/2k3p1/1p4P1/1P1b1P2/3K1n2/8 b - - 2 43", "1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3 O-O 9. h3 Nb8 10. d4 Nbd7 11. c4 c6 12. cxb5 axb5 13. Nc3 Bb7 14. Bg5 b4 15. Nb1 h6 16. Bh4 c5 17. dxe5 Nxe4 18. Bxe7 Qxe7 19. exd6 Qf6 20. Nbd2 Nxd6 21. Nc4 Nxc4 22. Bxc4 Nb6 23. Ne5 Rae8 24. Bxf7+ Rxf7 25. Nxf7 Rxe1+ 26. Qxe1 Kxf7 27. Qe3 Qg5 28. Qxg5 hxg5 29. b3 Ke6 30. a3 Kd6 31. axb4 cxb4 32. Ra5 Nd5 33. f3 Bc8 34. Kf2 Bf5 35. Ra7 g6 36. Ra6+ Kc5 37. Ke1 Nf4 38. g3 Nxh3 39. Kd2 Kb5 40. Rd6 Kc5 41. Ra6 Nf2 42. g4 Bd3 43. Re6 1/2-1/2");
}

TEST(PGN, GameWithBlackFromFEN) {
TEST_F(PGN, GameWithBlackFromFEN) {
ChessGame game;
game.setFEN("1K1k4/1P6/8/8/8/8/r7/2R5 w - - 0 1");
game.move("c1", "d1");
Expand All @@ -154,7 +161,7 @@ TEST(PGN, GameWithBlackFromFEN) {
ASSERT_EQ(pgn, "[FEN \"1K1k4/1P6/8/8/8/8/r7/2R5 w - - 0 1\"]\n[Setup \"1\"]\n1. Rd1+ *");
}

TEST(PGN, GameWithBlackPromotion) {
TEST_F(PGN, GameWithBlackPromotion) {
ChessGame game;
ASSERT_TRUE(FPGN::setGame("1.e4 Nf6 2.Nc3 Nxe4 3.Nxe4 d5 4.Nc3 Qd6 5.Nf3 h5 6.d4 Qd8 7.Bb5+ c6 8.Ba4 b5 9.Bb3 a5 10.a4 b4 11.Na2 Bg4 12.Qd3 Bxf3 13.Qxf3 h4 14.h3 Qd6 15.Bf4 Qe6+ 16.Be3 Qd6 17.O-O g6 18.c4 bxc3 19.bxc3 Nd7 20.c4 dxc4 21.Bxc4 Qf6 22.Qg4 e5 23.Rfe1 Qg7 24.dxe5 Nc5 25.Bxc5 Bxc5 26.e6 Bd4 27.exf7+ Kf8 28.Qe6 Qf6 29.Rad1 Qxe6 30.Bxe6 c5 31.Bd5 Rb8 32.Nc1 Rh5 33.Bc4 Rf5 34.Re2 Rf4 35.Rde1 Bxf2+ 36.Rxf2 Rxc4 37.Nd3 Kg7 38.Re7 Kf8 39.Re6 Rxa4 40.Nxc5 Ra1+ 41.Kh2 Rd1 42.Ne4 Kg7 43.Ng5 Rf8 44.Rfe2 Rxf7 45.Nxf7 Kxf7 46.R6e4 Ra1 47.Rxh4 Kg8 48.Rf2 Kg7 49.Rhf4 a4 50.Rf7+ Kh6 51.R2f4 a3 52.Rg4 a2 53.Rf6 Rh1+ 54.Kxh1", game));

Expand All @@ -165,7 +172,7 @@ TEST(PGN, GameWithBlackPromotion) {
ASSERT_EQ(pgn, "1. e4 Nf6 2. Nc3 Nxe4 3. Nxe4 d5 4. Nc3 Qd6 5. Nf3 h5 6. d4 Qd8 7. Bb5+ c6 8. Ba4 b5 9. Bb3 a5 10. a4 b4 11. Na2 Bg4 12. Qd3 Bxf3 13. Qxf3 h4 14. h3 Qd6 15. Bf4 Qe6+ 16. Be3 Qd6 17. O-O g6 18. c4 bxc3 19. bxc3 Nd7 20. c4 dxc4 21. Bxc4 Qf6 22. Qg4 e5 23. Rfe1 Qg7 24. dxe5 Nc5 25. Bxc5 Bxc5 26. e6 Bd4 27. exf7+ Kf8 28. Qe6 Qf6 29. Rad1 Qxe6 30. Bxe6 c5 31. Bd5 Rb8 32. Nc1 Rh5 33. Bc4 Rf5 34. Re2 Rf4 35. Rde1 Bxf2+ 36. Rxf2 Rxc4 37. Nd3 Kg7 38. Re7 Kf8 39. Re6 Rxa4 40. Nxc5 Ra1+ 41. Kh2 Rd1 42. Ne4 Kg7 43. Ng5 Rf8 44. Rfe2 Rxf7 45. Nxf7 Kxf7 46. Re6e4 Ra1 47. Rxh4 Kg8 48. Rf2 Kg7 49. Rhf4 a4 50. Rf7+ Kh6 51. Rf2f4 a3 52. Rg4 a2 53. Rf6 Rh1+ 54. Kxh1 a1=Q+ *");
}

TEST(PGN, OutputFromInitialPosition) {
TEST_F(PGN, OutputFromInitialPosition) {
ChessGame game;
ASSERT_EQ(StartFEN, game.initialFEN);

Expand All @@ -176,7 +183,7 @@ TEST(PGN, OutputFromInitialPosition) {
ASSERT_EQ("1. e4 *", FPGN::getGame(game));
}

TEST(PGN, InputFromInitialPosition) {
TEST_F(PGN, InputFromInitialPosition) {
ChessGame game;
FPGN::setGame("*", game);
ASSERT_EQ(StartFEN, game.initialFEN);
Expand All @@ -186,7 +193,7 @@ TEST(PGN, InputFromInitialPosition) {
ASSERT_EQ("1. e4 *", FPGN::getGame(game));
}

TEST(PGN, OutputFromPosition) {
TEST_F(PGN, OutputFromPosition) {
std::string fen = "r1bqkbnr/ppp1pppp/2n5/3p4/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3";

ChessGame game;
Expand All @@ -197,19 +204,36 @@ TEST(PGN, OutputFromPosition) {
ASSERT_EQ("[FEN \"r1bqkbnr/ppp1pppp/2n5/3p4/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3\"]\n[Setup \"1\"]\n*", pgn);
}

TEST(PGN, InputFromPosition) {
TEST_F(PGN, InputFromPosition) {
ChessGame game;
FPGN::setGame("[FEN \"r1bqkbnr/ppp1pppp/2n5/3p4/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3\"]\n*", game);

ASSERT_EQ("r1bqkbnr/ppp1pppp/2n5/3p4/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3", game.initialFEN);

ASSERT_EQ(0, game.moveCursor);
ASSERT_EQ(0, game.getNumberOfMoves());
}

TEST(PGN, InputMultipleGames) {
TEST_F(PGN, InputMultipleGames) {
std::string pgn = "[Title \"a\"] 1. e4 e5 [Title \"b\"] 1. d4 d5";
std::vector<ChessGame> games;
FPGN::setGames(pgn, games);
ASSERT_TRUE(FPGN::setGames(pgn, games));
ASSERT_EQ(2, games.size());
}

TEST_F(PGN, PGNWithVariations) {
// 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 (1. d4)";

ChessGame game;
ASSERT_TRUE(FPGN::setGame(pgn, game));

ASSERT_EQ(1, game.getNumberOfMoves());
ASSERT_EQ(2, game.getRoot().variations.size());

// TODO: generate PGN with variation included
auto pgnAgain = FPGN::getGame(game);
ASSERT_EQ("1. e4 *", pgnAgain);
}
2 changes: 1 addition & 1 deletion Shared/Bridge/FEngineInfo.mm
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ - (NSString*)bestLine:(BOOL)uci {
} else {
// Remember the number of moves in the game
// so we only display the PGN for the best line.
int cursor = (int)self.game.moveCursor;
int cursor = (int)self.game.getNumberOfMoves();

// Copy the current game and play the best line.
ChessGame lineGame = self.game;
Expand Down
8 changes: 4 additions & 4 deletions Shared/Engine/Engine/ChessEngine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ class ChessEngine {
// by the openings loaded with loadOpening()
bool isValidOpeningMoves(std::string &name) {
name = "";
if (game.moves.count > 0) {
bool result = openings.lookup(game.moves, [&](auto & node) {
if (game.getNumberOfMoves() > 0) {
bool result = openings.lookup(game.allMoves(), [&](auto & node) {
// no-op
name = node.name;
});
Expand All @@ -139,14 +139,14 @@ class ChessEngine {
}

bool lookupOpeningMove(ChessEvaluation & evaluation) {
if (game.moves.count == 0 && game.board.fullMoveCount > 1) {
if (game.getNumberOfMoves() == 0 && game.board.fullMoveCount > 1) {
// If the game is not at the starting position, that is,
// there are no moves recorded yet but the fullMoveCount is greater
// than one (meaning the game has one or more move already), don't use
// any openings.
return false;
}
bool result = openings.best(game.moves, [&evaluation](OpeningTreeNode & node) {
bool result = openings.best(game.allMoves(), [&evaluation](OpeningTreeNode & node) {
evaluation.line.push(node.move);
});
return result;
Expand Down
60 changes: 43 additions & 17 deletions Shared/Engine/Engine/ChessGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ ChessGame::ChessGame() {
}

void ChessGame::reset() {
moveCursor = 0;
moves.count = 0;
moveIndexes.reset();
history->clear();
board.reset();
outcome = Outcome::in_progress;
Expand Down Expand Up @@ -52,20 +51,45 @@ std::vector<Move> ChessGame::movesAt(File file, Rank rank) {
}

std::vector<Move> ChessGame::allMoves() {
return moves.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);
}
return all;
}

void ChessGame::move(Move move) {
assert(MOVE_ISVALID(move));

if (moveCursor < moves.count) {
// Clear the rest of the history
moves.count = moveCursor;
history->clear();
}

moves.push(move);
moveCursor = moves.count;
// Lookup the node representing the last move by `moveIndexes`
root.lookupNode(0, moveIndexes.moveCursor, moveIndexes, [&move, this](auto & node) {
// First try to match the move with an existing move for that node.
// This happens, for example, when a PGN has been loaded and
// the player is playing a series of move that correspond to that PGN.
bool found = false;
for (int index=0; index<node.variations.size(); index++) {
if (node.variations[index].move == move) {
moveIndexes.moves.push_back(index);
moveIndexes.moveCursor++;
found = true;
break;
}
}

// If the move is not found in any variations of this node, this mean
// this is a new variation (either main variation if no variation exists yet).
if (!found) {
MoveNode newNode = MoveNode();
newNode.move = move;
node.variations.push_back(newNode);

// Insert the index of that new variation to the current move indexes
moveIndexes.moves.push_back((int)node.variations.size() - 1);
moveIndexes.moveCursor++;
}
});

board.move(move);

Expand Down Expand Up @@ -96,30 +120,31 @@ void ChessGame::move(std::string from, std::string to) {
}

bool ChessGame::canUndoMove() {
return moveCursor > 0;
return moveIndexes.moveCursor > 0;
}

bool ChessGame::canRedoMove() {
return moveCursor < moves.count;
return moveIndexes.moveCursor < getNumberOfMoves();
}

void ChessGame::undoMove() {
assert(canUndoMove());
moveCursor--;
moveIndexes.moveCursor--;
replayMoves();
}

void ChessGame::redoMove() {
assert(canRedoMove());
moveCursor++;
moveIndexes.moveCursor++;
replayMoves();
}

void ChessGame::replayMoves() {
board.reset();
auto result = FFEN::setFEN(initialFEN, board);
assert(result);
for (int index=0; index<moveCursor; index++) {
auto moves = allMoves();
for (int index=0; index<moves.size(); index++) {
board.move(moves[index]);
}
}
Expand All @@ -132,7 +157,8 @@ std::string ChessGame::getState() {
ChessState state;
state.set(replay);

for (int index=0; index<moveCursor; index++) {
auto moves = allMoves();
for (int index=0; index<moves.size(); index++) {
replay.move(moves[index]);
state.update(replay);
}
Expand Down
Loading

0 comments on commit 43e9cc4

Please sign in to comment.