Updated ffish.js to 0.6.0
authorQueensGambit <curry-berry@freenet.de>
Tue, 2 Feb 2021 22:14:29 +0000 (23:14 +0100)
committerFabian Fichter <ianfab@users.noreply.github.com>
Tue, 2 Feb 2021 23:14:15 +0000 (00:14 +0100)
* 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
src/ffishjs.cpp
tests/js/README.md
tests/js/package.json
tests/js/test.js

index dc9b8fb..b452664 100644 (file)
@@ -692,6 +692,19 @@ bool no_king_piece_in_pockets(const std::array<std::string, 2>& 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<std::string> 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;
 }
 }
-
index d615140..59ff3b9 100644 (file)
@@ -64,6 +64,17 @@ const Variant* get_variant(const std::string& uciVariant) {
   return variants.find(uciVariant)->second;
 }
 
+template <bool isUCI>
+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<LEGAL>(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<true>(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<false>(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<true>(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<true>(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<StateInfo>(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<void(std::string, Notation)>(&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>("Game")
     .function("headerKeys", &Game::header_keys)
     .function("headers", &Game::headers)
index f902362..dee068a 100644 (file)
@@ -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 {
index eb555a8..bb76379 100644 (file)
@@ -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": {
index cb24d72..72945db 100644 (file)
@@ -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);