diff --git a/src/ffishjs.cpp b/src/ffishjs.cpp index 7e4f26dc0..46ab043f6 100644 --- a/src/ffishjs.cpp +++ b/src/ffishjs.cpp @@ -211,17 +211,20 @@ class Board { std::string variation_san(std::string uciMoves, Notation notation, bool moveNumbers) { std::stringstream ss(uciMoves); - StateListPtr tempStates; std::vector moves; std::string variationSan = ""; std::string uciMove; bool first = true; + bool invalidMoveEncountered = false; + size_t statesAdded = 0; while (std::getline(ss, uciMove, ' ')) { const Move move = UCI::to_move(this->pos, uciMove); - if (is_move_none(move, uciMove, pos)) - return ""; - moves.emplace_back(UCI::to_move(this->pos, uciMove)); + if (is_move_none(move, uciMove, pos)) { + invalidMoveEncountered = true; + break; + } + moves.emplace_back(move); if (first) { first = false; if (moveNumbers) { @@ -243,14 +246,22 @@ class Board { variationSan += SAN::move_to_san(this->pos, moves.back(), Notation(notation)); } states->emplace_back(); + ++statesAdded; pos.do_move(moves.back(), states->back()); } // recover initial state - for(auto rIt = std::rbegin(moves); rIt != std::rend(moves); ++rIt) { + for (auto rIt = std::rbegin(moves); rIt != std::rend(moves); ++rIt) pos.undo_move(*rIt); + + while (statesAdded > 0) { + states->pop_back(); + --statesAdded; } + if (invalidMoveEncountered) + return ""; + return variationSan; } @@ -271,6 +282,10 @@ class Board { return pos.game_ply(); } + int state_stack_size() const { + return static_cast(states->size()); + } + bool has_insufficient_material(bool turn) const { return Stockfish::has_insufficient_material(turn ? WHITE : BLACK, pos); } @@ -712,6 +727,7 @@ EMSCRIPTEN_BINDINGS(ffish_js) { .function("fullmoveNumber", &Board::fullmove_number) .function("halfmoveClock", &Board::halfmove_clock) .function("gamePly", &Board::game_ply) + .function("stateStackSize", &Board::state_stack_size) .function("hasInsufficientMaterial", &Board::has_insufficient_material) .function("isInsufficientMaterial", &Board::is_insufficient_material) .function("isGameOver", select_overload(&Board::is_game_over)) diff --git a/tests/js/ffish.d.ts b/tests/js/ffish.d.ts index f92fff49e..a0e605c77 100644 --- a/tests/js/ffish.d.ts +++ b/tests/js/ffish.d.ts @@ -55,6 +55,7 @@ export interface Board { fullmoveNumber(): number; halfmoveClock(): number; gamePly(): number; + stateStackSize(): number; hasInsufficientMaterial(turn: boolean): boolean; isInsufficientMaterial(): boolean; isGameOver(claimDraw?: boolean): boolean; diff --git a/tests/js/test.js b/tests/js/test.js index 3c82032c6..6230f74ee 100644 --- a/tests/js/test.js +++ b/tests/js/test.js @@ -282,6 +282,27 @@ describe('board.variationSan(uciMoves, notation, moveNumbers)', function () { }); }); +describe('board.stateStackSize()', function () { + it("it remains aligned with the number of real moves after repeated variationSan calls", () => { + let board = new ffish.Board(); + const actualMoves = ["e2e4", "e7e5", "g1f3"]; + actualMoves.forEach(move => { + chai.expect(board.push(move)).to.equal(true); + }); + + const expectedStates = actualMoves.length + 1; + chai.expect(board.stateStackSize()).to.equal(expectedStates); + + const variationLine = "g8f6 d2d3 Bf8c5"; + for (let i = 0; i < 5; ++i) { + chai.expect(board.variationSan(variationLine)).to.not.equal(""); + chai.expect(board.stateStackSize()).to.equal(expectedStates); + } + + board.delete(); + }); +}); + describe('board.turn()', function () { it("it returns the side to move", () => { let board = new ffish.Board();