diff --git a/.github/workflows/ffishjs.yml b/.github/workflows/ffishjs.yml index e02c7fc67..a6fba4ed3 100644 --- a/.github/workflows/ffishjs.yml +++ b/.github/workflows/ffishjs.yml @@ -43,3 +43,48 @@ jobs: - name: Run unit tests working-directory: tests/js run: npm test + + build: + runs-on: ubuntu-20.04 + needs: [test] #Building process must start after successful testing process + + strategy: + matrix: + node-version: [12.x] + + steps: + - uses: actions/checkout@v4 + - name: Setup cache + id: cache-system-libraries + uses: actions/cache@v2 + with: + path: ${{env.EM_CACHE_FOLDER}} + key: emsdk-${{env.EM_VERSION}}-${{ runner.os }} + - uses: mymindstorm/setup-emsdk@v7 + with: + version: ${{env.EM_VERSION}} + actions-cache-folder: ${{env.EM_CACHE_FOLDER}} + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Build ffish.js ES6/ES2015 module + working-directory: src + run: rm -f ../tests/js/ffish.js & rm -f ../tests/js/ffish.wasm & make -f Makefile_js build es6=yes + - name: Upload ffish.js ES6/ES2015 module ZIP archive + uses: actions/upload-artifact@v4 + with: + name: ffishjs-es6 + path: tests/js/* + if-no-files-found: error + compression-level: 9 + - name: Build ffish.js standard module + working-directory: src + run: rm -f ../tests/js/ffish.js & rm -f ../tests/js/ffish.wasm & make -f Makefile_js build + - name: Upload ffish.js standard module ZIP archive + uses: actions/upload-artifact@v4 + with: + name: ffishjs-standard + path: tests/js/* + if-no-files-found: error + compression-level: 9 diff --git a/setup.py b/setup.py index bcbd9e481..d8aa3ea9c 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ sources=sources, extra_compile_args=args) -setup(name="pyffish", version="0.0.84", +setup(name="pyffish", version="0.0.85", description="Fairy-Stockfish Python wrapper", long_description=long_description, long_description_content_type="text/markdown", diff --git a/src/position.cpp b/src/position.cpp index 81fa3c774..bd4adc828 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -681,7 +681,7 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { /// Position::fen() returns a FEN representation of the position. In case of /// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. -string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string holdings) const { +string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string holdings, Bitboard fogArea) const { int emptyCnt; std::ostringstream ss; @@ -690,7 +690,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string { for (File f = FILE_A; f <= max_file(); ++f) { - for (emptyCnt = 0; f <= max_file() && !(pieces() & make_square(f, r)); ++f) + for (emptyCnt = 0; f <= max_file() && !(pieces() & make_square(f, r)) && !(fogArea & make_square(f, r)); ++f) ++emptyCnt; if (emptyCnt) @@ -698,7 +698,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string if (f <= max_file()) { - if (empty(make_square(f, r))) + if (empty(make_square(f, r)) || fogArea & make_square(f, r)) // Wall square ss << "*"; else if (unpromoted_piece_on(make_square(f, r))) diff --git a/src/position.h b/src/position.h index 8868d7646..9818ede2b 100644 --- a/src/position.h +++ b/src/position.h @@ -114,7 +114,7 @@ class Position { // FEN string input/output Position& set(const Variant* v, const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th, bool sfen = false); Position& set(const std::string& code, Color c, StateInfo* si); - std::string fen(bool sfen = false, bool showPromoted = false, int countStarted = 0, std::string holdings = "-") const; + std::string fen(bool sfen = false, bool showPromoted = false, int countStarted = 0, std::string holdings = "-", Bitboard fogArea = 0) const; // Variant rule properties const Variant* variant() const; @@ -328,6 +328,7 @@ class Position { Score psq_score() const; Value non_pawn_material(Color c) const; Value non_pawn_material() const; + Bitboard fog_area() const; // Position consistency check, for debugging bool pos_is_ok() const; @@ -1407,6 +1408,20 @@ inline Piece Position::captured_piece() const { return st->capturedPiece; } +inline Bitboard Position::fog_area() const { + Bitboard b = board_bb(); + // Our own pieces are visible + Bitboard visible = pieces(sideToMove); + // Squares where we can move to are visible as well + for (const auto& m : MoveList(*this)) + { + Square to = to_sq(m); + visible |= to; + } + // Everything else is invisible + return ~visible & b; +} + inline const std::string Position::piece_to_partner() const { if (!st->capturedPiece) return std::string(); Color color = color_of(st->capturedPiece); diff --git a/src/pyffish.cpp b/src/pyffish.cpp index ed1f487a3..0c297190e 100644 --- a/src/pyffish.cpp +++ b/src/pyffish.cpp @@ -54,7 +54,7 @@ void buildPosition(Position& pos, StateListPtr& states, const char *variant, con } extern "C" PyObject* pyffish_version(PyObject* self) { - return Py_BuildValue("(iii)", 0, 0, 84); + return Py_BuildValue("(iii)", 0, 0, 85); } extern "C" PyObject* pyffish_info(PyObject* self) { @@ -383,6 +383,22 @@ extern "C" PyObject* pyffish_validateFen(PyObject* self, PyObject *args) { return Py_BuildValue("i", FEN::validate_fen(std::string(fen), variants.find(std::string(variant))->second, chess960)); } +// INPUT variant, fen +extern "C" PyObject* pyffish_getFogFEN(PyObject* self, PyObject *args) { + PyObject* moveList = PyList_New(0); + Position pos; + const char *fen, *variant; + + int chess960 = false, sfen = false, showPromoted = false, countStarted = 0; + if (!PyArg_ParseTuple(args, "ss|p", &fen, &variant, &chess960)) { + return NULL; + } + StateListPtr states(new std::deque(1)); + buildPosition(pos, states, variant, fen, moveList, chess960); + + Py_XDECREF(moveList); + return Py_BuildValue("s", pos.fen(sfen, showPromoted, countStarted, "-", pos.fog_area()).c_str()); +} static PyMethodDef PyFFishMethods[] = { {"version", (PyCFunction)pyffish_version, METH_NOARGS, "Get package version."}, @@ -405,6 +421,7 @@ static PyMethodDef PyFFishMethods[] = { {"is_optional_game_end", (PyCFunction)pyffish_isOptionalGameEnd, METH_VARARGS, "Get result from given FEN it rules enable game end by player."}, {"has_insufficient_material", (PyCFunction)pyffish_hasInsufficientMaterial, METH_VARARGS, "Checks for insufficient material."}, {"validate_fen", (PyCFunction)pyffish_validateFen, METH_VARARGS, "Validate an input FEN."}, + {"get_fog_fen", (PyCFunction)pyffish_getFogFEN, METH_VARARGS, "Get Fog of War FEN from given FEN."}, {NULL, NULL, 0, NULL}, // sentinel }; diff --git a/src/variants.ini b/src/variants.ini index d905dcb03..afbeacdfb 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -2010,3 +2010,37 @@ passOnStalemate = true [andersanti:antichess] passOnStalemate = true + +# A 10x10 drop variant consisting of only Leapers, Shogi pawns, and Kings. +[leaperhouse] +variantTemplate = shogi +pieceToCharTable = PN...OU.CR.+Kpn...ou.cr.+k +maxFile = 10 +maxRank = 10 +pocketSize = 6 +startFen = oncurkucno/10/pppppppppp/10/10/10/10/PPPPPPPPPP/10/ONCUKRUCNO[] w - - 0 1 +pieceDrops = true +capturesToHand = true +shogiPawn = p +knight = n +customPiece1 = u:Z +customPiece2 = c:C +customPiece3 = r:GH +customPiece4 = o:AD +commoner = g +king = k +promotionRegionWhite = *8 *9 *10 +promotionRegionBlack = *3 *2 *1 +promotedPieceType = p:g +doubleStep = false +castling = false +perpetualCheckIllegal = true +dropNoDoubled = p +stalemateValue = loss +nMoveRule = 0 +nFoldValue = loss +flagPiece = k +flagRegionWhite = *10 +flagRegionBlack = *1 +immobilityIllegal = true +mandatoryPiecePromotion = true diff --git a/test.py b/test.py index 03f656b64..0999589d7 100644 --- a/test.py +++ b/test.py @@ -113,6 +113,13 @@ customPiece5 = f:mBpBmWpR2 promotedPieceType = u:w a:w c:f i:f startFen = lnsgkgsnl/1rci1uab1/p1p1p1p1p/9/9/9/P1P1P1P1P/1BAU1ICR1/LNSGKGSNL[-] w 0 1 + +[fogofwar:chess] +king = - +commoner = k +castlingKingPiece = k +extinctionValue = loss +extinctionPieceTypes = k """ sf.load_variant_config(ini_text) @@ -1156,5 +1163,15 @@ def test_validate_fen(self): fen = sf.start_fen(variant) self.assertEqual(sf.validate_fen(fen, variant), sf.FEN_OK) + def test_get_fog_fen(self): + fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # startpos + result = sf.get_fog_fen(fen, "fogofwar") + self.assertEqual(result, "********/********/********/********/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") + + fen = "rnbqkbnr/p1p2ppp/8/Pp1pp3/4P3/8/1PPP1PPP/RNBQKBNR w KQkq b6 0 1" + result = sf.get_fog_fen(fen, "fogofwar") + self.assertEqual(result, "********/********/2******/Pp*p***1/4P3/4*3/1PPP1PPP/RNBQKBNR w KQkq b6 0 1") + + if __name__ == '__main__': unittest.main(verbosity=2)