Added functionality to ffish.js (closes #177)
authorQueensGambit <curry-berry@freenet.de>
Tue, 8 Sep 2020 21:45:05 +0000 (23:45 +0200)
committerFabian Fichter <ianfab@users.noreply.github.com>
Wed, 9 Sep 2020 17:55:34 +0000 (19:55 +0200)
+ board.fullmoveNumber()
+ board.isCheck()
+ board.isBikjang()
+ board.sanMove(notation)
+ board.variationSan(uciMoves)
+ board.variationSan(uciMoves, notation)
+ board.variationSan(uciMoves, notation, moveNumbers)
+ enum ffish.Notation
+ ffish.setOption()
+ ffish.setOptionInt()
+ ffish.setOptionBool()
+ ES6 module export

Readme.md
src/ffishjs.cpp
tests/js/README.md
tests/js/package.json
tests/js/test.js

index 97b8466..c246836 100644 (file)
--- a/Readme.md
+++ b/Readme.md
@@ -5,6 +5,7 @@
 [![Build Status](https://travis-ci.org/ianfab/Fairy-Stockfish.svg?branch=master)](https://travis-ci.org/ianfab/Fairy-Stockfish)
 [![Build Status](https://ci.appveyor.com/api/projects/status/github/ianfab/Fairy-Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/ianfab/Fairy-Stockfish/branch/master)
 [![PyPI version](https://badge.fury.io/py/pyffish.svg)](https://badge.fury.io/py/pyffish)
+[![NPM version](https://img.shields.io/npm/v/ffish.svg?sanitize=true)](https://www.npmjs.com/package/ffish)
 
 Fairy-Stockfish is a chess variant engine derived from [Stockfish](https://github.com/official-stockfish/Stockfish/) designed for the support of fairy chess variants and easy extensibility with more games. It can play various regional, historical, and modern chess variants as well as [games with user-defined rules](https://github.com/ianfab/Fairy-Stockfish/wiki/Variant-configuration). For [compatibility with graphical user interfaces](https://github.com/ianfab/Fairy-Stockfish/wiki/Usage) it supports the UCI, UCCI, USI, and CECP/XBoard protocols.
 
index 73eb6b6..462c65a 100644 (file)
@@ -20,6 +20,7 @@
 #include <emscripten/bind.h>
 #include <vector>
 #include <string>
+#include <sstream>
 
 #include "misc.h"
 #include "types.h"
@@ -39,7 +40,7 @@
 using namespace emscripten;
 
 
-void initializeStockfish(std::string& uciVariant) {
+void initialize_stockfish(std::string& uciVariant) {
   pieceMap.init();
   variants.init();
   UCI::init(Options);
@@ -57,20 +58,21 @@ private:
   Position pos;
   Thread* thread;
   std::vector<Move> moveStack;
- static bool sfInitialized;
   bool is960;
 
 public:
+  static bool sfInitialized;
+
   Board():
     Board("chess", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" , false) {
   }
 
-  Board(std::string uciVariant) {
-    init(uciVariant, "", is960);
+  Board(std::string uciVariant):
+    Board(uciVariant, "", false) {
   }
 
   Board(std::string uciVariant, std::string fen):
-    Board(uciVariant, fen , false) {
+    Board(uciVariant, fen, false) {
   }
 
   Board(std::string uciVariant, std::string fen, bool is960) {
@@ -81,12 +83,12 @@ public:
     std::string moves = "";
     bool first = true;
     for (const ExtMove& move : MoveList<LEGAL>(this->pos)) {
-        if (first) {
-          moves = UCI::move(this->pos, move);
-          first = false;
-        }
-        else
-          moves += " "  + UCI::move(this->pos, move);
+      if (first) {
+        moves = UCI::move(this->pos, move);
+        first = false;
+      }
+      else
+        moves += " "  + UCI::move(this->pos, move);
     }
     return moves;
   }
@@ -95,17 +97,17 @@ public:
     std::string movesSan = "";
     bool first = true;
     for (const ExtMove& move : MoveList<LEGAL>(this->pos)) {
-        if (first) {
-          movesSan = move_to_san(this->pos, move, NOTATION_SAN);
-          first = false;
-        }
-        else
-          movesSan += " "  + move_to_san(this->pos, move, NOTATION_SAN);
+      if (first) {
+        movesSan = move_to_san(this->pos, move, NOTATION_SAN);
+        first = false;
+      }
+      else
+        movesSan += " "  + move_to_san(this->pos, move, NOTATION_SAN);
     }
     return movesSan;
   }
 
-  int number_legal_moves() {
+  int number_legal_moves() const {
     return MoveList<LEGAL>(pos).size();
   }
 
@@ -115,18 +117,17 @@ public:
 
   // TODO: This is a naive implementation which compares all legal SAN moves with the requested string.
   // If the SAN move wasn't found the position remains unchanged. Alternatively, implement a direct conversion.
-   void push_san(std::string sanMove) {
-     Move foundMove = MOVE_NONE;
-     for (const ExtMove& move : MoveList<LEGAL>(pos)) {
-        if (sanMove == move_to_san(this->pos, move, NOTATION_SAN)) {
-          foundMove = move;
-          break;
-        }
-     }
-     if (foundMove != MOVE_NONE)
-        do_move(foundMove);
-   }
-
+  void push_san(std::string sanMove) {
+    Move foundMove = MOVE_NONE;
+    for (const ExtMove& move : MoveList<LEGAL>(pos)) {
+      if (sanMove == move_to_san(this->pos, move, NOTATION_SAN)) {
+        foundMove = move;
+        break;
+      }
+    }
+    if (foundMove != MOVE_NONE)
+      do_move(foundMove);
+  }
 
   void pop() {
     pos.undo_move(this->moveStack.back());
@@ -134,17 +135,17 @@ public:
     states->pop_back();
   }
 
-   void reset() {
-     set_fen(v->startFen);
-   }
+  void reset() {
+    set_fen(v->startFen);
+  }
 
-   bool is_960() {
-     return is960;
-   }
+  bool is_960() const {
+    return is960;
+  }
 
-   std::string fen() const {
-     return this->pos.fen();
-   }
+  std::string fen() const {
+    return this->pos.fen();
+  }
 
   void set_fen(std::string fen) {
     resetStates();
@@ -157,26 +158,86 @@ public:
     return move_to_san(this->pos, UCI::to_move(this->pos, uciMove), NOTATION_SAN);
   }
 
+  std::string san_move(std::string uciMove, Notation notation) {
+    return move_to_san(this->pos, UCI::to_move(this->pos, uciMove), Notation(notation));
+  }
+  std::string variation_san(std::string uciMoves) {
+    return variation_san(uciMoves, NOTATION_SAN, true);
+  }
+
+  std::string variation_san(std::string uciMoves, Notation notation) {
+    return variation_san(uciMoves, notation, true);
+  }
+
+  std::string variation_san(std::string uciMoves, Notation notation, bool moveNumbers) {
+    std::stringstream ss(uciMoves);
+    StateListPtr tempStates;
+    std::vector<Move> moves;
+    std::string variationSan = "";
+    std::string uciMove;
+    bool first = true;
+
+    while (std::getline(ss, uciMove, ' ')) {
+      moves.emplace_back(UCI::to_move(this->pos, uciMove));
+      if (first) {
+        first = false;
+        if (moveNumbers) {
+          variationSan = std::to_string(fullmove_number());
+          if (pos.side_to_move() == WHITE)
+            variationSan += ". ";
+          else
+            variationSan += "...";
+        }
+        variationSan += move_to_san(this->pos, moves.back(), Notation(notation));
+      }
+      else {
+        if (moveNumbers && pos.side_to_move() == WHITE)
+          variationSan += " " + std::to_string(fullmove_number()) + ".";
+        variationSan += " " + move_to_san(this->pos, moves.back(), Notation(notation));
+      }
+      states->emplace_back();
+      pos.do_move(moves.back(), states->back());
+    }
+
+    // recover initial state
+    for(auto rIt = std::rbegin(moves); rIt != std::rend(moves); ++rIt) {
+      pos.undo_move(*rIt);
+    }
+
+    return variationSan;
+  }
+
   // returns true for WHITE and false for BLACK
-  bool turn() {
+  bool turn() const {
     return !pos.side_to_move();
   }
 
-  int halfmove_clock() {
+  int fullmove_number() const {
+    return pos.game_ply() / 2 + 1;
+  }
+
+  int halfmove_clock() const {
     return pos.rule50_count();
   }
 
-  int game_ply() {
+  int game_ply() const {
     return pos.game_ply();
   }
 
-  bool is_game_over() {
-    for (const ExtMove& move : MoveList<LEGAL>(pos)) {
-        return false;
-    }
+  bool is_game_over() const {
+    for (const ExtMove& move: MoveList<LEGAL>(pos))
+      return false;
     return true;
   }
 
+  bool is_check() const {
+    return pos.checkers();
+  }
+
+  bool is_bikjang() const {
+    return pos.bikjang();
+  }
+
   // TODO: return board in ascii notation
   // static std::string get_string_from_instance(const Board& board) {
   // }
@@ -193,14 +254,14 @@ private:
   }
 
   void init(std::string& uciVariant, std::string fen, bool is960) {
-   if (!Board::sfInitialized) {
-        initializeStockfish(uciVariant);
-       Board::sfInitialized = true;
-   }
+    if (!Board::sfInitialized) {
+      initialize_stockfish(uciVariant);
+      Board::sfInitialized = true;
+    }
     this->v = variants.find(uciVariant)->second;
     this->resetStates();
     if (fen == "")
-      fen = v->startFen;
+    fen = v->startFen;
     this->pos.set(this->v, fen, is960, &this->states->back(), this->thread);
     this->is960 = is960;
   }
@@ -208,14 +269,19 @@ private:
 
 // returns the version of the Fairy-Stockfish binary
 std::string info() {
-   return engine_info();
+  return engine_info();
 }
 
 bool Board::sfInitialized = false;
 
+template <typename T>
+void set_option(std::string name, T value) {
+  Options[name] = value;
+  Board::sfInitialized = false;
+}
+
 // binding code
 EMSCRIPTEN_BINDINGS(ffish_js) {
-  function("info", &info);
   class_<Board>("Board")
     .constructor<>()
     .constructor<std::string>()
@@ -231,11 +297,32 @@ EMSCRIPTEN_BINDINGS(ffish_js) {
     .function("is960", &Board::is_960)
     .function("fen", &Board::fen)
     .function("setFen", &Board::set_fen)
-    .function("sanMove", &Board::san_move)
+    .function("sanMove", select_overload<std::string(std::string)>(&Board::san_move))
+    .function("sanMove", select_overload<std::string(std::string, Notation)>(&Board::san_move))
+    .function("variationSan", select_overload<std::string(std::string)>(&Board::variation_san))
+    .function("variationSan", select_overload<std::string(std::string, Notation)>(&Board::variation_san))
+    .function("variationSan", select_overload<std::string(std::string, Notation, bool)>(&Board::variation_san))
     .function("turn", &Board::turn)
+    .function("fullmoveNumber", &Board::fullmove_number)
     .function("halfmoveClock", &Board::halfmove_clock)
     .function("gamePly", &Board::game_ply)
-    .function("isGameOver", &Board::is_game_over);
-    // TODO: enable to string conversion method
-    // .class_function("getStringFromInstance", &Board::get_string_from_instance);
+    .function("isGameOver", &Board::is_game_over)
+    .function("isCheck", &Board::is_check)
+    .function("isBikjang", &Board::is_bikjang);
+  // usage: e.g. ffish.Notation.DEFAULT
+  enum_<Notation>("Notation")
+    .value("DEFAULT", NOTATION_DEFAULT)
+    .value("SAN", NOTATION_SAN)
+    .value("LAN", NOTATION_LAN)
+    .value("SHOGI_HOSKING", NOTATION_SHOGI_HOSKING)
+    .value("SHOGI_HODGES", NOTATION_SHOGI_HODGES)
+    .value("SHOGI_HODGES_NUMBER", NOTATION_SHOGI_HODGES_NUMBER)
+    .value("JANGGI", NOTATION_JANGGI)
+    .value("XIANGQI_WXF", NOTATION_XIANGQI_WXF);
+  function("info", &info);
+  function("setOption", &set_option<std::string>);
+  function("setOptionInt", &set_option<int>);
+  function("setOptionBool", &set_option<bool>);
+  // TODO: enable to string conversion method
+  // .class_function("getStringFromInstance", &Board::get_string_from_instance);
 }
index 8d2f881..443368a 100644 (file)
@@ -1,68 +1,60 @@
-# ffish.js
+<h2 align="center">ffish.js</h2>
 
-A high performance WebAssembly chess variant library based on _Fairy-Stockfish_.
+<p align="center">
+<a href="https://img.shields.io/badge/-ffish.js-green"><img src="https://img.shields.io/badge/-ffish.js-green" alt="Package"></a>
+<a href="https://npmcharts.com/compare/ffish?minimal=true"><img src="https://img.shields.io/npm/dm/ffish.svg?sanitize=true" alt="Downloads"></a>
+<a href="https://www.npmjs.com/package/ffish"><img src="https://img.shields.io/npm/v/ffish.svg?sanitize=true" alt="Version"></a>
+</p>
 
-It is built using emscripten/Embind from C++ source code.
+<p align="center">
+<a href="https://img.shields.io/badge/-ffish--es6.js-green"><img src="https://img.shields.io/badge/-ffish--es6.js-green" alt="Package-ES6"></a>
+<a href="https://npmcharts.com/compare/ffish-es6?minimal=true"><img src="https://img.shields.io/npm/dm/ffish-es6.svg?sanitize=true" alt="Downloads-ES6"></a>
+<a href="https://www.npmjs.com/package/ffish-es6"><img src="https://img.shields.io/npm/v/ffish-es6.svg?sanitize=true" alt="Version-ES6"></a>
+</p>
 
-* https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html
+
+The package **ffish.js** is a high performance WebAssembly chess variant library based on [_Fairy-Stockfish_](https://github.com/ianfab/Fairy-Stockfish).
+
+It is available as a [standard module](https://www.npmjs.com/package/ffish) and as an [ES6 module](https://www.npmjs.com/package/ffish-es6).
 
 ## Install instructions
 
+### Standard module
+
 ```bash
 npm install ffish
 ```
 
-## Build instuctions
-
-```bash
-cd Fairy-Stockfish/src
-```
+### ES6 module
 ```bash
-emcc -O3 --bind -s TOTAL_MEMORY=67108864 -s ALLOW_MEMORY_GROWTH=1 \
- -s WASM_MEM_MAX=2147483648 -DLARGEBOARDS -DPRECOMPUTED_MAGICS \
-ffishjs.cpp \
-benchmark.cpp \
-bitbase.cpp \
-bitboard.cpp \
-endgame.cpp \
-evaluate.cpp \
-material.cpp \
-misc.cpp \
-movegen.cpp \
-movepick.cpp \
-parser.cpp \
-partner.cpp \
-pawns.cpp \
-piece.cpp \
-position.cpp \
-psqt.cpp \
-search.cpp \
-thread.cpp \
-timeman.cpp \
-tt.cpp \
-uci.cpp \
-syzygy/tbprobe.cpp \
-ucioption.cpp \
-variant.cpp \
-xboard.cpp \
--o ../tests/js/ffish.js
+npm install ffish-es6
 ```
 
-If you want to disable variants with a board greater than 8x8,
- you can remove the flags `-s TOTAL_MEMORY=67108864 -s
-  ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=2147483648
-   -DLARGEBOARDS` ``-DPRECOMPUTED_MAGICS`.
-
-The pre-compiled wasm binary is built with `-DLARGEBOARDS`.
-
 ## Examples
 
 Load the API in JavaScript:
 
+### Standard module
+
 ```javascript
 const ffish = require('ffish');
 ```
 
+### ES6 module
+
+```javascript
+import Module from 'ffish';
+let ffish = null;
+
+new Module().then(loadedModule => {
+    ffish = loadedModule;
+    console.log(`initialized ${ffish} ${loadedModule}`);
+    }
+});
+```
+
+### Board object
+
 Create a new variant board from its default starting position.
 The even `onRuntimeInitialized` ensures that the wasm file was properly loaded.
 
@@ -105,21 +97,58 @@ Therefore, you need to call `.delete()` to free the heap memory of an object.
 board.delete();
 ```
 
-## Instructions to run the tests
-```bash
-npm install
-npm test
-```
 
-## Instructions to run the example server
+## Build instuctions
+
+It is built using emscripten/Embind from C++ source code.
+
+* https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html
+
+
+If you want to disable variants with a board greater than 8x8,
+ you can remove the flags `-s TOTAL_MEMORY=67108864 -s
+  ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=2147483648
+   -DLARGEBOARDS -DPRECOMPUTED_MAGICS`.
+
+The pre-compiled wasm binary is built with `-DLARGEBOARDS`.
+
+### Compile as standard module
+
 ```bash
-npm install
+cd Fairy-Stockfish/src
 ```
 ```bash
-node index.js
+emcc -O3 --bind -s TOTAL_MEMORY=67108864 -s ALLOW_MEMORY_GROWTH=1 \
+ -s WASM_MEM_MAX=2147483648 -DLARGEBOARDS -DPRECOMPUTED_MAGICS \
+ffishjs.cpp \
+benchmark.cpp \
+bitbase.cpp \
+bitboard.cpp \
+endgame.cpp \
+evaluate.cpp \
+material.cpp \
+misc.cpp \
+movegen.cpp \
+movepick.cpp \
+parser.cpp \
+partner.cpp \
+pawns.cpp \
+piece.cpp \
+position.cpp \
+psqt.cpp \
+search.cpp \
+thread.cpp \
+timeman.cpp \
+tt.cpp \
+uci.cpp \
+syzygy/tbprobe.cpp \
+ucioption.cpp \
+variant.cpp \
+xboard.cpp \
+-o ../tests/js/ffish.js
 ```
 
-## Compile as ES6/ES2015 module
+### Compile as ES6/ES2015 module
 
 Some environments such as [vue-js](https://vuejs.org/) may require the library to be exported
   as a ES6/ES2015 module.
@@ -160,17 +189,18 @@ xboard.cpp \
 -o ../tests/js/ffish.js
 ```
 
-Later the module can be imported as follows:
+Reference: [emscripten/#10114](https://github.com/emscripten-core/emscripten/issues/10114)
 
-```javascript
-import Module from './ffish.js';
-let ffish = null;
-
-new Module().then(loadedModule => {
-    ffish = loadedModule;
-    console.log(`initialized ${ffish} ${loadedModule}`);
-    }
-});
+## Instructions to run the tests
+```bash
+npm install
+npm test
+```
 
+## Instructions to run the example server
+```bash
+npm install
+```
+```bash
+node index.js
 ```
-References: [emscripten/#10114](https://github.com/emscripten-core/emscripten/issues/10114)
index ee83576..ad237c1 100644 (file)
@@ -1,6 +1,6 @@
 {
   "name": "ffish",
-  "version": "0.1.5",
+  "version": "0.2.0",
   "description": "A high performance WebAssembly chess variant library based on Fairy-Stockfish",
   "main": "ffish.js",
   "scripts": {
       "url": "https://github.com/QueensGambit"
     }
   ],
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/ianfab/Fairy-Stockfish.git"
+  },
   "keywords": ["wasm", "library", "chess-variants", "fairy-stockfish"],
   "license": "GPL-3.0",
   "homepage": "https://github.com/ianfab/Fairy-Stockfish/tree/master/tests/js#readme",
index 177b810..e92e0ea 100644 (file)
@@ -8,7 +8,7 @@ before(() => {
   });
 });
 
-describe('Constructor: no parameter ', function () {
+describe('Board()', function () {
   it("it creates a chess board from the default position", () => {
     const board = new ffish.Board();
     chai.expect(board.fen()).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
@@ -17,7 +17,7 @@ describe('Constructor: no parameter ', function () {
   });
 });
 
-describe('Constructor: variant parameter ', function () {
+describe('Board(uciVariant) ', function () {
   it("it creates a board object from a given UCI-variant", () => {
     const board = new ffish.Board("chess");
     chai.expect(board.fen()).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
@@ -26,7 +26,7 @@ describe('Constructor: variant parameter ', function () {
   });
 });
 
-describe('Constructor: variant parameter with large board', function () {
+describe('Board(uciVariant) with large board', function () {
   it("it creates a large-board object from a given UCI-variant", () => {
     const board = new ffish.Board("xiangqi");
     chai.expect(board.fen()).to.equal("rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1");
@@ -35,7 +35,7 @@ describe('Constructor: variant parameter with large board', function () {
   });
 });
 
-describe('Constructor: variant parameter + fen ', function () {
+describe('Board(uciVariant, fen) ', function () {
   it("it creates a board object for a given UCI-variant with a given FEN", () => {
     const board = new ffish.Board("crazyhouse", "rnbqkb1r/pp3ppp/5p2/2pp4/8/5N2/PPPP1PPP/RNBQKB1R/Np w KQkq - 0 5");
     chai.expect(board.fen()).to.equal("rnbqkb1r/pp3ppp/5p2/2pp4/8/5N2/PPPP1PPP/RNBQKB1R[Np] w KQkq - 0 5");
@@ -44,7 +44,7 @@ describe('Constructor: variant parameter + fen ', function () {
   });
 });
 
-describe('Constructor: variant parameter + fen + is960', function () {
+describe('Board(uciVariant, fen, is960)', function () {
   it("it creates a board object for a given UCI-variant with a given FEN and is960 identifier", () => {
     const board = new ffish.Board("chess", "rnknb1rq/pp2ppbp/3p2p1/2p5/4PP2/2N1N1P1/PPPP3P/R1K1BBRQ b KQkq - 1 5", true);
     chai.expect(board.fen()).to.equal("rnknb1rq/pp2ppbp/3p2p1/2p5/4PP2/2N1N1P1/PPPP3P/R1K1BBRQ b GAga - 1 5");
@@ -87,7 +87,7 @@ describe('board.numberLegalMoves()', function () {
   });
 });
 
-describe('board.push()', function () {
+describe('board.push(uciMove)', function () {
   it("it pushes a move in uci notation to the board", () => {
     let board = new ffish.Board();
     board.push("e2e4");
@@ -156,7 +156,7 @@ describe('board.fen()', function () {
   });
 });
 
-describe('board.setFen()', function () {
+describe('board.setFen(fen)', function () {
   it("it sets a custom position via fen", () => {
     let board = new ffish.Board();
     board.setFen("r1bqkbnr/ppp2ppp/2np4/1B6/3NP3/8/PPP2PPP/RNBQK2R b KQkq - 0 5");
@@ -174,6 +174,51 @@ describe('board.sanMove()', function () {
   });
 });
 
+describe('board.sanMove(ffish.Notation)', function () {
+  it("it converts an uci move into san using a given notation", () => {
+    const board = new ffish.Board();
+    chai.expect(board.sanMove("g1f3", ffish.Notation.DEFAULT)).to.equal("Nf3");
+    chai.expect(board.sanMove("g1f3", ffish.Notation.SAN)).to.equal("Nf3");
+    chai.expect(board.sanMove("g1f3", ffish.Notation.LAN)).to.equal("Ng1-f3");
+    chai.expect(board.sanMove("g1f3", ffish.Notation.SHOGI_HOSKING)).to.equal("N36");
+    chai.expect(board.sanMove("g1f3", ffish.Notation.SHOGI_HODGES)).to.equal("N-3f");
+    chai.expect(board.sanMove("g1f3", ffish.Notation.SHOGI_HODGES_NUMBER)).to.equal("N-36");
+    chai.expect(board.sanMove("g1f3", ffish.Notation.JANGGI)).to.equal("N87-66");
+    chai.expect(board.sanMove("g1f3", ffish.Notation.XIANGQI_WXF)).to.equal("N2+3");
+    board.delete();
+  });
+});
+
+describe('board.variationSan(uciMoves)', function () {
+  it("it converts a list of uci moves into san notation. The board will not changed by this method.", () => {
+    let board = new ffish.Board();
+    board.push("e2e4")
+    const sanMoves = board.variationSan("e7e5 g1f3 b8c6 f1c4");
+    chai.expect(sanMoves).to.equal("1...e5 2. Nf3 Nc6 3. Bc4");
+    board.delete();
+  });
+});
+
+describe('board.variationSan(uciMoves, notation)', function () {
+  it("it converts a list of uci moves into san notation given a notation format", () => {
+    let board = new ffish.Board();
+    board.push("e2e4")
+    const sanMoves = board.variationSan("e7e5 g1f3 b8c6 f1c4", ffish.Notation.LAN);
+    chai.expect(sanMoves).to.equal("1...e7-e5 2. Ng1-f3 Nb8-c6 3. Bf1-c4");
+    board.delete();
+  });
+});
+
+describe('board.variationSan(uciMoves, notation, moveNumbers)', function () {
+  it("it converts a list of uci moves into san notation given a notation format and optionally disabling move numbers", () => {
+    let board = new ffish.Board();
+    board.push("e2e4")
+    const sanMoves = board.variationSan("e7e5 g1f3 b8c6 f1c4", ffish.Notation.SAN, false);
+    chai.expect(sanMoves).to.equal("e5 Nf3 Nc6 Bc4");
+    board.delete();
+  });
+});
+
 describe('board.turn()', function () {
   it("it returns the side to move", () => {
     let board = new ffish.Board();
@@ -184,6 +229,21 @@ describe('board.turn()', function () {
   });
 });
 
+describe('board.fullmoveNumber()', function () {
+  it("it returns the move number starting with 1 and is increment after each move of the 2nd player", () => {
+    let board = new ffish.Board();
+    chai.expect(board.fullmoveNumber()).to.equal(1);
+    board.push("e2e4");
+    chai.expect(board.fullmoveNumber()).to.equal(1);
+    board.push("e7e5");
+    board.push("g1f3");
+    board.push("g8f6");
+    board.push("f3e5");
+    chai.expect(board.fullmoveNumber()).to.equal(3);
+    board.delete();
+  });
+});
+
 describe('board.halfmoveClock()', function () {
   it("it returns the halfmoveClock / 50-move-rule-counter", () => {
     let board = new ffish.Board();
@@ -226,8 +286,52 @@ describe('board.isGameOver()', function () {
   });
 });
 
+describe('board.isCheck()', function () {
+  it("it checks if a player is in check", () => {
+    let board = new ffish.Board();
+    chai.expect(board.isCheck()).to.equal(false);
+    board.setFen("rnbqkb1r/pppp1Bpp/5n2/4p3/4P3/8/PPPP1PPP/RNBQK1NR b KQkq - 0 3");
+    chai.expect(board.isCheck()).to.equal(true);
+    board.setFen("r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 4 4");
+    board.pushSan("Qxf7#");
+    chai.expect(board.isCheck()).to.equal(true);
+    board.delete();
+  });
+});
+
+describe('board.isBikjang()', function () {
+  it("it checks if a player is in bikjang (only relevant for janggi)", () => {
+    let board = new ffish.Board("janggi");
+    chai.expect(board.isBikjang()).to.equal(false);
+    board.setFen("rnba1abnr/4k4/1c5c1/p1p3p1p/9/9/P1P3P1P/1C5C1/4K4/RNBA1ABNR w - - 0 1");
+    chai.expect(board.isBikjang()).to.equal(true);
+    board.delete();
+  });
+});
+
 describe('ffish.info()', function () {
   it("it returns the version of the Fairy-Stockfish binary", () => {
     chai.expect(ffish.info()).to.be.a('string');
   });
 });
+
+describe('ffish.setOption(name, value)', function () {
+  it("it sets a string uci option value pair", () => {
+    ffish.setOption("VariantPath", "variants.ini");
+    chai.expect(true).to.equal(true);
+  });
+});
+
+describe('ffish.setOptionInt(name, value)', function () {
+  it("it sets a int uci option value pair", () => {
+    ffish.setOptionInt("Threads", 4);
+    chai.expect(true).to.equal(true);
+  });
+});
+
+describe('ffish.setOptionBool(name, value)', function () {
+  it("it sets a boolean uci option value pair", () => {
+    ffish.setOptionBool("Ponder", true);
+    chai.expect(true).to.equal(true);
+  });
+});