From b4a1738611594d67ca6cb983c94fd0a60e237801 Mon Sep 17 00:00:00 2001 From: QueensGambit Date: Tue, 2 Feb 2021 23:14:29 +0100 Subject: [PATCH] Updated ffish.js to 0.6.0 * made validate_fen() more robust (allows '-' for variants which do not have no-progress counter or castling rights) * added is_move_none() check to board.push(), board.push_san(), board.san_move(), board.variation_san() * added board.variant() * bug fix: Added ++curIdx after parsing comment in read_game_pgn() * ffish.js supports now "atomic" as it has been integrated into Fairy- Stockfish --- src/apiutil.h | 41 +++++++++++++++++--------- src/ffishjs.cpp | 77 +++++++++++++++++++++++++++++++++++-------------- tests/js/README.md | 23 +++++++++------ tests/js/package.json | 2 +- tests/js/test.js | 35 +++++++++++++++++++++- 5 files changed, 130 insertions(+), 48 deletions(-) diff --git a/src/apiutil.h b/src/apiutil.h index dc9b8fb..b452664 100644 --- a/src/apiutil.h +++ b/src/apiutil.h @@ -692,6 +692,19 @@ bool no_king_piece_in_pockets(const std::array& pockets) { return pockets[WHITE].find('k') == std::string::npos && pockets[BLACK].find('k') == std::string::npos; } +Validation check_digit_field(const std::string& field) +{ + if (field.size() == 1 && field[0] == '-') { + return OK; + } + for (char c : field) + if (!isdigit(c)) { + return NOK; + } + return OK; +} + + FenValidation validate_fen(const std::string& fen, const Variant* v) { const std::string validSpecialCharacters = "/+~[]-"; @@ -712,9 +725,12 @@ FenValidation validate_fen(const std::string& fen, const Variant* v) { std::vector starFenParts = get_fen_parts(v->startFen, ' '); const unsigned int nbFenParts = starFenParts.size(); - // check for number of parts - if (fenParts.size() != nbFenParts) { - std::cerr << "Invalid number of fen parts. Expected: " << nbFenParts << " Actual: " << fenParts.size() << std::endl; + // check for number of parts (also up to two additional "-" for non-existing no-progress counter or castling rights) + const unsigned int maxNumberFenParts = 7U; + const unsigned int topThreshold = std::min(nbFenParts + 2, maxNumberFenParts); + if (fenParts.size() < nbFenParts || fenParts.size() > topThreshold) { + std::cerr << "Invalid number of fen parts. Expected: >= " << nbFenParts << " and <= " << topThreshold + << " Actual: " << fenParts.size() << std::endl; return FEN_INVALID_NB_PARTS; } @@ -810,21 +826,18 @@ FenValidation validate_fen(const std::string& fen, const Variant* v) { // 6) Part // check half move counter - for (char c : fenParts[nbFenParts-2]) - if (!isdigit(c)) { - std::cerr << "Invalid half move counter: '" << c << "'." << std::endl; - return FEN_INVALID_HALF_MOVE_COUNTER; - } + if (!check_digit_field(fenParts[fenParts.size()-2])) { + std::cerr << "Invalid half move counter: '" << fenParts[fenParts.size()-2] << "'." << std::endl; + return FEN_INVALID_HALF_MOVE_COUNTER; + } // 7) Part // check move counter - for (char c : fenParts[nbFenParts-1]) - if (!isdigit(c)) { - std::cerr << "Invalid move counter: '" << c << "'." << std::endl; - return FEN_INVALID_MOVE_COUNTER; - } + if (!check_digit_field(fenParts[fenParts.size()-1])) { + std::cerr << "Invalid move counter: '" << fenParts[fenParts.size()-1] << "'." << std::endl; + return FEN_INVALID_MOVE_COUNTER; + } return FEN_OK; } } - diff --git a/src/ffishjs.cpp b/src/ffishjs.cpp index d615140..59ff3b9 100644 --- a/src/ffishjs.cpp +++ b/src/ffishjs.cpp @@ -64,6 +64,17 @@ const Variant* get_variant(const std::string& uciVariant) { return variants.find(uciVariant)->second; } +template +inline bool is_move_none(Move move, const std::string& strMove, const Position& pos) { + if (move == MOVE_NONE) { + std::cerr << "The given "; + isUCI ? std::cerr << "uciMove" : std::cerr << "sanMove"; + std::cerr << " '" << strMove << "' for position '" << pos.fen() << "' is invalid." << std::endl; + return true; + } + return false; +} + class Board { // note: we can't use references for strings here due to conversion to JavaScript private: @@ -117,8 +128,12 @@ public: return MoveList(pos).size(); } - void push(std::string uciMove) { - do_move(UCI::to_move(this->pos, uciMove)); + bool push(std::string uciMove) { + const Move move = UCI::to_move(this->pos, uciMove); + if (is_move_none(move, uciMove, pos)) + return false; + do_move(move); + return true; } bool push_san(std::string sanMove) { @@ -135,11 +150,10 @@ public: break; } } - if (foundMove != MOVE_NONE) { - do_move(foundMove); - return true; - } - return false; + if (is_move_none(foundMove, sanMove, pos)) + return false; + do_move(foundMove); + return true; } void pop() { @@ -168,12 +182,16 @@ public: // note: const identifier for pos not possible due to move_to_san() std::string san_move(std::string uciMove) { - return move_to_san(this->pos, UCI::to_move(this->pos, uciMove), NOTATION_SAN); + return san_move(uciMove, NOTATION_SAN); } std::string san_move(std::string uciMove, Notation notation) { + const Move move = UCI::to_move(this->pos, uciMove); + if (is_move_none(move, uciMove, pos)) + return ""; return move_to_san(this->pos, UCI::to_move(this->pos, uciMove), notation); } + std::string variation_san(std::string uciMoves) { return variation_san(uciMoves, NOTATION_SAN, true); } @@ -191,6 +209,9 @@ public: bool first = true; 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 (first) { first = false; @@ -323,6 +344,16 @@ public: return ss.str(); } + std::string variant() { + // Iterate through the variants map + for (auto it = variants.begin(); it != variants.end(); ++it) + if (it->second == v) + return it->first; + + std::cerr << "Current variant is not registered." << std::endl; + return "unknown"; + } + private: void resetStates() { this->states = StateListPtr(new std::deque(1)); @@ -433,17 +464,14 @@ public: bool skip_comment(const std::string& pgn, size_t& curIdx, size_t& lineEnd) { - if (pgn[curIdx] == '{') { - // skip comment - curIdx = pgn.find('}', curIdx); - if (curIdx == std::string::npos) { - std::cerr << "Missing '}' for move comment while reading pgn." << std::endl; - return false; - } - if (curIdx > lineEnd) - lineEnd = pgn.find('\n', curIdx); - } - return true; + curIdx = pgn.find('}', curIdx); + if (curIdx == std::string::npos) { + std::cerr << "Missing '}' for move comment while reading pgn." << std::endl; + return false; + } + if (curIdx > lineEnd) + lineEnd = pgn.find('\n', curIdx); + return true; } Game read_game_pgn(std::string pgn) { @@ -493,8 +521,11 @@ Game read_game_pgn(std::string pgn) { if (pgn[curIdx] == '*') return game; - if (!skip_comment(pgn, curIdx, lineEnd)) - return game; + if (pgn[curIdx] == '{') { + if (!skip_comment(pgn, curIdx, lineEnd)) + return game; + ++curIdx; + } // Movetext RAV (Recursive Annotation Variation) size_t openedRAV = 0; @@ -547,6 +578,7 @@ Game read_game_pgn(std::string pgn) { size_t annotationChar2 = sanMove.find('!'); if (annotationChar1 != std::string::npos || annotationChar2 != std::string::npos) sanMove = sanMove.substr(0, std::min(annotationChar1, annotationChar2)); + std::cout << sanMove << " "; game.board->push_san(sanMove); } curIdx = sanMoveEnd+1; @@ -597,7 +629,8 @@ EMSCRIPTEN_BINDINGS(ffish_js) { .function("pushSanMoves", select_overload(&Board::push_san_moves)) .function("pocket", &Board::pocket) .function("toString", &Board::to_string) - .function("toVerboseString", &Board::to_verbose_string); + .function("toVerboseString", &Board::to_verbose_string) + .function("variant", &Board::variant); class_("Game") .function("headerKeys", &Game::header_keys) .function("headers", &Game::headers) diff --git a/tests/js/README.md b/tests/js/README.md index f902362..dee068a 100644 --- a/tests/js/README.md +++ b/tests/js/README.md @@ -61,13 +61,18 @@ Show all available variants supported by _Fairy-Stockfish_ and **ffish.js**. ffish.variants() ``` ``` -3check 5check ai-wok almost amazon antichess armageddon asean ataxx breakthrough bughouse cambodian\ -capablanca capahouse caparandom centaur chancellor chess chessgi chigorin clobber clobber10 codrus courier\ -crazyhouse dobutsu embassy euroshogi extinction fairy fischerandom gardner giveaway gorogoro gothic grand\ -hoppelpoppel horde janggi janggicasual janggimodern janggitraditional janus jesonmor judkins karouk kinglet\ -kingofthehill knightmate koedem kyotoshogi loop losalamos losers makpong makruk manchu micro mini minishogi\ -minixiangqi modern newzealand nocastle normal placement pocketknight racingkings seirawan shako shatar\ -shatranj shogi shouse sittuyin suicide supply threekings xiangqi +3check 3check-crazyhouse 5check ai-wok almost amazon anti-losalamos antichess\ +armageddon asean ataxx atomic breakthrough bughouse cambodian capablanca\ +capahouse caparandom centaur cfour chancellor chaturanga chess chessgi chigorin\ +clobber clobber10 codrus coffeehouse courier crazyhouse dobutsu embassy euroshogi\ +extinction fairy fischerandom flipello flipersi gardner gemini giveaway gorogoro\ +gothic grand grandhouse hoppelpoppel horde indiangreat janggi janggicasual\ +janggihouse janggimodern janggitraditional janus jesonmor judkins karouk kinglet\ +kingofthehill knightmate koedem kyotoshogi loop losalamos losers makpong makruk\ +makrukhouse manchu micro mini minishogi minixiangqi modern newzealand nocastle\ +nocheckatomic normal orda pawnsonly peasant placement pocketknight racingkings\ +seirawan semitorpedo shako shatar shatranj shogi shogun shouse sittuyin suicide\ +supply threekings tictactoe upsidedown weak xiangqi xiangqihouse ``` ### Board object @@ -81,10 +86,10 @@ ffish['onRuntimeInitialized'] = () => { } ``` -Set a custom fen position including fen valdiation: +Set a custom fen position including fen validiation: ```javascript fen = "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"; -if (ffish.valdiateFen(fen) == 1) { // ffish.valdiateFen(fen) can return different error codes, it returns 1 for FEN_OK +if (ffish.validateFen(fen) == 1) { // ffish.validateFen(fen) can return different error codes, it returns 1 for FEN_OK board.setFen(fen); } else { diff --git a/tests/js/package.json b/tests/js/package.json index eb555a8..bb76379 100644 --- a/tests/js/package.json +++ b/tests/js/package.json @@ -1,6 +1,6 @@ { "name": "ffish", - "version": "0.5.1", + "version": "0.6.0", "description": "A high performance WebAssembly chess variant library based on Fairy-Stockfish", "main": "ffish.js", "scripts": { diff --git a/tests/js/test.js b/tests/js/test.js index cb24d72..72945db 100644 --- a/tests/js/test.js +++ b/tests/js/test.js @@ -114,6 +114,8 @@ describe('board.push(uciMove)', function () { board.push("e7e5"); board.push("g1f3"); chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); + chai.expect(board.push("q2q7")).to.equal(false); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); board.delete(); }); }); @@ -125,6 +127,8 @@ describe('board.pushSan(sanMove)', function () { board.pushSan("e5"); board.pushSan("Nf3"); chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); + chai.expect(board.pushSan("Nf3")).to.equal(false); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); board.delete(); }); }); @@ -226,6 +230,8 @@ describe('board.variationSan(uciMoves)', function () { board.push("e2e4") const sanMoves = board.variationSan("e7e5 g1f3 b8c6 f1c4"); chai.expect(sanMoves).to.equal("1...e5 2. Nf3 Nc6 3. Bc4"); + const sanMovesInvalid = board.variationSan("e7e5 g1f3 b8c6 f1c7"); + chai.expect(sanMovesInvalid).to.equal(""); board.delete(); }); }); @@ -477,6 +483,26 @@ describe('board.toVerboseString()', function () { }); }); +describe('board.variant()', function () { + it("it returns the uci-variant of the board.", () => { + const board = new ffish.Board("chess"); + chai.expect(board.variant()).to.equal("chess"); + board.delete(); + const board2 = new ffish.Board(""); + chai.expect(board2.variant()).to.equal("chess"); + board2.delete(); + const board3 = new ffish.Board("standard"); + chai.expect(board3.variant()).to.equal("chess"); + board3.delete(); + const board4 = new ffish.Board("Standard"); + chai.expect(board4.variant()).to.equal("chess"); + board4.delete(); + const board5 = new ffish.Board("atomic"); + chai.expect(board5.variant()).to.equal("atomic"); + board5.delete(); + }); +}); + describe('ffish.info()', function () { it("it returns the version of the Fairy-Stockfish binary", () => { chai.expect(ffish.info()).to.be.a('string'); @@ -522,8 +548,13 @@ describe('ffish.validateFen(fen, uciVariant)', function () { // check if starting fens are valid for all variants const variants = ffish.variants().split(" ") for (let idx = 0; idx < variants.length; ++idx) { - const startFen = ffish.startingFen(variants[idx]); - chai.expect(ffish.validateFen(startFen, variants[idx])).to.equal(1); + const variant = variants[idx]; + const startFen = ffish.startingFen(variant); + chai.expect(ffish.validateFen(startFen, variant)).to.equal(1); + // check if the FEN is still valid if board.fen() is returned + const board = new ffish.Board(variant); + chai.expect(ffish.validateFen(board.fen(), variant)).to.equal(1); + board.delete(); } // alternative pocket piece formulation chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/RB w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(1); -- 1.7.0.4