return pos.game_ply();
}
+ bool has_insufficient_material(bool turn) const {
+ return Stockfish::has_insufficient_material(turn ? WHITE : BLACK, pos);
+ }
+
+ bool is_insufficient_material() const {
+ return Stockfish::has_insufficient_material(WHITE, pos) && Stockfish::has_insufficient_material(BLACK, pos);
+ }
+
bool is_game_over() const {
- for (const ExtMove& move: MoveList<LEGAL>(pos))
- return false;
- return true;
+ return is_game_over(false);
+ }
+
+ bool is_game_over(bool claim_draw) const {
+ if (is_insufficient_material())
+ return true;
+ if (claim_draw && pos.is_optional_game_end())
+ return true;
+ return MoveList<LEGAL>(pos).size() == 0;
+ }
+
+ std::string result() const {
+ return result(false);
+ }
+
+ std::string result(bool claim_draw) const {
+ Value result;
+ bool gameEnd = pos.is_immediate_game_end(result);
+ if (!gameEnd) {
+ if (is_insufficient_material()) {
+ gameEnd = true;
+ result = VALUE_DRAW;
+ }
+ }
+ if (!gameEnd && MoveList<LEGAL>(pos).size() == 0) {
+ gameEnd = true;
+ result = pos.checkers() ? pos.checkmate_value() : pos.stalemate_value();
+ }
+ if (!gameEnd && claim_draw)
+ gameEnd = pos.is_optional_game_end(result);
+
+ if (!gameEnd)
+ return "*";
+ if (result == 0) {
+ if (pos.material_counting())
+ result = pos.material_counting_result();
+
+ if (result == 0)
+ return "1/2-1/2";
+ }
+ if (pos.side_to_move() == BLACK)
+ result = -result;
+ if (result > 0)
+ return "1-0";
+ else
+ return "0-1";
}
bool is_check() const {
.function("fullmoveNumber", &Board::fullmove_number)
.function("halfmoveClock", &Board::halfmove_clock)
.function("gamePly", &Board::game_ply)
- .function("isGameOver", &Board::is_game_over)
+ .function("hasInsufficientMaterial", &Board::has_insufficient_material)
+ .function("isInsufficientMaterial", &Board::is_insufficient_material)
+ .function("isGameOver", select_overload<bool() const>(&Board::is_game_over))
+ .function("isGameOver", select_overload<bool(bool) const>(&Board::is_game_over))
+ .function("result", select_overload<std::string() const>(&Board::result))
+ .function("result", select_overload<std::string(bool) const>(&Board::result))
.function("isCheck", &Board::is_check)
.function("isBikjang", &Board::is_bikjang)
.function("moveStack", &Board::move_stack)
.value("SHOGI_HODGES_NUMBER", NOTATION_SHOGI_HODGES_NUMBER)
.value("JANGGI", NOTATION_JANGGI)
.value("XIANGQI_WXF", NOTATION_XIANGQI_WXF);
+ // usage: e.g. ffish.Termination.CHECKMATE
+ enum_<Termination>("Termination")
+ .value("ONGOING", ONGOING)
+ .value("CHECKMATE", CHECKMATE)
+ .value("STALEMATE", STALEMATE)
+ .value("INSUFFICIENT_MATERIAL", INSUFFICIENT_MATERIAL)
+ .value("N_MOVE_RULE", N_MOVE_RULE)
+ .value("N_FOLD_REPETITION", N_FOLD_REPETITION)
+ .value("VARIANT_END", VARIANT_END);
function("info", &ffish::info);
function("setOption", &ffish::set_option<std::string>);
function("setOptionInt", &ffish::set_option<int>);
});
});
+describe('board.hasInsufficientMaterial(side)', function () {
+ it("it returns if the given side has insufficient mating material", () => {
+ let board = new ffish.Board();
+ chai.expect(board.hasInsufficientMaterial(true)).to.equal(false);
+ chai.expect(board.hasInsufficientMaterial(false)).to.equal(false);
+ board.setFen("8/5k2/8/8/8/2K5/6R1/8 w - - 0 1");
+ chai.expect(board.hasInsufficientMaterial(true)).to.equal(false);
+ chai.expect(board.hasInsufficientMaterial(false)).to.equal(true);
+ board.setFen("8/5k2/8/8/8/2K5/6q1/8 w - - 0 1");
+ chai.expect(board.hasInsufficientMaterial(true)).to.equal(true);
+ chai.expect(board.hasInsufficientMaterial(false)).to.equal(false);
+ board.setFen("8/5k2/8/8/8/2K5/6B1/8 w - - 0 1");
+ chai.expect(board.hasInsufficientMaterial(true)).to.equal(true);
+ chai.expect(board.hasInsufficientMaterial(false)).to.equal(true);
+ board.delete();
+ });
+});
+
+describe('board.isInsufficientMaterial()', function () {
+ it("it returns if the game is drawn due to insufficient material", () => {
+ let board = new ffish.Board();
+ chai.expect(board.isInsufficientMaterial()).to.equal(false);
+ board.setFen("8/5k2/8/8/8/2K5/6R1/8 w - - 0 1");
+ chai.expect(board.isInsufficientMaterial()).to.equal(false);
+ board.setFen("8/5k2/8/8/8/2K5/6q1/8 w - - 0 1");
+ chai.expect(board.isInsufficientMaterial()).to.equal(false);
+ board.setFen("8/5k2/8/8/8/2K5/6B1/8 w - - 0 1");
+ chai.expect(board.isInsufficientMaterial()).to.equal(true);
+ board.delete();
+ });
+});
+
describe('board.isGameOver()', function () {
- it("it checks if the game is over based on the number of legal moves", () => {
+ it("it checks if the game is over", () => {
+ // No legal moves
let board = new ffish.Board();
chai.expect(board.isGameOver()).to.equal(false);
board.setFen("r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 4 4");
board.pushSan("Qxf7#");
chai.expect(board.isGameOver()).to.equal(true);
+
+ // Insufficient material
+ board.setFen("3Rk3/8/8/8/8/8/2N5/3K4 b - - 0 1");
+ chai.expect(board.isGameOver()).to.equal(false);
+ board.pushSan("Kxd8");
+ chai.expect(board.isGameOver()).to.equal(true);
+
+ // Optional draw claimed
+ board.reset();
+ board.pushSanMoves("Nf3 Nc6 Ng1 Nb8 Nf3 Nc6 Ng1");
+ chai.expect(board.isGameOver(false)).to.equal(false);
+ chai.expect(board.isGameOver(true)).to.equal(false);
+ board.pushSan("Nb8");
+ chai.expect(board.isGameOver(false)).to.equal(false);
+ chai.expect(board.isGameOver(true)).to.equal(true);
board.delete();
});
});
+describe('board.result()', function () {
+ it("it returns a string representing the winner of the game", () => {
+ // Scholar's mate (win for white)
+ let board = new ffish.Board();
+ chai.expect(board.result()).to.equal("*");
+ board.pushSanMoves("e4 e5 Bc4 Nc6 Qh5 Nf6");
+ chai.expect(board.result()).to.equal("*");
+ board.pushSan("Qxf7#");
+ chai.expect(board.result()).to.equal("1-0");
+
+ // Fool's mate (win for black)
+ board.reset();
+ board.pushSanMoves("f3 e5 g4");
+ chai.expect(board.result()).to.equal("*");
+ board.pushSan("Qh4#");
+ chai.expect(board.result()).to.equal("0-1");
+
+ // Stalemate
+ board.setFen("2Q2bnr/4p1pq/5pkr/7p/7P/4P3/PPPP1PP1/RNB1KBNR w KQ - 1 10");
+ chai.expect(board.result()).to.equal("*");
+ board.pushSan("Qe6");
+ chai.expect(board.result()).to.equal("1/2-1/2");
+
+ // Draw claimed by n-fold repetition
+ board.reset();
+ board.pushSanMoves("Nf3 Nc6 Ng1 Nb8 Nf3 Nc6 Ng1");
+ chai.expect(board.result(false)).to.equal("*");
+ chai.expect(board.result(true)).to.equal("*");
+ board.pushSan("Nb8");
+ chai.expect(board.result(false)).to.equal("*");
+ chai.expect(board.result(true)).to.equal("1/2-1/2");
+
+ // Draw claimed by n-move rule
+ board.setFen("rnbqkbn1/ppppppp1/6r1/7p/2R4P/8/PPPPPPP1/RNBQKBN1 b Qq - 99 51");
+ chai.expect(board.result(false)).to.equal("*");
+ chai.expect(board.result(true)).to.equal("*");
+ board.pushSan("Rh6");
+ chai.expect(board.result(false)).to.equal("*");
+ chai.expect(board.result(true)).to.equal("1/2-1/2");
+
+ // Insufficient material
+ board.setFen("3Rk3/8/8/8/8/8/2N5/3K4 b - - 0 1");
+ chai.expect(board.result()).to.equal("*");
+ board.pushSan("Kxd8");
+ chai.expect(board.result()).to.equal("1/2-1/2");
+
+ // Insufficient material with material counting - black draw odds (armageddon)
+ board.delete();
+ board = new ffish.Board("armageddon");
+ board.setFen("3Rk3/8/8/8/8/8/2N5/3K4 b - - 0 1");
+ chai.expect(board.result()).to.equal("*");
+ board.pushSan("Kxd8");
+ chai.expect(board.result()).to.equal("0-1");
+
+ // Stalemate with material counting - black draw odds (armageddon)
+ board.setFen("2Q2bnr/4p1pq/5pkr/7p/7P/4P3/PPPP1PP1/RNB1KBNR w KQ - 1 10");
+ chai.expect(board.result()).to.equal("*");
+ board.pushSan("Qe6");
+ chai.expect(board.result()).to.equal("0-1");
+
+ // Atomic chess exploded king (variant ending)
+ board.delete();
+ board = new ffish.Board("atomic");
+ board.pushMoves("e2e4 e7e5 d1h5 a7a6");
+ chai.expect(board.result()).to.equal("*");
+ board.push("h5f7");
+ chai.expect(board.result()).to.equal("1-0");
+
+ // Exploded king AND insufficient material - exploded king takes priority
+ board.setFen("3qk3/8/8/8/8/8/8/3QK3 w - - 0 1");
+ chai.expect(board.result()).to.equal("*");
+ board.push("d1d8");
+ chai.expect(board.result()).to.equal("1-0");
+ board.delete();
+ })
+})
+
describe('board.isCheck()', function () {
it("it checks if a player is in check", () => {
let board = new ffish.Board();