Add Board.checkedPieces() to ffish.js (#626)
authorAda Joule <ada.fulmina@gmail.com>
Sun, 2 Apr 2023 19:25:37 +0000 (02:25 +0700)
committerGitHub <noreply@github.com>
Sun, 2 Apr 2023 19:25:37 +0000 (21:25 +0200)
src/apiutil.h
src/ffishjs.cpp
src/position.cpp
src/position.h
src/pyffish.cpp
tests/js/ffish.d.ts
tests/js/test.js

index 6f36579..988ff5e 100644 (file)
@@ -407,9 +407,9 @@ inline bool has_insufficient_material(Color c, const Position& pos) {
     return true;
 }
 
-inline bool is_check(const Position& pos) {
-    return pos.checkers()
-        || (pos.extinction_pseudo_royal() && pos.attackers_to_pseudo_royals(~pos.side_to_move()));
+inline Bitboard checked(const Position& pos) {
+    return (pos.checkers() ? square_bb(pos.square<KING>(pos.side_to_move())) : Bitboard(0))
+        | (pos.extinction_pseudo_royal() ? pos.checked_pseudo_royals(pos.side_to_move()) : Bitboard(0));
 }
 
 namespace FEN {
index c781511..7e4f26d 100644 (file)
@@ -328,8 +328,20 @@ public:
       return "0-1";
   }
 
+  std::string checked_pieces() const {
+    Bitboard checked = Stockfish::checked(pos);
+    std::string squares;
+    while (checked) {
+      Square sr = pop_lsb(checked);
+      squares += UCI::square(pos, sr);
+      squares += DELIM;
+    }
+    save_pop_back(squares);
+    return squares;
+  }
+
   bool is_check() const {
-    return Stockfish::is_check(pos);
+    return Stockfish::checked(pos);
   }
 
   bool is_bikjang() const {
@@ -706,6 +718,7 @@ EMSCRIPTEN_BINDINGS(ffish_js) {
     .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("checkedPieces", &Board::checked_pieces)
     .function("isCheck", &Board::is_check)
     .function("isBikjang", &Board::is_bikjang)
     .function("isCapture", &Board::is_capture)
index b1f9082..72afba8 100644 (file)
@@ -983,41 +983,39 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const {
   return attackers_to(s, occupied, WHITE) | attackers_to(s, occupied, BLACK);
 }
 
-/// Position::attackers_to_pseudo_royals computes a bitboard of all pieces
-/// of a particular color attacking at least one opposing pseudo-royal piece
-Bitboard Position::attackers_to_pseudo_royals(Color c) const {
+/// Position::checked_pseudo_royals computes a bitboard of
+/// all pseudo-royal pieces of a particular color that are in check
+Bitboard Position::checked_pseudo_royals(Color c) const {
   assert(extinction_pseudo_royal());
-  Bitboard attackers = 0;
-  Bitboard pseudoRoyals = st->pseudoRoyals & pieces(~c);
-  Bitboard pseudoRoyalsTheirs = st->pseudoRoyals & pieces(c);
+  Bitboard checked = 0;
+  Bitboard pseudoRoyals = st->pseudoRoyals & pieces(c);
+  Bitboard pseudoRoyalsTheirs = st->pseudoRoyals & pieces(~c);
   while (pseudoRoyals)
   {
       Square sr = pop_lsb(pseudoRoyals);
-      if (   blast_on_capture()
-          && pseudoRoyalsTheirs & attacks_bb<KING>(sr))
-          // skip if capturing this piece would blast all of the attacker's pseudo-royal pieces
-          continue;
-      attackers |= attackers_to(sr, c);
+      // skip if capturing this piece would blast any of the attacker's pseudo-royal pieces
+      if (!(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb<KING>(sr)))
+          && attackers_to(sr, ~c))
+          checked |= sr;
   }
   // Look for duple check
   if (var->dupleCheck)
   {
-      Bitboard b;
-      Bitboard allAttackers = 0;
-      Bitboard pseudoRoyalCandidates = st->pseudoRoyalCandidates & pieces(~c);
+      Bitboard allAttacked = 0;
+      Bitboard pseudoRoyalCandidates = st->pseudoRoyalCandidates & pieces(c);
       while (pseudoRoyalCandidates)
       {
           Square sr = pop_lsb(pseudoRoyalCandidates);
           if (!(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb<KING>(sr)))
-              && (b = attackers_to(sr, c)))
-              allAttackers |= b;
+              && attackers_to(sr, ~c))
+              allAttacked |= sr;
           else
               // If at least one isn't attacked, it is not a duple check
-              return attackers;
+              return checked;
       }
-      attackers |= allAttackers;
+      checked |= allAttacked;
   }
-  return attackers;
+  return checked;
 }
 
 
index 2fe0b7e..7fe9951 100644 (file)
@@ -246,7 +246,7 @@ public:
   Bitboard blockers_for_king(Color c) const;
   Bitboard check_squares(PieceType pt) const;
   Bitboard pinners(Color c) const;
-  Bitboard attackers_to_pseudo_royals(Color c) const;
+  Bitboard checked_pseudo_royals(Color c) const;
 
   // Attacks to/from a given square
   Bitboard attackers_to(Square s) const;
index 09a8a54..537148c 100644 (file)
@@ -259,7 +259,7 @@ extern "C" PyObject* pyffish_givesCheck(PyObject* self, PyObject *args) {
 
     StateListPtr states(new std::deque<StateInfo>(1));
     buildPosition(pos, states, variant, fen, moveList, chess960);
-    return Py_BuildValue("O", Stockfish::is_check(pos) ? Py_True : Py_False);
+    return Py_BuildValue("O", Stockfish::checked(pos) ? Py_True : Py_False);
 }
 
 // INPUT variant, fen, move list, move
index 5f62852..f92fff4 100644 (file)
@@ -59,6 +59,7 @@ export interface Board {
     isInsufficientMaterial(): boolean;
     isGameOver(claimDraw?: boolean): boolean;
     result(claimDraw?: boolean): string;
+    checkedPieces(): string;
     isCheck(): boolean;
     isBikjang(): boolean;
     isCapture(uciMove: string): boolean;
index 0f268c1..88f4c59 100644 (file)
@@ -474,6 +474,51 @@ describe('board.result()', function () {
   })
 })
 
+describe('board.checkedPieces()', function () {
+  it("it returns the squares of all checked royal pieces in a concatenated string", () => {
+    let board = new ffish.Board();
+    chai.expect(board.checkedPieces()).to.equal("");
+    board.setFen("rnbqkb1r/pppp1Bpp/5n2/4p3/4P3/8/PPPP1PPP/RNBQK1NR b KQkq - 0 3");
+    chai.expect(board.checkedPieces()).to.equal("e8");
+    board.setFen("r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 4 4");
+    board.pushSan("Qxf7#");
+    chai.expect(board.checkedPieces()).to.equal("e8");
+    board.delete();
+
+    board = new ffish.Board("atomic");
+    chai.expect(board.checkedPieces()).to.equal("");
+    board.setFen("rnbqkbnr/ppp1pppp/8/1B1p4/4P3/8/PPPP1PPP/RNBQK1NR b KQkq - 3 2");
+    chai.expect(board.checkedPieces()).to.equal("e8");
+    board.setFen("rnbqkbnr/ppp2ppp/8/8/8/8/PPP2PPP/RNBQKBNR w KQkq - 0 4");
+    board.pushSan("Qd7");
+    chai.expect(board.checkedPieces()).to.equal("e8");
+    board.setFen("8/8/kK6/8/8/8/Q7/8 b - - 0 1")
+    chai.expect(board.checkedPieces()).to.equal("");
+    board.delete();
+
+    board = new ffish.Board("spartan");
+    chai.expect(board.checkedPieces()).to.equal("");
+    board.setFen("lgkcckw1/hhhhhhhh/1N3lN1/8/8/8/PPPPPPPP/R1BQKB1R b KQ - 11 6");
+    chai.expect(board.checkedPieces().split(' ').sort().join()).to.equal("c8,f8");
+    chai.expect(board.isCheck()).to.equal(true);
+    board.setFen("lgkcckwl/hhhhhhhh/6N1/8/8/8/PPPPPPPP/RNBQKB1R b KQ - 5 3")
+    chai.expect(board.checkedPieces()).to.equal("");
+    board.delete();
+
+    board = new ffish.Board("shako");
+    board.setFen("10/5r4/2p3pBk1/1p6Pr/p3p5/9e/1PP2P4/P2P2PP2/ER3K2R1/8C1 w K - 7 38")
+    board.pushMoves("f2h2");
+    chai.expect(board.checkedPieces()).to.equal("i8");
+    board.delete();
+
+    board = new ffish.Board("janggi");
+    board.setFen("4ka3/4a4/9/4R4/2B6/9/9/5K3/4p4/3r5 b - - 0 113")
+    board.pushMoves("e2f2");
+    chai.expect(board.checkedPieces()).to.equal("f3");
+    board.delete();
+  })
+})
+
 describe('board.isCheck()', function () {
   it("it checks if a player is in check", () => {
     let board = new ffish.Board();
@@ -495,8 +540,29 @@ describe('board.isCheck()', function () {
     board.setFen("8/8/kK6/8/8/8/Q7/8 b - - 0 1")
     chai.expect(board.isCheck()).to.equal(false);
     board.delete();
-  });
-});
+
+    board = new ffish.Board("spartan");
+    board.setFen("lgkcckw1/hhhhhhhh/1N3lN1/8/8/8/PPPPPPPP/R1BQKB1R b KQ - 11 6");
+    chai.expect(board.isCheck()).to.equal(true);
+    board.setFen("lgkcckwl/hhhhhhhh/6N1/8/8/8/PPPPPPPP/RNBQKB1R b KQ - 5 3")
+    chai.expect(board.isCheck()).to.equal(false);
+    board.setFen("lgkcckwl/hhhhhhhh/8/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1")
+    chai.expect(board.isCheck()).to.equal(false);
+    board.delete();
+
+    board = new ffish.Board("shako");
+    board.setFen("10/5r4/2p3pBk1/1p6Pr/p3p5/9e/1PP2P4/P2P2PP2/ER3K2R1/8C1 w K - 7 38")
+    board.pushMoves("f2h2");
+    chai.expect(board.isCheck()).to.equal(true);
+    board.delete();
+
+    board = new ffish.Board("janggi");
+    board.setFen("4ka3/4a4/9/4R4/2B6/9/9/5K3/4p4/3r5 b - - 0 113")
+    board.pushMoves("e2f2");
+    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)", () => {