From: Fabian Fichter Date: Sun, 31 Jan 2021 13:59:24 +0000 (+0100) Subject: Merge official-stockfish/master X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=49ada69632c4dca9a38cd9cd8f4983a55bf3a7d5;p=fairystockfish.git Merge official-stockfish/master bench: 4622210 --- 49ada69632c4dca9a38cd9cd8f4983a55bf3a7d5 diff --cc .gitignore index 622e651,8981efc..b045cda --- a/.gitignore +++ b/.gitignore @@@ -1,20 -1,12 +1,30 @@@ - stockfish - *.o - .depend - *.nnue + # Files from build + **/*.o + **/*.s + src/.depend + + # Built binary + src/stockfish* + src/-lstdc++.res + + # Neural network for the NNUE evaluation + **/*.nnue + ++ ++# Build/test artifacts +*.exe +tests/syzygy + +# ffishjs +tests/js/node_modules +ffish.js +*.wasm + +# pyffish +venv +*.so +pyffish.egg-info + +# IDEs +.vscode - .idea ++.idea diff --cc src/Makefile_js index 7e5ff9c,0000000..1e0c34d mode 100644,000000..100644 --- a/src/Makefile_js +++ b/src/Makefile_js @@@ -1,61 -1,0 +1,61 @@@ +# ffish.js, a JavaScript chess variant library derived from Fairy-Stockfish - # Copyright (C) 2020 Fabian Fichter, Johannes Czech ++# Copyright (C) 2021 Fabian Fichter, Johannes Czech +# +# ffish.js is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ffish.js is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +EXE = ../tests/js/ffish.js + +SRCS = ffishjs.cpp benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp \ + material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \ + search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ + nnue/evaluate_nnue.cpp nnue/features/half_kp.cpp \ + partner.cpp parser.cpp piece.cpp variant.cpp xboard.cpp + +CXX=emcc +CXXFLAGS += --bind -DNNUE_EMBEDDING_OFF -DNO_THREADS -std=c++17 -Wall + +largeboards = yes +optimize = yes +debug = no + +### Debugging +ifeq ($(debug),no) + CXXFLAGS += -DNDEBUG -s ASSERTIONS=0 -s SAFE_HEAP=0 +else + CXXFLAGS += -g -s ASSERTIONS=1 -s SAFE_HEAP=1 +endif + +### Optimization +ifeq ($(optimize),yes) + CXXFLAGS += -O3 +endif + +# Compile version with support for large board variants +# Use precomputed magics by default +ifneq ($(largeboards),no) + CXXFLAGS += -DLARGEBOARDS -DPRECOMPUTED_MAGICS -s TOTAL_MEMORY=32MB -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=1GB +endif + +### Compile as ES6/ES2015 module +ifeq ($(es6),yes) + CXXFLAGS += -s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0 +endif + +objclean: + @rm -f $(EXE) *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o + +clean: objclean + +build: + $(CXX) $(CXXFLAGS) $(SRCS) -o $(EXE) diff --cc src/apiutil.h index 57a5f11,0000000..dc9b8fb mode 100644,000000..100644 --- a/src/apiutil.h +++ b/src/apiutil.h @@@ -1,830 -1,0 +1,830 @@@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish - Copyright (C) 2018-2020 Fabian Fichter ++ Copyright (C) 2018-2021 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include +#include +#include +#include +#include +#include + +#include "types.h" +#include "variant.h" + + +namespace PSQT { +void init(const Variant* v); +} + +enum Notation { + NOTATION_DEFAULT, + // https://en.wikipedia.org/wiki/Algebraic_notation_(chess) + NOTATION_SAN, + NOTATION_LAN, + // https://en.wikipedia.org/wiki/Shogi_notation#Western_notation + NOTATION_SHOGI_HOSKING, // Examples: P76, S’34 + NOTATION_SHOGI_HODGES, // Examples: P-7f, S*3d + NOTATION_SHOGI_HODGES_NUMBER, // Examples: P-76, S*34 + // http://www.janggi.pl/janggi-notation/ + NOTATION_JANGGI, + // https://en.wikipedia.org/wiki/Xiangqi#Notation + NOTATION_XIANGQI_WXF, +}; + +Notation default_notation(const Variant* v) { + if (v->variantTemplate == "shogi") + return NOTATION_SHOGI_HODGES_NUMBER; + return NOTATION_SAN; +} + +enum Disambiguation { + NO_DISAMBIGUATION, + FILE_DISAMBIGUATION, + RANK_DISAMBIGUATION, + SQUARE_DISAMBIGUATION, +}; + +bool is_shogi(Notation n) { + return n == NOTATION_SHOGI_HOSKING || n == NOTATION_SHOGI_HODGES || n == NOTATION_SHOGI_HODGES_NUMBER; +} + +std::string piece(const Position& pos, Move m, Notation n) { + Color us = pos.side_to_move(); + Square from = from_sq(m); + Piece pc = pos.moved_piece(m); + PieceType pt = type_of(pc); + // Quiet pawn moves + if ((n == NOTATION_SAN || n == NOTATION_LAN) && type_of(pc) == PAWN && type_of(m) != DROP) + return ""; + // Tandem pawns + else if (n == NOTATION_XIANGQI_WXF && popcount(pos.pieces(us, pt) & file_bb(from)) > 2) + return std::to_string(popcount(forward_file_bb(us, from) & pos.pieces(us, pt)) + 1); + // Moves of promoted pieces + else if (is_shogi(n) && type_of(m) != DROP && pos.unpromoted_piece_on(from)) + return "+" + std::string(1, toupper(pos.piece_to_char()[pos.unpromoted_piece_on(from)])); + // Promoted drops + else if (is_shogi(n) && type_of(m) == DROP && dropped_piece_type(m) != in_hand_piece_type(m)) + return "+" + std::string(1, toupper(pos.piece_to_char()[in_hand_piece_type(m)])); + else if (pos.piece_to_char_synonyms()[pc] != ' ') + return std::string(1, toupper(pos.piece_to_char_synonyms()[pc])); + else + return std::string(1, toupper(pos.piece_to_char()[pc])); +} + +std::string file(const Position& pos, Square s, Notation n) { + switch (n) { + case NOTATION_SHOGI_HOSKING: + case NOTATION_SHOGI_HODGES: + case NOTATION_SHOGI_HODGES_NUMBER: + return std::to_string(pos.max_file() - file_of(s) + 1); + case NOTATION_JANGGI: + return std::to_string(file_of(s) + 1); + case NOTATION_XIANGQI_WXF: + return std::to_string((pos.side_to_move() == WHITE ? pos.max_file() - file_of(s) : file_of(s)) + 1); + default: + return std::string(1, char('a' + file_of(s))); + } +} + +std::string rank(const Position& pos, Square s, Notation n) { + switch (n) { + case NOTATION_SHOGI_HOSKING: + case NOTATION_SHOGI_HODGES_NUMBER: + return std::to_string(pos.max_rank() - rank_of(s) + 1); + case NOTATION_SHOGI_HODGES: + return std::string(1, char('a' + pos.max_rank() - rank_of(s))); + case NOTATION_JANGGI: + return std::to_string((pos.max_rank() - rank_of(s) + 1) % 10); + case NOTATION_XIANGQI_WXF: + { + if (pos.empty(s)) + return std::to_string(relative_rank(pos.side_to_move(), s, pos.max_rank()) + 1); + else if (pos.pieces(pos.side_to_move(), type_of(pos.piece_on(s))) & forward_file_bb(pos.side_to_move(), s)) + return "-"; + else + return "+"; + } + default: + return std::to_string(rank_of(s) + 1); + } +} + +std::string square(const Position& pos, Square s, Notation n) { + switch (n) { + case NOTATION_JANGGI: + return rank(pos, s, n) + file(pos, s, n); + default: + return file(pos, s, n) + rank(pos, s, n); + } +} + +Disambiguation disambiguation_level(const Position& pos, Move m, Notation n) { + // Drops never need disambiguation + if (type_of(m) == DROP) + return NO_DISAMBIGUATION; + + // NOTATION_LAN and Janggi always use disambiguation + if (n == NOTATION_LAN || n == NOTATION_JANGGI) + return SQUARE_DISAMBIGUATION; + + Color us = pos.side_to_move(); + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = pos.moved_piece(m); + PieceType pt = type_of(pc); + + // Xiangqi uses either file disambiguation or +/- if two pieces on file + if (n == NOTATION_XIANGQI_WXF) + { + // Disambiguate by rank (+/-) if target square of other piece is valid + if (popcount(pos.pieces(us, pt) & file_bb(from)) == 2) + { + Square otherFrom = lsb((pos.pieces(us, pt) & file_bb(from)) ^ from); + Square otherTo = otherFrom + Direction(to) - Direction(from); + if (is_ok(otherTo) && (pos.board_bb(us, pt) & otherTo)) + return RANK_DISAMBIGUATION; + } + return FILE_DISAMBIGUATION; + } + + // Pawn captures always use disambiguation + if (n == NOTATION_SAN && pt == PAWN) + { + if (pos.capture(m)) + return FILE_DISAMBIGUATION; + if (type_of(m) == PROMOTION && from != to && pos.sittuyin_promotion()) + return SQUARE_DISAMBIGUATION; + } + + // A disambiguation occurs if we have more then one piece of type 'pt' + // that can reach 'to' with a legal move. + Bitboard b = pos.pieces(us, pt) ^ from; + Bitboard others = 0; + + while (b) + { + Square s = pop_lsb(&b); + if ( pos.pseudo_legal(make_move(s, to)) + && pos.legal(make_move(s, to)) + && !(is_shogi(n) && pos.unpromoted_piece_on(s) != pos.unpromoted_piece_on(from))) + others |= s; + } + + if (!others) + return NO_DISAMBIGUATION; + else if (is_shogi(n)) + return SQUARE_DISAMBIGUATION; + else if (!(others & file_bb(from))) + return FILE_DISAMBIGUATION; + else if (!(others & rank_bb(from))) + return RANK_DISAMBIGUATION; + else + return SQUARE_DISAMBIGUATION; +} + +std::string disambiguation(const Position& pos, Square s, Notation n, Disambiguation d) { + switch (d) + { + case FILE_DISAMBIGUATION: + return file(pos, s, n); + case RANK_DISAMBIGUATION: + return rank(pos, s, n); + case SQUARE_DISAMBIGUATION: + return square(pos, s, n); + default: + assert(d == NO_DISAMBIGUATION); + return ""; + } +} + +const std::string move_to_san(Position& pos, Move m, Notation n) { + std::string san = ""; + Color us = pos.side_to_move(); + Square from = from_sq(m); + Square to = to_sq(m); + + if (type_of(m) == CASTLING) + { + san = to > from ? "O-O" : "O-O-O"; + + if (is_gating(m)) + { + san += std::string("/") + pos.piece_to_char()[make_piece(WHITE, gating_type(m))]; + san += square(pos, gating_square(m), n); + } + } + else + { + // Piece + san += piece(pos, m, n); + + // Origin square, disambiguation + Disambiguation d = disambiguation_level(pos, m, n); + san += disambiguation(pos, from, n, d); + + // Separator/Operator + if (type_of(m) == DROP) + san += n == NOTATION_SHOGI_HOSKING ? '\'' : is_shogi(n) ? '*' : '@'; + else if (n == NOTATION_XIANGQI_WXF) + { + if (rank_of(from) == rank_of(to)) + san += '='; + else if (relative_rank(us, to, pos.max_rank()) > relative_rank(us, from, pos.max_rank())) + san += '+'; + else + san += '-'; + } + else if (pos.capture(m)) + san += 'x'; + else if (n == NOTATION_LAN || (is_shogi(n) && (n != NOTATION_SHOGI_HOSKING || d == SQUARE_DISAMBIGUATION)) || n == NOTATION_JANGGI) + san += '-'; + + // Destination square + if (n == NOTATION_XIANGQI_WXF && type_of(m) != DROP) + san += file_of(to) == file_of(from) ? std::to_string(std::abs(rank_of(to) - rank_of(from))) : file(pos, to, n); + else + san += square(pos, to, n); + + // Suffix + if (type_of(m) == PROMOTION) + san += std::string("=") + pos.piece_to_char()[make_piece(WHITE, promotion_type(m))]; + else if (type_of(m) == PIECE_PROMOTION) + san += is_shogi(n) ? std::string("+") : std::string("=") + pos.piece_to_char()[make_piece(WHITE, pos.promoted_piece_type(type_of(pos.moved_piece(m))))]; + else if (type_of(m) == PIECE_DEMOTION) + san += is_shogi(n) ? std::string("-") : std::string("=") + std::string(1, pos.piece_to_char()[pos.unpromoted_piece_on(from)]); + else if (type_of(m) == NORMAL && is_shogi(n) && pos.pseudo_legal(make(from, to))) + san += std::string("="); + if (is_gating(m)) + san += std::string("/") + pos.piece_to_char()[make_piece(WHITE, gating_type(m))]; + } + + // Check and checkmate + if (pos.gives_check(m) && !is_shogi(n)) + { + StateInfo st; + pos.do_move(m, st); + san += MoveList(pos).size() ? "+" : "#"; + pos.undo_move(m); + } + + return san; +} + +bool hasInsufficientMaterial(Color c, const Position& pos) { + + // Other win rules + if ( pos.captures_to_hand() + || pos.count_in_hand(c, ALL_PIECES) + || pos.extinction_value() != VALUE_NONE + || (pos.capture_the_flag_piece() && pos.count(c, pos.capture_the_flag_piece()))) + return false; + + // Restricted pieces + Bitboard restricted = pos.pieces(~c, KING); + for (PieceType pt : pos.piece_types()) + if (pt == KING || !(pos.board_bb(c, pt) & pos.board_bb(~c, KING))) + restricted |= pos.pieces(c, pt); + + // Mating pieces + for (PieceType pt : { ROOK, QUEEN, ARCHBISHOP, CHANCELLOR, SILVER, GOLD, COMMONER, CENTAUR }) + if ((pos.pieces(c, pt) & ~restricted) || (pos.count(c, PAWN) && pos.promotion_piece_types().find(pt) != pos.promotion_piece_types().end())) + return false; + + // Color-bound pieces + Bitboard colorbound = 0, unbound; + for (PieceType pt : { BISHOP, FERS, FERS_ALFIL, ALFIL, ELEPHANT }) + colorbound |= pos.pieces(pt) & ~restricted; + unbound = pos.pieces() ^ restricted ^ colorbound; + if ((colorbound & pos.pieces(c)) && (((DarkSquares & colorbound) && (~DarkSquares & colorbound)) || unbound)) + return false; + + // Unbound pieces require one helper piece of either color + if ((pos.pieces(c) & unbound) && (popcount(pos.pieces() ^ restricted) >= 2 || pos.stalemate_value() != VALUE_DRAW)) + return false; + + return true; +} + +namespace fen { + +enum FenValidation : int { + FEN_MISSING_SPACE_DELIM = -12, + FEN_INVALID_NB_PARTS = -11, + FEN_INVALID_CHAR = -10, + FEN_TOUCHING_KINGS = -9, + FEN_INVALID_BOARD_GEOMETRY = -8, + FEN_INVALID_POCKET_INFO = -7, + FEN_INVALID_SIDE_TO_MOVE = -6, + FEN_INVALID_CASTLING_INFO = -5, + FEN_INVALID_EN_PASSANT_SQ = -4, + FEN_INVALID_NUMBER_OF_KINGS = -3, + FEN_INVALID_HALF_MOVE_COUNTER = -2, + FEN_INVALID_MOVE_COUNTER = -1, + FEN_EMPTY = 0, + FEN_OK = 1 +}; +enum Validation : int { + NOK, + OK +}; + +struct CharSquare { + int rowIdx; + int fileIdx; + CharSquare() : rowIdx(-1), fileIdx(-1) {} + CharSquare(int rowIdx, int fileIdx) : rowIdx(rowIdx), fileIdx(fileIdx) {} +}; + +bool operator==(const CharSquare& s1, const CharSquare& s2) { + return s1.rowIdx == s2.rowIdx && s1.fileIdx == s2.fileIdx; +} + +bool operator!=(const CharSquare& s1, const CharSquare& s2) { + return !(s1 == s2); +} + +int non_root_euclidian_distance(const CharSquare& s1, const CharSquare& s2) { + return pow(s1.rowIdx - s2.rowIdx, 2) + pow(s1.fileIdx - s2.fileIdx, 2); +} + +class CharBoard { +private: + int nbRanks; + int nbFiles; + std::vector board; // fill an array where the pieces are for later geometry checks +public: + CharBoard(int nbRanks, int nbFiles) : nbRanks(nbRanks), nbFiles(nbFiles) { + assert(nbFiles > 0 && nbRanks > 0); + board = std::vector(nbRanks * nbFiles, ' '); + } + void set_piece(int rankIdx, int fileIdx, char c) { + board[rankIdx * nbFiles + fileIdx] = c; + } + char get_piece(int rowIdx, int fileIdx) const { + return board[rowIdx * nbFiles + fileIdx]; + } + int get_nb_ranks() const { + return nbRanks; + } + int get_nb_files() const { + return nbFiles; + } + /// Returns the square of a given character + CharSquare get_square_for_piece(char piece) const { + CharSquare s; + for (int r = 0; r < nbRanks; ++r) { + for (int c = 0; c < nbFiles; ++c) { + if (get_piece(r, c) == piece) { + s.rowIdx = r; + s.fileIdx = c; + return s; + } + } + } + return s; + } + /// Returns all square positions for a given piece + std::vector get_squares_for_piece(char piece) const { + std::vector squares; + for (int r = 0; r < nbRanks; ++r) { + for (int c = 0; c < nbFiles; ++c) { + if (get_piece(r, c) == piece) { + squares.emplace_back(CharSquare(r, c)); + } + } + } + return squares; + } + /// Checks if a given character is on a given rank index + bool is_piece_on_rank(char piece, int rowIdx) const { + for (int f = 0; f < nbFiles; ++f) + if (get_piece(rowIdx, f) == piece) + return true; + return false; + } + friend std::ostream& operator<<(std::ostream& os, const CharBoard& board); +}; + +std::ostream& operator<<(std::ostream& os, const CharBoard& board) { + for (int r = 0; r < board.nbRanks; ++r) { + for (int c = 0; c < board.nbFiles; ++c) { + os << "[" << board.get_piece(r, c) << "] "; + } + os << std::endl; + } + return os; +} + +Validation check_for_valid_characters(const std::string& firstFenPart, const std::string& validSpecialCharacters, const Variant* v) { + for (char c : firstFenPart) { + if (!isdigit(c) && v->pieceToChar.find(c) == std::string::npos && validSpecialCharacters.find(c) == std::string::npos) { + std::cerr << "Invalid piece character: '" << c << "'." << std::endl; + return NOK; + } + } + return OK; +} + +std::vector get_fen_parts(const std::string& fullFen, char delim) { + std::vector fenParts; + std::string curPart; + std::stringstream ss(fullFen); + while (std::getline(ss, curPart, delim)) + fenParts.emplace_back(curPart); + return fenParts; +} + +/// fills the character board according to a given FEN string +Validation fill_char_board(CharBoard& board, const std::string& fenBoard, const std::string& validSpecialCharacters, const Variant* v) { + int rankIdx = 0; + int fileIdx = 0; + + char prevChar = '?'; + for (char c : fenBoard) { + if (c == ' ' || c == '[') + break; + if (isdigit(c)) { + fileIdx += c - '0'; + // if we have multiple digits attached we can add multiples of 9 to compute the resulting number (e.g. -> 21 = 2 + 2 * 9 + 1) + if (isdigit(prevChar)) + fileIdx += 9 * (prevChar - '0'); + } + else if (c == '/') { + ++rankIdx; + if (fileIdx != board.get_nb_files()) { + std::cerr << "curRankWidth != nbFiles: " << fileIdx << " != " << board.get_nb_files() << std::endl; + return NOK; + } + if (rankIdx == board.get_nb_ranks()) + break; + fileIdx = 0; + } + else if (validSpecialCharacters.find(c) == std::string::npos) { // normal piece + if (fileIdx == board.get_nb_files()) { + std::cerr << "File index: " << fileIdx << " for piece '" << c << "' exceeds maximum of allowed number of files: " << board.get_nb_files() << "." << std::endl; + return NOK; + } + board.set_piece(v->maxRank-rankIdx, fileIdx, c); // we mirror the rank index because the black pieces are given first in the FEN + ++fileIdx; + } + prevChar = c; + } + + if (v->pieceDrops) { // pockets can either be defined by [] or / + if (rankIdx+1 != board.get_nb_ranks() && rankIdx != board.get_nb_ranks()) { + std::cerr << "Invalid number of ranks. Expected: " << board.get_nb_ranks() << " Actual: " << rankIdx+1 << std::endl; + return NOK; + } + } + else { + if (rankIdx+1 != board.get_nb_ranks()) { + std::cerr << "Invalid number of ranks. Expected: " << board.get_nb_ranks() << " Actual: " << rankIdx+1 << std::endl; + return NOK; + } + } + return OK; +} + +Validation fill_castling_info_splitted(const std::string& castlingInfo, std::array& castlingInfoSplitted) { + for (char c : castlingInfo) { + if (c != '-') { + if (!isalpha(c)) { + std::cerr << "Invalid castling specification: '" << c << "'." << std::endl; + return NOK; + } + else if (isupper(c)) + castlingInfoSplitted[WHITE] += tolower(c); + else + castlingInfoSplitted[BLACK] += c; + } + } + return OK; +} + +std::string color_to_string(Color c) { + switch (c) { + case WHITE: + return "WHITE"; + case BLACK: + return "BLACK"; + case COLOR_NB: + return "COLOR_NB"; + default: + return "INVALID_COLOR"; + } +} + +Validation check_960_castling(const std::array& castlingInfoSplitted, const CharBoard& board, const std::array& kingPositionsStart) { + + for (Color color : {WHITE, BLACK}) { + for (char charPiece : {'K', 'R'}) { + if (castlingInfoSplitted[color].size() == 0) + continue; + const Rank rank = Rank(kingPositionsStart[color].rowIdx); + if (color == BLACK) + charPiece = tolower(charPiece); + if (!board.is_piece_on_rank(charPiece, rank)) { + std::cerr << "The " << color_to_string(color) << " king and rook must be on rank " << rank << " if castling is enabled for " << color_to_string(color) << "." << std::endl; + return NOK; + } + } + } + return OK; +} + +std::string castling_rights_to_string(CastlingRights castlingRights) { + switch (castlingRights) { + case KING_SIDE: + return "KING_SIDE"; + case QUEEN_SIDE: + return "QUEENS_SIDE"; + case WHITE_OO: + return "WHITE_OO"; + case WHITE_OOO: + return "WHITE_OOO"; + case BLACK_OO: + return "BLACK_OO"; + case BLACK_OOO: + return "BLACK_OOO"; + case WHITE_CASTLING: + return "WHITE_CASTLING"; + case BLACK_CASTLING: + return "BLACK_CASTLING"; + case ANY_CASTLING: + return "ANY_CASTLING"; + case CASTLING_RIGHT_NB: + return "CASTLING_RIGHT_NB"; + default: + return "INVALID_CASTLING_RIGHTS"; + } +} + +Validation check_touching_kings(const CharBoard& board, const std::array& kingPositions) { + if (non_root_euclidian_distance(kingPositions[WHITE], kingPositions[BLACK]) <= 2) { + std::cerr << "King pieces are next to each other." << std::endl; + std::cerr << board << std::endl; + return NOK; + } + return OK; +} + +Validation check_standard_castling(std::array& castlingInfoSplitted, const CharBoard& board, + const std::array& kingPositions, const std::array& kingPositionsStart, + const std::array, 2>& rookPositionsStart) { + + for (Color c : {WHITE, BLACK}) { + if (castlingInfoSplitted[c].size() == 0) + continue; + if (kingPositions[c] != kingPositionsStart[c]) { + std::cerr << "The " << color_to_string(c) << " KING has moved. Castling is no longer valid for " << color_to_string(c) << "." << std::endl; + return NOK; + } + + for (CastlingRights castling: {KING_SIDE, QUEEN_SIDE}) { + CharSquare rookStartingSquare = castling == QUEEN_SIDE ? rookPositionsStart[c][0] : rookPositionsStart[c][1]; + char targetChar = castling == QUEEN_SIDE ? 'q' : 'k'; + char rookChar = 'R'; // we don't use v->pieceToChar[ROOK]; here because in the newzealand_variant the ROOK is replaced by ROOKNI + if (c == BLACK) + rookChar = tolower(rookChar); + if (castlingInfoSplitted[c].find(targetChar) != std::string::npos) { + if (board.get_piece(rookStartingSquare.rowIdx, rookStartingSquare.fileIdx) != rookChar) { + std::cerr << "The " << color_to_string(c) << " ROOK on the "<< castling_rights_to_string(castling) << " has moved. " + << castling_rights_to_string(castling) << " castling is no longer valid for " << color_to_string(c) << "." << std::endl; + return NOK; + } + } + + } + } + return OK; +} + +Validation check_pocket_info(const std::string& fenBoard, int nbRanks, const Variant* v, std::array& pockets) { + + char stopChar; + int offset = 0; + if (std::count(fenBoard.begin(), fenBoard.end(), '/') == nbRanks) { + // look for last '/' + stopChar = '/'; + } + else { + // pocket is defined as [ and ] + stopChar = '['; + offset = 1; + if (*(fenBoard.end()-1) != ']') { + std::cerr << "Pocket specification does not end with ']'." << std::endl; + return NOK; + } + } + + // look for last '/' + for (auto it = fenBoard.rbegin()+offset; it != fenBoard.rend(); ++it) { + const char c = *it; + if (c == stopChar) + return OK; + if (c != '-') { + if (v->pieceToChar.find(c) == std::string::npos) { + std::cerr << "Invalid pocket piece: '" << c << "'." << std::endl; + return NOK; + } + else { + if (isupper(c)) + pockets[WHITE] += tolower(c); + else + pockets[BLACK] += c; + } + } + } + std::cerr << "Pocket piece closing character '" << stopChar << "' was not found." << std::endl; + return NOK; +} + +Validation check_number_of_kings(const std::string& fenBoard, const Variant* v) { + int nbWhiteKings = std::count(fenBoard.begin(), fenBoard.end(), toupper(v->pieceToChar[KING])); + int nbBlackKings = std::count(fenBoard.begin(), fenBoard.end(), tolower(v->pieceToChar[KING])); + + if (nbWhiteKings != 1) { + std::cerr << "Invalid number of white kings. Expected: 1. Given: " << nbWhiteKings << std::endl; + return NOK; + } + if (nbBlackKings != 1) { + std::cerr << "Invalid number of black kings. Expected: 1. Given: " << nbBlackKings << std::endl; + return NOK; + } + return OK; +} + +Validation check_en_passant_square(const std::string& enPassantInfo) { + const char firstChar = enPassantInfo[0]; + if (firstChar != '-') { + if (enPassantInfo.size() != 2) { + std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 2 characters. Actual: " << enPassantInfo.size() << " character(s)." << std::endl; + return NOK; + } + if (isdigit(firstChar)) { + std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 1st character to be a digit." << std::endl; + return NOK; + } + const char secondChar = enPassantInfo[1]; + if (!isdigit(secondChar)) { + std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 2nd character to be a non-digit." << std::endl; + return NOK; + } + } + return OK; +} + +bool no_king_piece_in_pockets(const std::array& pockets) { + return pockets[WHITE].find('k') == std::string::npos && pockets[BLACK].find('k') == std::string::npos; +} + +FenValidation validate_fen(const std::string& fen, const Variant* v) { + + const std::string validSpecialCharacters = "/+~[]-"; + // 0) Layout + // check for empty fen + if (fen.size() == 0) { + std::cerr << "Fen is empty." << std::endl; + return FEN_EMPTY; + } + + // check for space + if (fen.find(' ') == std::string::npos) { + std::cerr << "Fen misses space as delimiter." << std::endl; + return FEN_MISSING_SPACE_DELIM; + } + + std::vector fenParts = get_fen_parts(fen, ' '); + std::vector 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; + return FEN_INVALID_NB_PARTS; + } + + // 1) Part + // check for valid characters + if (check_for_valid_characters(fenParts[0], validSpecialCharacters, v) == NOK) { + return FEN_INVALID_CHAR; + } + + // check for number of ranks + const int nbRanks = v->maxRank + 1; + // check for number of files + const int nbFiles = v->maxFile + 1; + CharBoard board(nbRanks, nbFiles); // create a 2D character board for later geometry checks + + if (fill_char_board(board, fenParts[0], validSpecialCharacters, v) == NOK) + return FEN_INVALID_BOARD_GEOMETRY; + + // check for pocket + std::array pockets; + if (v->pieceDrops) { + if (check_pocket_info(fenParts[0], nbRanks, v, pockets) == NOK) + return FEN_INVALID_POCKET_INFO; + } + + // check for number of kings (skip all extinction variants for this check (e.g. horde is a sepcial case where only one side has a royal king)) + if (v->pieceTypes.find(KING) != v->pieceTypes.end() && v->extinctionPieceTypes.size() == 0) { + // we have a royal king in this variant, ensure that each side has exactly one king + // (variants like giveaway use the COMMONER piece type instead) + if (check_number_of_kings(fenParts[0], v) == NOK) + return FEN_INVALID_NUMBER_OF_KINGS; + + // if kings are still in pockets skip this check (e.g. placement_variant) + if (no_king_piece_in_pockets(pockets)) { + // check if kings are touching + std::array kingPositions; + // check if kings are touching + kingPositions[WHITE] = board.get_square_for_piece(toupper(v->pieceToChar[KING])); + kingPositions[BLACK] = board.get_square_for_piece(tolower(v->pieceToChar[KING])); + if (check_touching_kings(board, kingPositions) == NOK) + return FEN_TOUCHING_KINGS; + + // 3) Part + // check castling rights + if (v->castling) { + std::array castlingInfoSplitted; + if (fill_castling_info_splitted(fenParts[2], castlingInfoSplitted) == NOK) + return FEN_INVALID_CASTLING_INFO; + + if (castlingInfoSplitted[WHITE].size() != 0 || castlingInfoSplitted[BLACK].size() != 0) { + + CharBoard startBoard(board.get_nb_ranks(), board.get_nb_files()); + fill_char_board(startBoard, v->startFen, validSpecialCharacters, v); + std::array kingPositionsStart; + kingPositionsStart[WHITE] = startBoard.get_square_for_piece(toupper(v->pieceToChar[KING])); + kingPositionsStart[BLACK] = startBoard.get_square_for_piece(tolower(v->pieceToChar[KING])); + + if (v->chess960) { + if (check_960_castling(castlingInfoSplitted, board, kingPositionsStart) == NOK) + return FEN_INVALID_CASTLING_INFO; + } + else { + std::array, 2> rookPositionsStart; + // we don't use v->pieceToChar[ROOK]; here because in the newzealand_variant the ROOK is replaced by ROOKNI + rookPositionsStart[WHITE] = startBoard.get_squares_for_piece('R'); + rookPositionsStart[BLACK] = startBoard.get_squares_for_piece('r'); + + if (check_standard_castling(castlingInfoSplitted, board, kingPositions, kingPositionsStart, rookPositionsStart) == NOK) + return FEN_INVALID_CASTLING_INFO; + } + } + + } + } + } + + // 2) Part + // check side to move char + if (fenParts[1][0] != 'w' && fenParts[1][0] != 'b') { + std::cerr << "Invalid side to move specification: '" << fenParts[1][0] << "'." << std::endl; + return FEN_INVALID_SIDE_TO_MOVE; + } + + // 4) Part + // check en-passant square + if (v->doubleStep && v->pieceTypes.find(PAWN) != v->pieceTypes.end()) { + if (check_en_passant_square(fenParts[3]) == NOK) + return FEN_INVALID_EN_PASSANT_SQ; + } + + // 5) Part + // checkCounting is skipped because if only one check is required to win it must not be part of the FEN (e.g. karouk_variant) + + // 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; + } + + // 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; + } + + return FEN_OK; +} +} + diff --cc src/evaluate.cpp index ce1495a,0b21126..808418f --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@@ -800,20 -616,9 +801,20 @@@ namespace score -= PawnlessFlank; // Penalty if king flank is under attack, potentially moving toward the king - score -= FlankAttacks * kingFlankAttack; + score -= FlankAttacks * kingFlankAttack * (1 + 5 * pos.captures_to_hand() + pos.check_counting()); + + if (pos.check_counting()) + score += make_score(0, mg_value(score) * 2 / (2 + pos.checks_remaining(Them))); + + if (pos.king_type() == WAZIR) + score += make_score(0, mg_value(score) / 2); + + // For drop games, king danger is independent of game phase, but dependent on material density + if (pos.captures_to_hand() || pos.two_boards()) + score = make_score(mg_value(score) * me->material_density() / 11000, + mg_value(score) * me->material_density() / 11000); - if (T) + if constexpr (T) Trace::add(KING, Us, score); return score; @@@ -1054,38 -809,10 +1057,38 @@@ } } // r > RANK_3 - score += bonus - PassedFile * edge_distance(file_of(s)); + score += bonus - PassedFile * edge_distance(file_of(s), pos.max_file()); + } + + // Scale by maximum promotion piece value + Value maxMg = VALUE_ZERO, maxEg = VALUE_ZERO; + for (PieceType pt : pos.promotion_piece_types()) + { + maxMg = std::max(maxMg, PieceValue[MG][pt]); + maxEg = std::max(maxEg, PieceValue[EG][pt]); + } + score = make_score(mg_value(score) * int(maxMg - PawnValueMg) / (QueenValueMg - PawnValueMg), + eg_value(score) * int(maxEg - PawnValueEg) / (QueenValueEg - PawnValueEg)); + + // Score passed shogi pawns + PieceType pt = pos.promoted_piece_type(SHOGI_PAWN); + if (pt != NO_PIECE_TYPE) + { + b = pos.pieces(Us, SHOGI_PAWN); + while (b) + { + Square s = pop_lsb(&b); + if ((pos.pieces(Them, SHOGI_PAWN) & forward_file_bb(Us, s)) || relative_rank(Us, s, pos.max_rank()) == pos.max_rank()) + continue; + + Square blockSq = s + Up; + int d = 2 * std::max(pos.promotion_rank() - relative_rank(Us, s, pos.max_rank()), 1); + d += !!(attackedBy[Them][ALL_PIECES] & ~attackedBy2[Us] & blockSq); + score += make_score(PieceValue[MG][pt], PieceValue[EG][pt]) / (d * d); + } } - if (T) + if constexpr (T) Trace::add(PASSED, Us, score); return score; @@@ -1134,10 -853,7 +1137,10 @@@ int weight = pos.count(Us) - 3 + std::min(pe->blocked_count(), 9); Score score = make_score(bonus * weight * weight / 16, 0); + if (pos.capture_the_flag(Us)) + score += make_score(200, 200) * popcount(behind & safe & pos.capture_the_flag(Us)); + - if (T) + if constexpr (T) Trace::add(SPACE, Us, score); return score; @@@ -1311,16 -867,10 +1314,16 @@@ template Value Evaluation::winnable(Score score) const { - int outflanking = distance(pos.square(WHITE), pos.square(BLACK)) + // No initiative bonus for extinction variants + int complexity = 0; + bool pawnsOnBothFlanks = true; + if (pos.extinction_value() == VALUE_NONE && !pos.captures_to_hand() && !pos.connect_n() && !pos.material_counting()) + { + int outflanking = !pos.count(WHITE) || !pos.count(BLACK) ? 0 + : distance(pos.square(WHITE), pos.square(BLACK)) - - distance(pos.square(WHITE), pos.square(BLACK)); + + int(rank_of(pos.square(WHITE)) - rank_of(pos.square(BLACK))); - bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) + pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) && (pos.pieces(PAWN) & KingSide); bool almostUnwinnable = outflanking < 0 @@@ -1483,8 -1020,9 +1486,8 @@@ make_v Value v = winnable(score); // In case of tracing add all remaining individual evaluation terms - if (T) + if constexpr (T) { - Trace::add(MATERIAL, pos.psq_score()); Trace::add(IMBALANCE, me->imbalance()); Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK)); Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]); @@@ -1522,15 -1053,8 +1525,15 @@@ Value Eval::evaluate(const Position& po { // Scale and shift NNUE for compatibility with search and classical evaluation auto adjusted_NNUE = [&](){ - int mat = pos.non_pawn_material() + PawnValueMg * pos.count(); + int mat = pos.non_pawn_material() + 2 * PawnValueMg * pos.count(); - return NNUE::evaluate(pos) * (641 + mat / 32 - 4 * pos.rule50_count()) / 1024 + Tempo; + int v2 = VALUE_ZERO; + if (pos.check_counting()) + { + Color us = pos.side_to_move(); + v2 = mat / (30 * pos.checks_remaining( us)) + - mat / (30 * pos.checks_remaining(~us)); + } - return NNUE::evaluate(pos) * (679 + mat / 32) / 1024 + Tempo + v2; ++ return NNUE::evaluate(pos) * (641 + mat / 32 - 4 * pos.rule50_count()) / 1024 + Tempo + v2; }; // If there is PSQ imbalance use classical eval, with small probability if it is small diff --cc src/ffishjs.cpp index d9b765c,0000000..d615140 mode 100644,000000..100644 --- a/src/ffishjs.cpp +++ b/src/ffishjs.cpp @@@ -1,627 -1,0 +1,627 @@@ +/* + ffish.js, a JavaScript chess variant library derived from Fairy-Stockfish - Copyright (C) 2020 Fabian Fichter, Johannes Czech ++ Copyright (C) 2021 Fabian Fichter, Johannes Czech + + ffish.js is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ffish.js is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "types.h" +#include "bitboard.h" +#include "evaluate.h" +#include "position.h" +#include "search.h" +#include "syzygy/tbprobe.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" +#include "piece.h" +#include "variant.h" +#include "movegen.h" +#include "apiutil.h" + +using namespace emscripten; + + +void initialize_stockfish() { + pieceMap.init(); + variants.init(); + UCI::init(Options); + Bitboards::init(); + Position::init(); + Bitbases::init(); +} + +#define DELIM " " + +inline void save_pop_back(std::string& s) { + if (s.size() != 0) { + s.pop_back(); + } +} + +const Variant* get_variant(const std::string& uciVariant) { + if (uciVariant.size() == 0 || uciVariant == "Standard" || uciVariant == "standard") + return variants.find("chess")->second; + return variants.find(uciVariant)->second; +} + +class Board { + // note: we can't use references for strings here due to conversion to JavaScript +private: + const Variant* v; + StateListPtr states; + Position pos; + Thread* thread; + std::vector moveStack; + 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): + Board(uciVariant, "", false) { + } + + Board(std::string uciVariant, std::string fen): + Board(uciVariant, fen, false) { + } + + Board(std::string uciVariant, std::string fen, bool is960) { + init(uciVariant, fen, is960); + } + + std::string legal_moves() { + std::string moves; + for (const ExtMove& move : MoveList(this->pos)) { + moves += UCI::move(this->pos, move); + moves += DELIM; + } + save_pop_back(moves); + return moves; + } + + std::string legal_moves_san() { + std::string movesSan; + for (const ExtMove& move : MoveList(this->pos)) { + movesSan += move_to_san(this->pos, move, NOTATION_SAN); + movesSan += DELIM; + } + save_pop_back(movesSan); + return movesSan; + } + + int number_legal_moves() const { + return MoveList(pos).size(); + } + + void push(std::string uciMove) { + do_move(UCI::to_move(this->pos, uciMove)); + } + + bool push_san(std::string sanMove) { + return push_san(sanMove, NOTATION_SAN); + } + + // 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. + bool push_san(std::string sanMove, Notation notation) { + Move foundMove = MOVE_NONE; + for (const ExtMove& move : MoveList(pos)) { + if (sanMove == move_to_san(this->pos, move, notation)) { + foundMove = move; + break; + } + } + if (foundMove != MOVE_NONE) { + do_move(foundMove); + return true; + } + return false; + } + + void pop() { + pos.undo_move(this->moveStack.back()); + moveStack.pop_back(); + states->pop_back(); + } + + void reset() { + set_fen(v->startFen); + } + + bool is_960() const { + return is960; + } + + std::string fen() const { + return this->pos.fen(); + } + + void set_fen(std::string fen) { + resetStates(); + moveStack.clear(); + pos.set(v, fen, is960, &states->back(), thread); + } + + // 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); + } + + std::string san_move(std::string uciMove, Notation notation) { + 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); + } + + 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 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 += DELIM; + variationSan += std::to_string(fullmove_number()); + variationSan += "."; + } + variationSan += DELIM; + 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() const { + return !pos.side_to_move(); + } + + int fullmove_number() const { + return pos.game_ply() / 2 + 1; + } + + int halfmove_clock() const { + return pos.rule50_count(); + } + + int game_ply() const { + return pos.game_ply(); + } + + bool is_game_over() const { + for (const ExtMove& move: MoveList(pos)) + return false; + return true; + } + + bool is_check() const { + return pos.checkers(); + } + + bool is_bikjang() const { + return pos.bikjang(); + } + + std::string move_stack() const { + std::string moves; + for(auto it = std::begin(moveStack); it != std::end(moveStack); ++it) { + moves += UCI::move(pos, *it); + moves += DELIM; + } + save_pop_back(moves); + return moves; + } + + void push_moves(std::string uciMoves) { + std::stringstream ss(uciMoves); + std::string uciMove; + while (std::getline(ss, uciMove, ' ')) { + push(uciMove); + } + } + + void push_san_moves(std::string sanMoves) { + return push_san_moves(sanMoves, NOTATION_SAN); + } + + void push_san_moves(std::string sanMoves, Notation notation) { + std::stringstream ss(sanMoves); + std::string sanMove; + while (std::getline(ss, sanMove, ' ')) + push_san(sanMove, notation); + } + + std::string pocket(bool color) { + const Color c = Color(!color); + std::string pocket; + for (PieceType pt = KING; pt >= PAWN; --pt) { + for (int i = 0; i < pos.count_in_hand(c, pt); ++i) { + // only create BLACK pieces in order to convert to lower case + pocket += std::string(1, pos.piece_to_char()[make_piece(BLACK, pt)]); + } + } + return pocket; + } + + std::string to_string() { + std::string stringBoard; + for (Rank r = pos.max_rank(); r >= RANK_1; --r) { + for (File f = FILE_A; f <= pos.max_file(); ++f) { + if (f != FILE_A) + stringBoard += " "; + const Piece p = pos.piece_on(make_square(f, r)); + switch(p) { + case NO_PIECE: + stringBoard += '.'; + break; + default: + stringBoard += pos.piece_to_char()[p]; + } + } + if (r != RANK_1) + stringBoard += "\n"; + } + return stringBoard; + } + + std::string to_verbose_string() { + std::stringstream ss; + operator<<(ss, pos); + return ss.str(); + } + +private: + void resetStates() { + this->states = StateListPtr(new std::deque(1)); + } + + void do_move(Move move) { + states->emplace_back(); + this->pos.do_move(move, states->back()); + this->moveStack.emplace_back(move); + } + + void init(std::string uciVariant, std::string fen, bool is960) { + if (!Board::sfInitialized) { + initialize_stockfish(); + Board::sfInitialized = true; + } + v = get_variant(uciVariant); + this->resetStates(); + if (fen == "") + fen = v->startFen; + this->pos.set(this->v, fen, is960, &this->states->back(), this->thread); + this->is960 = is960; + } +}; + +bool Board::sfInitialized = false; + +namespace ffish { + // returns the version of the Fairy-Stockfish binary + std::string info() { + return engine_info(); + } + + template + void set_option(std::string name, T value) { + Options[name] = value; + Board::sfInitialized = false; + } + + std::string available_variants() { + std::string availableVariants; + for (std::string variant : variants.get_keys()) { + availableVariants += variant; + availableVariants += DELIM; + } + save_pop_back(availableVariants); + return availableVariants; + } + + void load_variant_config(std::string variantInitContent) { + std::stringstream ss(variantInitContent); + if (!Board::sfInitialized) + initialize_stockfish(); + variants.parse_istream(ss); + Options["UCI_Variant"].set_combo(variants.get_keys()); + Board::sfInitialized = true; + } + + std::string starting_fen(std::string uciVariant) { + const Variant* v = get_variant(uciVariant); + return v->startFen; + } + + int validate_fen(std::string fen, std::string uciVariant) { + const Variant* v = get_variant(uciVariant); + return fen::validate_fen(fen, v); + } + + int validate_fen(std::string fen) { + return validate_fen(fen, "chess"); + } +} + +class Game { +private: + std::unordered_map header; + std::unique_ptr board; + std::string variant = "chess"; + std::string fen = ""; // start pos + bool is960 = false; + bool parsedGame = false; +public: + std::string header_keys() { + std::string keys; + for (auto it = header.begin(); it != header.end(); ++it) { + keys += it->first; + keys += DELIM; + } + save_pop_back(keys); + return keys; + } + + std::string headers(std::string item) { + auto it = header.find(item); + if (it == header.end()) + return ""; + return it->second; + } + + std::string mainline_moves() { + if (!parsedGame) + return ""; + return board->move_stack(); + } + + friend Game read_game_pgn(std::string); +}; + + +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; +} + +Game read_game_pgn(std::string pgn) { + Game game; + size_t lineStart = 0; + bool headersParsed = false; + + while(true) { + size_t lineEnd = pgn.find('\n', lineStart); + + if (lineEnd == std::string::npos) + lineEnd = pgn.size(); + + if (!headersParsed && pgn[lineStart] == '[') { + // parse header + // look for item + size_t headerKeyStart = lineStart+1; + size_t headerKeyEnd = pgn.find(' ', lineStart); + size_t headerItemStart = pgn.find('"', headerKeyEnd)+1; + size_t headerItemEnd = pgn.find('"', headerItemStart); + + // put item into list + game.header[pgn.substr(headerKeyStart, headerKeyEnd-headerKeyStart)] = pgn.substr(headerItemStart, headerItemEnd-headerItemStart); + } + else { + if (!headersParsed) { + headersParsed = true; + auto it = game.header.find("Variant"); + if (it != game.header.end()) { + game.variant = it->second; + std::transform(game.variant.begin(), game.variant.end(), game.variant.begin(), + [](unsigned char c){ return std::tolower(c); }); + game.is960 = it->second.find("960") != std::string::npos; + } + + it = game.header.find("FEN"); + if (it != game.header.end()) + game.fen = it->second; + + game.board = std::make_unique(game.variant, game.fen, game.is960); + game.parsedGame = true; + } + + // game line + size_t curIdx = lineStart; + while (curIdx <= lineEnd) { + if (pgn[curIdx] == '*') + return game; + + if (!skip_comment(pgn, curIdx, lineEnd)) + return game; + + // Movetext RAV (Recursive Annotation Variation) + size_t openedRAV = 0; + if (pgn[curIdx] == '(') { + openedRAV = 1; + ++curIdx; + } + while (openedRAV != 0) { + switch (pgn[curIdx]) { + case '(': + ++openedRAV; + break; + case ')': + --openedRAV; + break; + case '{': + if (!skip_comment(pgn, curIdx, lineEnd)) + return game; + default: ; // pass + } + ++curIdx; + if (curIdx > lineEnd) + lineEnd = pgn.find('\n', curIdx); + } + + if (pgn[curIdx] == '$') { + // we are at a glyph + curIdx = pgn.find(' ', curIdx); + } + + if (pgn[curIdx] >= '0' && pgn[curIdx] <= '9') { + // we are at a move number -> look for next point + curIdx = pgn.find('.', curIdx); + if (curIdx == std::string::npos) + break; + ++curIdx; + // increment if we're at a space + while (curIdx < pgn.size() && pgn[curIdx] == ' ') + ++curIdx; + // increment if we're at a point + while (curIdx < pgn.size() && pgn[curIdx] == '.') + ++curIdx; + } + // extract sanMove + size_t sanMoveEnd = std::min(pgn.find(' ', curIdx), lineEnd); + if (sanMoveEnd > curIdx) { + std::string sanMove = pgn.substr(curIdx, sanMoveEnd-curIdx); + // clean possible ? and ! from string + size_t annotationChar1 = sanMove.find('?'); + size_t annotationChar2 = sanMove.find('!'); + if (annotationChar1 != std::string::npos || annotationChar2 != std::string::npos) + sanMove = sanMove.substr(0, std::min(annotationChar1, annotationChar2)); + game.board->push_san(sanMove); + } + curIdx = sanMoveEnd+1; + } + } + lineStart = lineEnd+1; + + if (lineStart >= pgn.size()) + return game; + } + return game; +} + + +// binding code +EMSCRIPTEN_BINDINGS(ffish_js) { + class_("Board") + .constructor<>() + .constructor() + .constructor() + .constructor() + .function("legalMoves", &Board::legal_moves) + .function("legalMovesSan", &Board::legal_moves_san) + .function("numberLegalMoves", &Board::number_legal_moves) + .function("push", &Board::push) + .function("pushSan", select_overload(&Board::push_san)) + .function("pushSan", select_overload(&Board::push_san)) + .function("pop", &Board::pop) + .function("reset", &Board::reset) + .function("is960", &Board::is_960) + .function("fen", &Board::fen) + .function("setFen", &Board::set_fen) + .function("sanMove", select_overload(&Board::san_move)) + .function("sanMove", select_overload(&Board::san_move)) + .function("variationSan", select_overload(&Board::variation_san)) + .function("variationSan", select_overload(&Board::variation_san)) + .function("variationSan", select_overload(&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) + .function("isCheck", &Board::is_check) + .function("isBikjang", &Board::is_bikjang) + .function("moveStack", &Board::move_stack) + .function("pushMoves", &Board::push_moves) + .function("pushSanMoves", select_overload(&Board::push_san_moves)) + .function("pushSanMoves", select_overload(&Board::push_san_moves)) + .function("pocket", &Board::pocket) + .function("toString", &Board::to_string) + .function("toVerboseString", &Board::to_verbose_string); + class_("Game") + .function("headerKeys", &Game::header_keys) + .function("headers", &Game::headers) + .function("mainlineMoves", &Game::mainline_moves); + // usage: e.g. ffish.Notation.DEFAULT + enum_("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", &ffish::info); + function("setOption", &ffish::set_option); + function("setOptionInt", &ffish::set_option); + function("setOptionBool", &ffish::set_option); + function("readGamePGN", &read_game_pgn); + function("variants", &ffish::available_variants); + function("loadVariantConfig", &ffish::load_variant_config); + function("startingFen", &ffish::starting_fen); + function("validateFen", select_overload(&ffish::validate_fen)); + function("validateFen", select_overload(&ffish::validate_fen)); + // TODO: enable to string conversion method + // .class_function("getStringFromInstance", &Board::get_string_from_instance); +} diff --cc src/magic.h index d24fe46,0000000..d8f305a mode 100644,000000..100644 --- a/src/magic.h +++ b/src/magic.h @@@ -1,1005 -1,0 +1,1005 @@@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish - Copyright (C) 2018-2020 Fabian Fichter ++ Copyright (C) 2018-2021 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MAGIC_H_INCLUDED +#define MAGIC_H_INCLUDED + +#ifdef PRECOMPUTED_MAGICS +#define B(a, b) (Bitboard(a) << 64) ^ Bitboard(b) + // Use precomputed magics if pext is not avaible, + // since the magics generation is very slow. + Bitboard RookMagicHInit[SQUARE_NB] = { + B(0x120000880110000, 0x1008000000020020), + B(0x24200C080840A052, 0x2004004000010008), + B(0xC030024000228800, 0x4000010400000020), + B(0x1A0020802008802, 0x206010208000), + B(0x12002000D001024, 0x80100800090138), + B(0x4220010000241010, 0x3098000602001500), + B(0x401010004801040, 0x8000280480100000), + B(0x820082024921836, 0x220028000), + B(0x100400502411400, 0x220402120240D14), + B(0x880202020010404, 0xA80202510000), + B(0x140002801000018, 0x1000346490040), + B(0x120000880110000, 0x1008000000020020), + B(0xD01004008030400, 0x104000408104420), + B(0x8420060100020000, 0x800280400000120), + B(0x4010020018010, 0x40A00001100000), + B(0x40006A0004000200, 0x40000000110), + B(0xD01004008030400, 0x104000408104420), + B(0x8908A20028110011, 0x800080000001A114), + B(0x200042000080F009, 0x20001000004000), + B(0x2820008820100, 0x10002400058000B9), + B(0x6083100420008050, 0x4040012600280080), + B(0x216020000000446, 0x4080204000000211), + B(0x340140003002089, 0x2402008000000911), + B(0xD01004008030400, 0x104000408104420), + B(0x1404040B20001000, 0x8000824010800011), + B(0x8C0488120024214, 0x8414880202291), + B(0x1010000060050000, 0x4000004050002602), + B(0x4022983A0060000, 0x80000040010400), + B(0x1404040B20001000, 0x8000824010800011), + B(0x6020101802002840, 0x31000003000004), + B(0x9000420008840, 0x4881300000000210), + B(0xA200808865, 0x41C0048023000128), + B(0x31801100400000, 0x8802DC001221240), + B(0x884000080200920, 0x1004002410401001), + B(0x2400040000884, 0x421006208040C0), + B(0x1404040B20001000, 0x8000824010800011), + B(0x24100400060009, 0x112008025042410), + B(0x1800040009040200, 0x180000A1004E408A), + B(0x24100400060009, 0x112008025042410), + B(0x4060402008080, 0xC240080000110000), + B(0x20080100920020, 0x2002248010242052), + B(0x10001010802050, 0x880000001C98420), + B(0x4000800100420022, 0x502022010A00D0), + B(0x4C18104500200885, 0x400880800), + B(0x8080810081020090, 0x8000000000000), + B(0x8000062812080201, 0x8004C8300800), + B(0xC010220920198, 0x85000A08000), + B(0x24100400060009, 0x112008025042410), + B(0x80102204040, 0x1000000900000000), + B(0x2080000004202804, 0x120880003461), + B(0x102004090A4030, 0x801020589240), + B(0x20001100814000A0, 0x420202000820004), + B(0x100800000A000120, 0x208000800010000), + B(0x1008205000040802, 0x80002000400040), + B(0x1480000098008401, 0xA0010000581010), + B(0x30C0008200100820, 0x102800080904834), + B(0x4810821884000500, 0x4400000200000212), + B(0x1811D00128A0180, 0x2500848803000000), + B(0x41618A0300040040, 0x21200200A421801), + B(0x80102204040, 0x1000000900000000), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x8C890020410000A0, 0xA010000048000400), + B(0x40006002210044, 0x600008000408000), + B(0x1200447220090042, 0x80001000160012), + B(0x48410010AB000000, 0x9200600000000100), + B(0x2040000000240003, 0x8020080288000600), + B(0x9080000088848088, 0x4010210500000041), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x480100400024, 0x1004800018200000), + B(0x808403080080200, 0x802601000000500), + B(0x8C890020410000A0, 0xA010000048000400), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x100A40000004008, 0x2800200400200480), + B(0x100A40000004008, 0x2800200400200480), + B(0x400014006000000, 0x10006000810001F5), + B(0xC410062001414, 0x820080041B01044), + B(0x20000800310, 0x430040000201000), + B(0xA40010008000008, 0x4002200028000040), + B(0xC00102000008021C, 0x10C2000A010E024), + B(0x80004200104008, 0x50A00800C400020), + B(0x20200080012542, 0x910F0040000402C0), + B(0xB040100504000300, 0x24802002000040), + B(0x800001000014008, 0x400031004000), + B(0x100A40000004008, 0x2800200400200480), + B(0x84008002041081C0, 0x8080500200000000), + B(0x440090001012001, 0x4020004010), + B(0x100A0028088020, 0x80040E00010020), + B(0x2180808000810, 0xB018040A00040000), + B(0x40C80920304C4001, 0x42800B200800000), + B(0x85000425001000, 0x4810048020001100), + B(0x600C000801000004, 0x8015084010200020), + B(0x20020050000240C0, 0x100202008600800), + B(0x38000050001220, 0x9200010200145900), + B(0x1042108040005, 0x1402A0802201001), + B(0x824240000C20400, 0x1000000400080010), + B(0x84008002041081C0, 0x8080500200000000), + B(0x400804A1000008, 0x1024104A0200010), + B(0x8000402308483, 0x20006020100100), + B(0x80880120000080, 0x8000240100084), + B(0x5840020004882001, 0x1004528000A00010), + B(0x8001018800300002, 0x84010040804), + B(0x180D10004000A008, 0xA001080008020004), + B(0x400080B, 0x10A0000004010000), + B(0x8080000200000, 0x2001000082004E0), + B(0x40040001000C2000, 0x2024800001004008), + B(0x400804A1000008, 0x1024104A0200010), + B(0x8000402308483, 0x20006020100100), + B(0x400804A1000008, 0x1024104A0200010), + B(0x2000200000, 0x1201011000802), + B(0x100100000000C4, 0x208004084048201), + B(0x400084000044, 0x100810140300), + B(0x29040C0C01010, 0x300204010820080), + B(0x1A808000020200, 0x1000000005210040), + B(0x20000400150000, 0x85008020), + B(0x40C040008184014, 0x8002AA00024010), + B(0x202000081B00804, 0x10001002008), + B(0x40011000210060, 0x6080C40000021004), + B(0x2000200000, 0x1201011000802), + B(0x4100480203840, 0x300080100804), + B(0x2000200000, 0x1201011000802), + }; + Bitboard RookMagicVInit[SQUARE_NB] = { + B(0x202000812104400, 0x24800B01C0000303), + B(0x340020400010D, 0x88060150C00400), + B(0x400802040609, 0x49010200501A0002), + B(0x8002680301000208, 0x628006C0C020200), + B(0x20400209001C0804, 0xA044000800143110), + B(0xC400082060010202, 0x4000480401014000), + B(0x22500200144040, 0x8204820084704C00), + B(0x8C1204009030020, 0x328400805000000), + B(0x84800800D0001640, 0x200080040060108), + B(0x804810208020040, 0x140010108020000), + B(0x1102010B008004, 0x300208006220020), + B(0x140080404A0A2428, 0x6308010100080), + B(0x20444002120408, 0xA080010508010001), + B(0x82011044000D02, 0x4112028620110809), + B(0x81010831000C02, 0x408802085000000), + B(0x81010831000C02, 0x408802085000000), + B(0x920008920600040, 0x8053801004000028), + B(0x81140283208300, 0x10040C004200420), + B(0x103080022201, 0xC01000081312620), + B(0x2200221100008, 0x1000408104000A4), + B(0x4402088080042008, 0x210401501040340), + B(0x898400202170001, 0x80040404208000), + B(0x20080004051012, 0x5100048200081800), + B(0x2320020000401018, 0x108501021040210), + B(0x21080410A422021, 0x83040180008800), + B(0x44E8100000408224, 0x20010008040400), + B(0x1800240002810405, 0x23004820000020), + B(0x80A0100400110, 0x80104020100C4028), + B(0x1002050001222C0, 0x5100818004024020), + B(0x104000200040, 0xC010A09800102000), + B(0x1020003A058120, 0x450900809000302), + B(0x40040045008B1, 0x202800400383010), + B(0x4640200220034, 0x8800485420304000), + B(0x5001042100084288, 0x110820001240080A), + B(0x2002C04004010120, 0xA15008020880001), + B(0x2800004080C4190, 0x890808280020080), + B(0x40C0401000104000, 0x2020880008002580), + B(0x40020C002400802, 0x801104010000000), + B(0x44842000040080, 0x2050011084000400), + B(0x4110040800000401, 0x2023810029008000), + B(0x20884000840, 0x8017102004008000), + B(0x10411104000480, 0x1414042000201001), + B(0x220040000008, 0x800306021000000), + B(0x41400A0008080, 0x501000298ACAD10), + B(0x800240012831810, 0x80120004468050E), + B(0x800005020801008, 0x20102400240000), + B(0x20C00040C114C010, 0x88080820200C00), + B(0x1044010100820081, 0x20080841004000), + B(0x8041048400022, 0x8020836040005002), + B(0x2001004010205, 0x8001002884042009), + B(0x128088400087, 0x20008002201002), + B(0x8084108040402000, 0x80809000A080400), + B(0x408081840880, 0x201002088000040), + B(0xA40180010280, 0x241004006000010), + B(0x4204100080048140, 0x2002C4F104202020), + B(0x100140A10204, 0x980200800840060), + B(0x1005140010202048, 0x1442280800202815), + B(0x2000082025008600, 0x1108400040600003), + B(0x1005050648000, 0x200020240008002), + B(0x202010208044000, 0x8210404060008), + B(0x8011040402000210, 0xC840180408016004), + B(0x404098801028, 0x80020A0001000400), + B(0x404098801028, 0x80020A0001000400), + B(0x80101002180140, 0x40C2080820000C0), + B(0x208202081260800, 0x14090E4C04000050), + B(0x4221201084004C2, 0x110480A011060), + B(0x8000008421090204, 0x1C01010800024), + B(0x8000008421090204, 0x1C01010800024), + B(0x200180C840088A0, 0x401100400820000), + B(0x10084043A021070, 0x202041600080200), + B(0x210E6202001040C, 0x10100800080B0), + B(0x848008021204002, 0x801004308100BAD), + B(0xC082C0390A000601, 0x4040080189008), + B(0x431200240210402D, 0x58102820000), + B(0x202020100A0019B0, 0x4010C0D018000000), + B(0x800800908402203, 0x102948C84C184), + B(0x26801100080845, 0x4009702022A00820), + B(0x8880520010401040, 0x1060084832052000), + B(0x100100022042081, 0x10000600008C121), + B(0x46020384100040, 0x800200320882021), + B(0xC0002010148, 0x4200800800040003), + B(0x2002208020090040, 0x40820210021410), + B(0x9000A41160002004, 0x2A09000100080043), + B(0x800004010008001, 0x1108002020104600), + B(0x800540C000A4E041, 0x18021180000401), + B(0x808200900A900202, 0x8364202140012005), + B(0x1DBA52000081010, 0x4008000023000010), + B(0x4100110204401481, 0x800040091020001C), + B(0x4100110204401481, 0x800040091020001C), + B(0x4101100020400482, 0x2000402302100120), + B(0x100408000A020212, 0xA000400111000020), + B(0x2000010488080104, 0x3000404410208100), + B(0x2684220180008DD0, 0x422040200004000A), + B(0x2021200C0424, 0x1010100000080200), + B(0x8908020020801006, 0x3010800020C2000), + B(0x4000030008062044, 0x244010202688000), + B(0x242101200408009, 0x8150040000200015), + B(0x42004C02180204, 0x210208014241040), + B(0x4E1A01C208410804, 0x8890041000012004), + B(0x2080200401000080, 0x8001098429008004), + B(0xA01400121804104, 0x280200C400000500), + B(0xD0080408040420, 0x1006040100224000), + B(0x28400205000800C9, 0x6021101401040075), + B(0x4000900040020104, 0x88129801100D0C), + B(0x8000004002180410, 0x400380200400204), + B(0x4002A430043008, 0x400200340100020), + B(0x401960004140A42, 0x100880710000464), + B(0x58014090102, 0xB8D30004010080), + B(0xA004C08000244000, 0x11280100E0000040), + B(0x2102008089208804, 0x110001004080040), + B(0x700010084E003004, 0x8080864112000D40), + B(0x4080881000200C20, 0x30324040880E0600), + B(0x2024A40401810820, 0x3000888002000000), + B(0x8200100400014, 0x4400340800252844), + B(0x24A00804288281, 0x410103002201140), + B(0x4080005022A08, 0x1000402200100264), + B(0x200080032244040, 0x200502189010001), + B(0x28108110404001, 0x400600120008412), + B(0xA00002102810020, 0xB1080240015408), + B(0x810080200806, 0x410440804080046) + }; + Bitboard BishopMagicInit[SQUARE_NB] = { + B(0x2001040305000010, 0x830200040400082), + B(0x1042400080E01200, 0x2004904010811400), + B(0x400010120200, 0x880080D080018000), + B(0x240190C00100040, 0x100A020140044404), + B(0x1018010404010004, 0x1001010018081E0), + B(0x41200A804C0904, 0x40000322000008), + B(0x4001180A004, 0x8000001106000000), + B(0x6006020020030600, 0x1840002100004841), + B(0x4200200100, 0x4001041808002000), + B(0x4100020050124600, 0x1001802902400CA0), + B(0x448C0081440161, 0x200206010008000), + B(0x400008008008408, 0x1000080210100080), + B(0x200280C01008200, 0x210200813000080), + B(0x1A000204400, 0x222200401023000), + B(0x10081040640A00, 0x8410021881400000), + B(0x1840400318080008, 0x800800840080000), + B(0x4204050C040, 0x6500600200140000), + B(0x1012100040204, 0x402404444400000), + B(0x6000012680008240, 0x410140000004220), + B(0x1000020810040008, 0x2D0011000060000), + B(0x1020020400, 0x400108059001001), + B(0x400020001100808, 0x480204800200000B), + B(0x10000010030084, 0x2042000848900022), + B(0x10000010030084, 0x2042000848900022), + B(0x100D801402400, 0x1512404009000400), + B(0x8000208005112400, 0xA02040401000000), + B(0x1000420002800200, 0x4CA000183020000), + B(0x800811480020, 0x408801010224001), + B(0xC805200810900100, 0x9000084204004020), + B(0x8200160204100004, 0x8040004004002022), + B(0x104514013080080, 0x146410040001000), + B(0x140844000080002, 0x1008102020040001), + B(0x4040400041A2002, 0x8040000A8802510), + B(0x801014041008002, 0x80068008025200), + B(0xA00540A414040, 0x4101040010A0000), + B(0x6484008010810002, 0x1100506884024000), + B(0x2800401008006000, 0x1005420884029020), + B(0x6822091010004421, 0x2000458080480), + B(0x40101000200101, 0x10020100001C4E0), + B(0x100400008C42, 0x4000100009008000), + B(0x851220018800400, 0x1681800040080080), + B(0x64200002010, 0x900020200040002), + B(0x20800080000022, 0x80040810002010), + B(0xA88408000802080, 0x20808001000000), + B(0x200000400C005040, 0x100140020290108), + B(0x224100000800408, 0x4204802004400020), + B(0x80080620010210, 0x91080088804040), + B(0x4008002100010, 0x80AC201001000001), + B(0x10008200902C046, 0x8080D03004000010), + B(0x3002100081000180, 0x2210002121528408), + B(0x8C101800804420, 0x1019880200043008), + B(0x200022000920D0, 0x8000800081300020), + B(0x1D40800880000, 0x400040001400050), + B(0x2020004100040, 0x200008040008008), + B(0x4840800040100001, 0x100100040203040), + B(0x40084001105, 0x8800080088000089), + B(0x4000128008020008, 0x4004200200440020), + B(0x210040008520000, 0x820219001080022), + B(0x1494040018002116, 0x400101047020008), + B(0x510008001910C224, 0x80200148118000), + B(0xC0301002301000, 0x4211A08004801), + B(0x50008E0C01001080, 0x100C004102845100), + B(0x400600020060400, 0x88024100250050), + B(0x8202920002002040, 0x810012000003), + B(0x800004208800200, 0x18AA00201000048), + B(0x402100800100002, 0x411000081000400), + B(0x101000022004044, 0x9000100040000), + B(0x41068001001, 0xC00400010001), + B(0x310210001040, 0x1A1200020010000), + B(0xA082409200004048, 0x490040800124101), + B(0x18844820E0040212, 0x1000404420D10000), + B(0x802908A40003348, 0x20200040104140), + B(0x1800404028205003, 0xC020010401089020), + B(0x802100044D01000, 0x8C41888000800040), + B(0x1D0161011410081, 0x10008000100200), + B(0x401000480040100, 0x286800404002212), + B(0x821030000100009, 0x2000090200A00000), + B(0x200020800200800, 0x2000480900841012), + B(0x80A000048030080, 0x200000120200008), + B(0x40B1400008020020, 0x148000200008004), + B(0xA021700002002010, 0x3040E400040100), + B(0x400242C200200640, 0x20440210200281), + B(0x80AC140040206240, 0x120000102801401), + B(0x2020340040832040, 0x10402100A44000), + B(0x420100400040220, 0x80014C8004000106), + B(0x504300822421120, 0x8004004008400100), + B(0x2001100008040, 0x2020104302000000), + B(0xA500802000A, 0x2008008000114100), + B(0x8A0020000200, 0x9C00101001002408), + B(0x104000001001008, 0x9001000204040060), + B(0x1000820080108200, 0xA401000008100001), + B(0x2008600009000480, 0x9008020001400000), + B(0x4000800200040200, 0xA00030400308082), + B(0x4004300202004709, 0x1000100180010020), + B(0xC014800100440010, 0x402020280002C010), + B(0x220208010884680, 0x1040280000042110), + B(0x40B0018019202801, 0x1008408000100040), + B(0x8269010206080044, 0x8001810000000040), + B(0x4000020880081040, 0x208A44000028000), + B(0x4004004E9004220A, 0x2104004001400024), + B(0x8035006008C0904, 0x402002001080120), + B(0x1800884002, 0x404400820000000), + B(0x8088000004008910, 0x8024100401000000), + B(0x142200086000100, 0x28021040020002E), + B(0x1000409141004018, 0x100410820080040A), + B(0x1800801800140, 0x810801060C0801), + B(0x1000C00100402220, 0x808023420000000), + B(0x8A0A202414305008, 0x100040200000021), + B(0xC0208024050, 0x8003088008020401), + B(0x8044004201440101, 0x400820080C024022), + B(0x406018884120099, 0xB00088018002000), + B(0x2000800010403010, 0xC5A002002010010), + B(0x800020040840, 0x201800202800200), + B(0x201280120020008D, 0x258809001000040), + B(0x9100002020181, 0x80400082204000), + B(0x104010080201001, 0x40080080181080), + B(0x8440248092000430, 0xA200804900100000), + B(0x2031010C01000C20, 0x200310A560082008), + B(0x400202081811400, 0x40081802050000C), + B(0x1011002100821300, 0x2400825040804100) + }; + Bitboard CannonMagicHInit[SQUARE_NB] = { + B(0x120000880110000, 0x1008000000020020), + B(0x24200C080840A052, 0x2004004000010008), + B(0xC030024000228800, 0x4000010400000020), + B(0x1A0020802008802, 0x206010208000), + B(0x12002000D001024, 0x80100800090138), + B(0x4220010000241010, 0x3098000602001500), + B(0x401010004801040, 0x8000280480100000), + B(0x820082024921836, 0x220028000), + B(0x100400502411400, 0x220402120240D14), + B(0x880202020010404, 0xA80202510000), + B(0x140002801000018, 0x1000346490040), + B(0x120000880110000, 0x1008000000020020), + B(0xD01004008030400, 0x104000408104420), + B(0x8420060100020000, 0x800280400000120), + B(0x4010020018010, 0x40A00001100000), + B(0x40006A0004000200, 0x40000000110), + B(0xD01004008030400, 0x104000408104420), + B(0x8908A20028110011, 0x800080000001A114), + B(0x200042000080F009, 0x20001000004000), + B(0x2820008820100, 0x10002400058000B9), + B(0x6083100420008050, 0x4040012600280080), + B(0x216020000000446, 0x4080204000000211), + B(0x340140003002089, 0x2402008000000911), + B(0xD01004008030400, 0x104000408104420), + B(0x1404040B20001000, 0x8000824010800011), + B(0x8C0488120024214, 0x8414880202291), + B(0x1010000060050000, 0x4000004050002602), + B(0x4022983A0060000, 0x80000040010400), + B(0x1404040B20001000, 0x8000824010800011), + B(0x6020101802002840, 0x31000003000004), + B(0x9000420008840, 0x4881300000000210), + B(0xA200808865, 0x41C0048023000128), + B(0x31801100400000, 0x8802DC001221240), + B(0x884000080200920, 0x1004002410401001), + B(0x2400040000884, 0x421006208040C0), + B(0x1404040B20001000, 0x8000824010800011), + B(0x24100400060009, 0x112008025042410), + B(0x1800040009040200, 0x180000A1004E408A), + B(0x24100400060009, 0x112008025042410), + B(0x4060402008080, 0xC240080000110000), + B(0x20080100920020, 0x2002248010242052), + B(0x10001010802050, 0x880000001C98420), + B(0x4000800100420022, 0x502022010A00D0), + B(0x4C18104500200885, 0x400880800), + B(0x8080810081020090, 0x8000000000000), + B(0x8000062812080201, 0x8004C8300800), + B(0x1800040009040200, 0x180000A1004E408A), + B(0x24100400060009, 0x112008025042410), + B(0x80102204040, 0x1000000900000000), + B(0x2080000004202804, 0x120880003461), + B(0x102004090A4030, 0x801020589240), + B(0x20001100814000A0, 0x420202000820004), + B(0x100800000A000120, 0x208000800010000), + B(0x1008205000040802, 0x80002000400040), + B(0x1480000098008401, 0xA0010000581010), + B(0x30C0008200100820, 0x102800080904834), + B(0x4810821884000500, 0x4400000200000212), + B(0x1811D00128A0180, 0x2500848803000000), + B(0x41618A0300040040, 0x21200200A421801), + B(0x80102204040, 0x1000000900000000), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x8C890020410000A0, 0xA010000048000400), + B(0x40006002210044, 0x600008000408000), + B(0x1200447220090042, 0x80001000160012), + B(0x48410010AB000000, 0x9200600000000100), + B(0x2040000000240003, 0x8020080288000600), + B(0x9080000088848088, 0x4010210500000041), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x480100400024, 0x1004800018200000), + B(0x808403080080200, 0x802601000000500), + B(0x8C890020410000A0, 0xA010000048000400), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x100A40000004008, 0x2800200400200480), + B(0x100A40000004008, 0x2800200400200480), + B(0x400014006000000, 0x10006000810001F5), + B(0xC410062001414, 0x820080041B01044), + B(0x20000800310, 0x430040000201000), + B(0xA40010008000008, 0x4002200028000040), + B(0xC00102000008021C, 0x10C2000A010E024), + B(0x80004200104008, 0x50A00800C400020), + B(0x20200080012542, 0x910F0040000402C0), + B(0xB040100504000300, 0x24802002000040), + B(0x800001000014008, 0x400031004000), + B(0x100A40000004008, 0x2800200400200480), + B(0x84008002041081C0, 0x8080500200000000), + B(0x440090001012001, 0x4020004010), + B(0x100A0028088020, 0x80040E00010020), + B(0x2180808000810, 0xB018040A00040000), + B(0x40C80920304C4001, 0x42800B200800000), + B(0x85000425001000, 0x4810048020001100), + B(0x600C000801000004, 0x8015084010200020), + B(0x20020050000240C0, 0x100202008600800), + B(0x38000050001220, 0x9200010200145900), + B(0x1042108040005, 0x1402A0802201001), + B(0x824240000C20400, 0x1000000400080010), + B(0x84008002041081C0, 0x8080500200000000), + B(0x400804A1000008, 0x1024104A0200010), + B(0x8000402308483, 0x20006020100100), + B(0x80880120000080, 0x8000240100084), + B(0x5840020004882001, 0x1004528000A00010), + B(0x8001018800300002, 0x84010040804), + B(0x180D10004000A008, 0xA001080008020004), + B(0x400080B, 0x10A0000004010000), + B(0x8080000200000, 0x2001000082004E0), + B(0x40040001000C2000, 0x2024800001004008), + B(0x400804A1000008, 0x1024104A0200010), + B(0x8000402308483, 0x20006020100100), + B(0x400804A1000008, 0x1024104A0200010), + B(0x2000200000, 0x1201011000802), + B(0x100100000000C4, 0x208004084048201), + B(0x400084000044, 0x100810140300), + B(0x29040C0C01010, 0x300204010820080), + B(0x1A808000020200, 0x1000000005210040), + B(0x20000400150000, 0x85008020), + B(0x40C040008184014, 0x8002AA00024010), + B(0x202000081B00804, 0x10001002008), + B(0x40011000210060, 0x6080C40000021004), + B(0x2000200000, 0x1201011000802), + B(0x4100480203840, 0x300080100804), + B(0x2000200000, 0x1201011000802), + }; + Bitboard CannonMagicVInit[SQUARE_NB] = { + B(0x202000812104400, 0x24800B01C0000303), + B(0x340020400010D, 0x88060150C00400), + B(0x400802040609, 0x49010200501A0002), + B(0x8002680301000208, 0x628006C0C020200), + B(0x20400209001C0804, 0xA044000800143110), + B(0xC400082060010202, 0x4000480401014000), + B(0x22500200144040, 0x8204820084704C00), + B(0x8C1204009030020, 0x328400805000000), + B(0x84800800D0001640, 0x200080040060108), + B(0x804810208020040, 0x140010108020000), + B(0x1102010B008004, 0x300208006220020), + B(0x140080404A0A2428, 0x6308010100080), + B(0x20444002120408, 0xA080010508010001), + B(0x82011044000D02, 0x4112028620110809), + B(0x81010831000C02, 0x408802085000000), + B(0x81010831000C02, 0x408802085000000), + B(0x920008920600040, 0x8053801004000028), + B(0x81140283208300, 0x10040C004200420), + B(0x103080022201, 0xC01000081312620), + B(0x2200221100008, 0x1000408104000A4), + B(0x4402088080042008, 0x210401501040340), + B(0x898400202170001, 0x80040404208000), + B(0x20080004051012, 0x5100048200081800), + B(0x2320020000401018, 0x108501021040210), + B(0x21080410A422021, 0x83040180008800), + B(0x44E8100000408224, 0x20010008040400), + B(0x1800240002810405, 0x23004820000020), + B(0x80A0100400110, 0x80104020100C4028), + B(0x1002050001222C0, 0x5100818004024020), + B(0x104000200040, 0xC010A09800102000), + B(0x1020003A058120, 0x450900809000302), + B(0x40040045008B1, 0x202800400383010), + B(0x4640200220034, 0x8800485420304000), + B(0x5001042100084288, 0x110820001240080A), + B(0x2002C04004010120, 0xA15008020880001), + B(0x2800004080C4190, 0x890808280020080), + B(0x40C0401000104000, 0x2020880008002580), + B(0x40020C002400802, 0x801104010000000), + B(0x44842000040080, 0x2050011084000400), + B(0x4110040800000401, 0x2023810029008000), + B(0x20884000840, 0x8017102004008000), + B(0x10411104000480, 0x1414042000201001), + B(0x220040000008, 0x800306021000000), + B(0x41400A0008080, 0x501000298ACAD10), + B(0x800240012831810, 0x80120004468050E), + B(0x800005020801008, 0x20102400240000), + B(0x20C00040C114C010, 0x88080820200C00), + B(0x1044010100820081, 0x20080841004000), + B(0x8041048400022, 0x8020836040005002), + B(0x2001004010205, 0x8001002884042009), + B(0x128088400087, 0x20008002201002), + B(0x8084108040402000, 0x80809000A080400), + B(0x408081840880, 0x201002088000040), + B(0xA40180010280, 0x241004006000010), + B(0x4204100080048140, 0x2002C4F104202020), + B(0x100140A10204, 0x980200800840060), + B(0x1005140010202048, 0x1442280800202815), + B(0x2000082025008600, 0x1108400040600003), + B(0x1005050648000, 0x200020240008002), + B(0x202010208044000, 0x8210404060008), + B(0x8011040402000210, 0xC840180408016004), + B(0x404098801028, 0x80020A0001000400), + B(0x404098801028, 0x80020A0001000400), + B(0x80101002180140, 0x40C2080820000C0), + B(0x208202081260800, 0x14090E4C04000050), + B(0x4221201084004C2, 0x110480A011060), + B(0x8000008421090204, 0x1C01010800024), + B(0x8000008421090204, 0x1C01010800024), + B(0x200180C840088A0, 0x401100400820000), + B(0x10084043A021070, 0x202041600080200), + B(0x210E6202001040C, 0x10100800080B0), + B(0x848008021204002, 0x801004308100BAD), + B(0xC082C0390A000601, 0x4040080189008), + B(0x431200240210402D, 0x58102820000), + B(0x202020100A0019B0, 0x4010C0D018000000), + B(0x800800908402203, 0x102948C84C184), + B(0x26801100080845, 0x4009702022A00820), + B(0x8880520010401040, 0x1060084832052000), + B(0x100100022042081, 0x10000600008C121), + B(0x46020384100040, 0x800200320882021), + B(0xC0002010148, 0x4200800800040003), + B(0x2002208020090040, 0x40820210021410), + B(0x9000A41160002004, 0x2A09000100080043), + B(0x800004010008001, 0x1108002020104600), + B(0x800540C000A4E041, 0x18021180000401), + B(0x808200900A900202, 0x8364202140012005), + B(0x1DBA52000081010, 0x4008000023000010), + B(0x4100110204401481, 0x800040091020001C), + B(0x4100110204401481, 0x800040091020001C), + B(0x4101100020400482, 0x2000402302100120), + B(0x100408000A020212, 0xA000400111000020), + B(0x2000010488080104, 0x3000404410208100), + B(0x2684220180008DD0, 0x422040200004000A), + B(0x2021200C0424, 0x1010100000080200), + B(0x8908020020801006, 0x3010800020C2000), + B(0x4000030008062044, 0x244010202688000), + B(0x242101200408009, 0x8150040000200015), + B(0x42004C02180204, 0x210208014241040), + B(0x4E1A01C208410804, 0x8890041000012004), + B(0x2080200401000080, 0x8001098429008004), + B(0xA01400121804104, 0x280200C400000500), + B(0xD0080408040420, 0x1006040100224000), + B(0x28400205000800C9, 0x6021101401040075), + B(0x4000900040020104, 0x88129801100D0C), + B(0x8000004002180410, 0x400380200400204), + B(0x4002A430043008, 0x400200340100020), + B(0x401960004140A42, 0x100880710000464), + B(0x58014090102, 0xB8D30004010080), + B(0xA004C08000244000, 0x11280100E0000040), + B(0x2102008089208804, 0x110001004080040), + B(0x700010084E003004, 0x8080864112000D40), + B(0x4080881000200C20, 0x30324040880E0600), + B(0x2024A40401810820, 0x3000888002000000), + B(0x8200100400014, 0x4400340800252844), + B(0x24A00804288281, 0x410103002201140), + B(0x4080005022A08, 0x1000402200100264), + B(0x200080032244040, 0x200502189010001), + B(0x28108110404001, 0x400600120008412), + B(0xA00002102810020, 0xB1080240015408), + B(0x810080200806, 0x410440804080046), + }; + Bitboard HorseMagicInit[SQUARE_NB] = { + B(0x3C080482A592000C, 0x540104000020000), + B(0x2802C40008000420, 0x4A00000001818009), + B(0x1083040280804000, 0x120004C20100880), + B(0x6840940880000892, 0x2014A01080800C2), + B(0x8401489004000180, 0x2000800000400000), + B(0x820161C800000110, 0x8000100000204020), + B(0x610011A122000109, 0x1000004020008004), + B(0x83282004023000, 0xE000020004848446), + B(0x6840940880000892, 0x2014A01080800C2), + B(0x4020120800800002, 0x88008000010020), + B(0x30025B140A1000, 0x3141801401000040), + B(0x41104D1810100050, 0x8141002010910), + B(0x4200828A298400, 0x400340001040C000), + B(0x8016A4900110040, 0x844812001068020), + B(0x2250035820400A2, 0x8012010080900), + B(0x820080083A009000, 0x880404091080110), + B(0x80401500AF0020, 0x240000082201A04), + B(0x668020020C081005, 0x4008001004100021), + B(0x240100910000000, 0x82000A0030454000), + B(0xA24091400008, 0x200014880004A921), + B(0x840110042200410, 0x100080000A400000), + B(0x40024024102000, 0x1000000002180404), + B(0x92828423000530, 0x118800020110), + B(0x1122404A1C90A8, 0x822040280020D00), + B(0x41201A40900A000, 0x80C0480040605100), + B(0x2504A85005488280, 0x3028112120022800), + B(0x210180080626B048, 0x8000401000014000), + B(0x1000410401040200, 0x41014000050C0106), + B(0x1040650210802200, 0x80C0041000000), + B(0x4020C10110900002, 0x2140C2001050009), + B(0x191180092200022, 0x6010008400400800), + B(0x8010821088080202, 0xCA240011008208), + B(0x8C0488120024214, 0x8414880202291), + B(0x8C0488120024214, 0x8414880202291), + B(0x22080C8A0161401, 0x200C10004C002002), + B(0x8430818023034080, 0x210090800000801), + B(0x4845087008200, 0x40661480000), + B(0x1202804428812050, 0x100022038020000), + B(0x400016001201080, 0x24002200402060), + B(0x680E041300800800, 0xE00130080004000), + B(0x3409080200, 0x282840210000000), + B(0x803310108400, 0x85200000080100A0), + B(0xE180008A04162104, 0x9088240412404), + B(0x20080100920020, 0x2002248010242052), + B(0x8A000400C2410, 0x1000024086014300), + B(0x1821040024663, 0x100000100010009), + B(0x4000822310611, 0x120280406014008), + B(0x1004008010818D08, 0x800000141892000), + B(0x8010800004024042, 0x44B106008800896), + B(0xA0063423444, 0x41002C15811008), + B(0x2040012381001282, 0x4804080104A4000), + B(0x10840101820880, 0xA800008000020020), + B(0x10840101820880, 0xA800008000020020), + B(0x60201D8300408190, 0x2010020920200000), + B(0x4048100200090090, 0x2008090100000900), + B(0x24200000280210, 0xD440050008004000), + B(0x1280001000580020, 0x2200040089000A4), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x4108000010209089, 0x913000000024840), + B(0x410C208008008E02, 0xE8000000000001), + B(0x802208004005, 0x94206000022080), + B(0xC00290018902002, 0x4204100000000000), + B(0x2102801400093816, 0x9810004001000202), + B(0x8008304000015800, 0x4A5C000000020000), + B(0x1020108380800514, 0x1144210000000080), + B(0xC0001000008090, 0x2812060000204000), + B(0x1001100200003100, 0x246240060A004004), + B(0xA00020A008002030, 0x2440C40000110B00), + B(0x80502104000C008, 0x8222200042100010), + B(0xC020200088014, 0x422094000000480), + B(0x1029002000001030, 0x8105841120000210), + B(0x49040D, 0x2310808A14042C0), + B(0x200040200080A02C, 0xB890290400080000), + B(0x2240180C0800002, 0x4151050280000100), + B(0x2240180C0800002, 0x4151050280000100), + B(0x8220224180420006, 0x4024501212011000), + B(0x1806810A0881000, 0x802002048400080), + B(0x400400A080842, 0x9305000401180000), + B(0x10008001444110, 0x4420401040041833), + B(0x2000002C02010E00, 0x400408D08009804), + B(0x69D008200020100, 0x100842240049021), + B(0x42C24450020000, 0xD38400880090884), + B(0x485800800100001, 0x2484086522018840), + B(0x900200020820042, 0x22302421400040C0), + B(0x50B0413001818000, 0x452014040800C40), + B(0x8004040021008, 0x20088A08000290), + B(0x600C000801000004, 0x8015084010200020), + B(0x208000C00, 0xE004804021100100), + B(0x20001000040204, 0x948110C0B2081), + B(0x268502400100021, 0x80A201840802080), + B(0x408C000008, 0x8822102408014), + B(0x1182080410100000, 0x608002046A0100), + B(0x100820A083C00002, 0x3100100410A00), + B(0x8401040000400124, 0x2000081288202200), + B(0xB014040003000800, 0x11960D1101210), + B(0x10040001900C000, 0x85603C1001280), + B(0x2000844000000100, 0x2000024C60800800), + B(0x120004234800900, 0x210010841040), + B(0x8010300040000002, 0x4200008222104100), + B(0x1000120402200100, 0x209080CC040108B4), + B(0x110049A00000800, 0x80000420022180A8), + B(0x80001C00080384, 0x1400101111081001), + B(0x8011200008100428, 0x2020000880800922), + B(0x10001000000204C8, 0x280C11104240), + B(0x50100C82C000500, 0x28000280618DD1), + B(0x8800498020000, 0x20500A0200320128), + B(0x20010104000860, 0x8021720186008), + B(0x4000000000100080, 0x35040084270C04), + B(0x4500080000800, 0x280100002482C842), + B(0x10400000000000, 0x20080051100130C2), + B(0x10400000000000, 0x20080051100130C2), + B(0x2000002110202014, 0x121004004004681), + B(0x400202001006D40, 0x82240082202424), + B(0x4500080000800, 0x280100002482C842), + B(0xC6000000D00804, 0x1050020C0081090C), + B(0x200080000000042, 0x10800661), + B(0x2000001011200200, 0x2A420000802A0222), + B(0x802020001202412, 0x2400404148426), + B(0x8000440801040002, 0x444002800010052A), + }; + Bitboard ElephantMagicInit[SQUARE_NB] = { + B(0x64D2990200008, 0x4401880001C000), + B(0x29BAA00010020, 0x200000400800600), + B(0x3024240000000, 0x4100400010080), + B(0xA490A00480020, 0x20084001041010A4), + B(0x328C021008042, 0x100000000C10204), + B(0x1964090001018, 0x7002040148001205), + B(0x800302098404080, 0x4983020000000001), + B(0x8812244630A02080, 0x8200006204003C08), + B(0x41120231008000, 0x240441401020), + B(0x840091030C00040, 0x1400008200023400), + B(0x8001040E77030200, 0x100040090022000), + B(0x602022139D835040, 0x101002010025900), + B(0x405707C48400, 0x40010000008001), + B(0x982003456A82050, 0x60800820040030), + B(0x204184849200088, 0x101800004006), + B(0x300222470949200, 0x2A0800200200800), + B(0x400001211914000, 0x8200001407001), + B(0x2000008614831020, 0x4000020001404000), + B(0x84000024A2048048, 0x1200102000042), + B(0x424010A58422008, 0x88440242212A0110), + B(0x20020812C0C4408, 0x4121400000080010), + B(0x680200062042420, 0x2001100000800000), + B(0x200010060AEC855, 0x8083002040200000), + B(0x4000008BAA85810, 0x82000805C0200A90), + B(0x81450B200A025400, 0x4400101050000040), + B(0x820A2241409010, 0x888420030000), + B(0x909203000028, 0xC000004C00200041), + B(0x8021400A84880240, 0x100180002010020), + B(0x8001A20061410000, 0x14008499A000000), + B(0x8201444800A00080, 0x402010040588120), + B(0x100C06280020, 0x60010104840130), + B(0x520040800080044, 0x8220000080001402), + B(0x102021410040202, 0x2004400410006000), + B(0x5401832090020400, 0x300010020001), + B(0x180003105A84C108, 0x1012008800081000), + B(0x480C10210026904, 0xA006000004200418), + B(0x48050820210843A6, 0x108001004000C00), + B(0x1030101182206324, 0x4401008921502002), + B(0x40281060800800, 0x406000201260022), + B(0xC29002440040C820, 0x400001002008020), + B(0x40000400800241, 0xC220000000400280), + B(0x40880126014208, 0x2A8004C008940000), + B(0x121028100114080, 0x5010280481100082), + B(0x4000088280442, 0x908420140008041), + B(0x808C42400C0020, 0x3028100840801000), + B(0x4000000410078488, 0x501000000620000), + B(0x90080001421020A4, 0x4118400101060406), + B(0x280420004855, 0xD200100400820000), + B(0xA0063423444, 0x41002C15811008), + B(0x200061201808102, 0x4286969000200002), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x4001A04880402000, 0x8100824080000001), + B(0x60201D8300408190, 0x2010020920200000), + B(0x20018C04908019, 0x2010884002002040), + B(0x800000000C40810, 0x680100081150000D), + B(0x2002002000040040, 0x8810049000010600), + B(0x41618A0300040040, 0x21200200A421801), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x5A04001400412854, 0x8A44006000010002), + B(0x13000C0810072432, 0x50049001021104), + B(0x400048801142130, 0x4C1204100226010C), + B(0x80001048, 0x408800104000080), + B(0x8104868204040412, 0x22244202000081), + B(0x8104868204040412, 0x22244202000081), + B(0x4140001000240440, 0x80209004410004E), + B(0x800800000100, 0xB111820100000002), + B(0x404240004220, 0x2110402802050080), + B(0x284010400004040, 0x100245002502020), + B(0x14880A100114010, 0x400208080010024), + B(0x4100004040440648, 0x10030D838041A80), + B(0x32004000210, 0x4010225C88014000), + B(0x2240180C0800002, 0x4151050280000100), + B(0x2010A12002000042, 0x189051442010000), + B(0x4060050080121883, 0x8250C10001000141), + B(0x10000000044100, 0x8401084010261009), + B(0xA00028040000, 0x2003224000002000), + B(0x2060009001000020, 0x1000432022020228), + B(0x404200000883080, 0x1080800848245000), + B(0x240000402080, 0xCA0820814210502), + B(0x200040200080A02C, 0xB890290400080000), + B(0x800000000300482, 0x9203008100100013), + B(0x8000210202042000, 0x22642104004C2400), + B(0x1040400805000401, 0x2A0300102C80010), + B(0x8010A01088020000, 0x122106105A06A030), + B(0x8000C00001010494, 0x130A1A20404120), + B(0x4B084010844290, 0x10A08008900840), + B(0x1180001802460000, 0xB08000034C82004), + B(0x4001880060028029, 0x204040002401000), + B(0x8021A0001308002A, 0x97001822040040), + B(0xC00000009A020AC1, 0x1000080900400), + B(0x60010110001990, 0x4000880900400000), + B(0x10290402401200, 0x230080402C08), + B(0x4220000219012000, 0x140204804100008), + B(0x1400200100002, 0x8E62200414128), + B(0x402808502004403, 0x20049100C0284520), + B(0xB30041004280280, 0x10020464DB200308), + B(0x440010800808, 0xA0102E295812100), + B(0x10008000B000, 0x2000058583220200), + B(0x2000844000000100, 0x2000024C60800800), + B(0x110000400100028, 0x24052304508004), + B(0x8458000000840004, 0x118006463400001), + B(0x804008000040050, 0x41044890228000), + B(0x20000050000400, 0x80A101824A00086), + B(0x600080404000020, 0x100007322480005), + B(0xD082200020020008, 0x642000630120001), + B(0x10000100040230, 0x8048114733320002), + B(0x20200442002A880A, 0x8200002CB4B8052), + B(0x290080000000, 0xA41297838F40D), + B(0x800205000080, 0xF221232039874400), + B(0x1444002004880C20, 0xC4100049144200), + B(0x4500080000800, 0x280100002482C842), + B(0x281240881008, 0x204084004C101900), + B(0x1444002004880C20, 0xC4100049144200), + B(0x4500080000800, 0x280100002482C842), + B(0xC0010928430540, 0x92041902180), + B(0x1051001208A, 0x4900064800C20640), + B(0x882020418C00000, 0x30004040092A821), + B(0x224404002004268C, 0x202500204C7D254), + B(0x290080000000, 0xA41297838F40D), + }; + Bitboard JanggiElephantMagicInit[SQUARE_NB] = { + B(0xC502282200061400, 0x2D07081241D90200), + B(0xC502282200061400, 0x2D07081241D90200), + B(0x8084810022440C2, 0x81402202004), + B(0x80204010A800500, 0x5000021001740218), + B(0x8048100401208000, 0x2001000390000044), + B(0x202080020000000, 0x4010800010090424), + B(0x4081A0480073200, 0x100000A010406000), + B(0x4081A0480073200, 0x100000A010406000), + B(0x2040450004000C40, 0x8400000006302), + B(0x84010410018201, 0xA00A00000100000), + B(0x840091030C00040, 0x1400008200023400), + B(0x801058C0A0022, 0xC1920480010034), + B(0x80B4004800840800, 0x4080210A42040010), + B(0x400402221000445, 0x80321200408040), + B(0x4028142401012A00, 0x4005009000104448), + B(0x1440102040800220, 0x82800010A082000), + B(0x4100040300C00200, 0x800805100120000), + B(0x8200080061100, 0x2000101400000), + B(0x2000100410070001, 0x40818200B0900410), + B(0x400088020080000, 0x4A000402000CA0), + B(0x1402040410004000, 0x9840044504040), + B(0x20800088A00A0400, 0x1000020100180), + B(0x2001820520308201, 0x2008003404349000), + B(0x4004808022100, 0x8001000008081080), + B(0x102041041100425, 0x840400180B100104), + B(0x8806446000800214, 0x404402100010000), + B(0x8200141409C04101, 0x209030004A00D00), + B(0x8806004800880080, 0x1560004201000A01), + B(0x4200050600200090, 0x1CD0000000000421), + B(0x4820100022408100, 0x101404080320), + B(0x2A000A0A08080080, 0x1C02808000C2C0), + B(0x8808425040040014, 0x2021000100020), + B(0x5282104044A0020, 0x6B402104200008), + B(0x4001091040068120, 0x202000004003031), + B(0x4001091040068120, 0x202000004003031), + B(0x98040200A0214344, 0xA00300840010), + B(0x82508040A40808A, 0x40010000110042), + B(0x4400100101023, 0x450C8480040022), + B(0x210588880010800, 0x800A000108018102), + B(0x9400010144400, 0xC00010100018000), + B(0x20A0400100040004, 0x1242000101002040), + B(0x8022900040001001, 0x100000014000260), + B(0x51004124000A080, 0x40098400000002), + B(0x2158040001080022, 0x80009238401222), + B(0xA0103A0000802220, 0x20000200400010), + B(0x1101001208240, 0x100000800001064), + B(0x821020002090081, 0x5840D0010290280), + B(0x821020002090081, 0x5840D0010290280), + B(0x10400C1042000400, 0x4005000000440200), + B(0x844022008804820, 0x1000800100118000), + B(0x10802A9800800139, 0x4802840100842200), + B(0x4000A008200081, 0x4001100200402000), + B(0x200000008108400, 0x1000C00008080020), + B(0x120C11500100081, 0x440300308041100), + B(0x8080040080060100, 0xC00101B0040028), + B(0x901420A00110000, 0x8200010044700280), + B(0x140080080410000, 0x808040000C001001), + B(0x80210C0200A0008, 0x88088004600201), + B(0x8000004202020301, 0x2100142104002000), + B(0x1101011210004880, 0x8500840400000000), + B(0x40208802004800, 0x8080806009011240), + B(0x800000140408880, 0xC001018004060040), + B(0xC008080420500, 0x8024A10000000000), + B(0x2800000000400010, 0x44001C00400408), + B(0xA804008001200408, 0x202000020001000), + B(0xC08288805004080, 0x200042000800004), + B(0xA40A01000080012, 0x8800080042408), + B(0x2200100000100810, 0x800200010000100), + B(0x9881800004040001, 0x8058100100884004), + B(0x820000044020014, 0x4AA00010245012), + B(0x820000044020014, 0x4AA00010245012), + B(0x4000080240000808, 0x10100022054000), + B(0x5002000840101, 0x202020004000A00), + B(0x1188008200008402, 0x8088100020A2204), + B(0x304012004044080, 0x8028108818006010), + B(0x102210000008400, 0x1008000200380002), + B(0x51410E114200, 0x100C00084000000), + B(0x5001242320218, 0x800025000040040), + B(0x4008000200008190, 0x400020021000000), + B(0x10910022F0040, 0x450084400040001), + B(0x180010810000040, 0x4004100040040), + B(0x1088801424062010, 0x400084010030401), + B(0x3000120408000040, 0x10802001080A4051), + B(0x200008420, 0x40C0100020008804), + B(0x1048C000004000, 0x4220120804004000), + B(0x404A180000000E, 0x4C30412008110102), + B(0x400000404202005, 0x800808550EC40044), + B(0x282000200212010, 0x8001C0C102000210), + B(0x9012240000008100, 0x280CA04010040000), + B(0x2000C04001020C00, 0x2002010101042000), + B(0x1010000204408408, 0x8008004800E0C4A), + B(0x800286801000025, 0x8402401040050088), + B(0x40002000A0880000, 0x8400300108082086), + B(0x2080004404011, 0x20C080400100001), + B(0xB0010218100800, 0x8040200482C14103), + B(0x8011035000000C20, 0x4200044043200040), + B(0x804008000040050, 0x41044890228000), + B(0x80000400A0020020, 0x5308022021000000), + B(0x2118200000008004, 0x4141014004423D00), + B(0x90C0000200008040, 0x41041062000082), + B(0x1D000100941204, 0x12402001200420), + B(0x8C0040400400065, 0x22300B408100000), + B(0x8C0040400400065, 0x22300B408100000), + B(0x802802044600000, 0x1210100401030082), + B(0x9400488010000000, 0x8005404902040000), + B(0x2214020200001, 0x40102100820200), + B(0x2022000000800000, 0x6400440108480), + B(0x110000400100028, 0x24052304508004), + B(0x848820140010000, 0x201012500A000), + B(0x848820140010000, 0x201012500A000), + B(0x100100000000C4, 0x208004084048201), + B(0x100500000000290, 0x10102818208000), + B(0x2800414000C000, 0x20004005001301), + B(0x698180005101241, 0x10002014800210), + B(0x20000080000009, 0x440340C040), + B(0x1C0220200290020, 0x42100004004011C0), + B(0x200E620018320208, 0x440410402), + B(0xD04101010004024, 0x20000121104010A4), + B(0x220400000A80040, 0x806080020810010C), + B(0xA000200000000080, 0x1040801A0081208), + }; +#undef B +#endif + +#endif // #ifndef MAGIC_H_INCLUDED diff --cc src/main.cpp index 8ac8329,ef66246..c0a79b8 --- a/src/main.cpp +++ b/src/main.cpp @@@ -25,14 -27,7 +27,10 @@@ #include "thread.h" #include "tt.h" #include "uci.h" + +#include "piece.h" +#include "variant.h" - #include "syzygy/tbprobe.h" - - namespace PSQT { - void init(const Variant* v); - } + int main(int argc, char* argv[]) { std::cout << engine_info() << std::endl; diff --cc src/movegen.cpp index 8391df1,14df1f0..f1becbb --- a/src/movegen.cpp +++ b/src/movegen.cpp @@@ -182,46 -125,16 +182,46 @@@ namespace Bitboard b3 = shift(pawnsOn7) & emptySquares; while (b1) - moveList = make_promotions(moveList, pop_lsb(&b1), ksq); + moveList = make_promotions(pos, moveList, pop_lsb(&b1)); while (b2) - moveList = make_promotions(moveList, pop_lsb(&b2), ksq); + moveList = make_promotions(pos, moveList, pop_lsb(&b2)); while (b3) - moveList = make_promotions(moveList, pop_lsb(&b3), ksq); + moveList = make_promotions(pos, moveList, pop_lsb(&b3)); + } + + // Sittuyin promotions + if (pos.sittuyin_promotion() && (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)) + { + Bitboard pawns = pos.pieces(Us, PAWN); + // Pawns need to be on diagonals on opponent's half if there is more than one pawn + if (pos.count(Us) > 1) + pawns &= ( PseudoAttacks[Us][BISHOP][make_square(FILE_A, relative_rank(Us, RANK_1, pos.max_rank()))] + | PseudoAttacks[Us][BISHOP][make_square(pos.max_file(), relative_rank(Us, RANK_1, pos.max_rank()))]) + & forward_ranks_bb(Us, relative_rank(Us, Rank((pos.max_rank() - 1) / 2), pos.max_rank())); + while (pawns) + { + Square from = pop_lsb(&pawns); + for (PieceType pt : pos.promotion_piece_types()) + { + if (pos.promotion_limit(pt) && pos.promotion_limit(pt) <= pos.count(Us, pt)) + continue; + Bitboard b = (pos.attacks_from(Us, pt, from) & ~pos.pieces()) | from; + if (Type == EVASIONS) + b &= target; + + while (b) + { + Square to = pop_lsb(&b); + if (!(attacks_bb(Us, pt, to, pos.pieces() ^ from) & pos.pieces(Them))) + *moveList++ = make(from, to, pt); + } + } + } } - // Standard and en-passant captures + // Standard and en passant captures if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { Bitboard b1 = shift(pawnsNotOn7) & enemies; @@@ -241,12 -154,10 +241,10 @@@ if (pos.ep_square() != SQ_NONE) { - assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6)); + assert(relative_rank(Them, rank_of(pos.ep_square()), pos.max_rank()) <= Rank(pos.double_step_rank_max() + 1)); - // An en passant capture can be an evasion only if the checking piece - // is the double pushed pawn and so is in the target. Otherwise this - // is a discovery check and we are forced to do otherwise. - if (Type == EVASIONS && !(target & (pos.ep_square() - Up))) + // An en passant capture cannot resolve a discovered check. + if (Type == EVASIONS && (target & (pos.ep_square() + Up))) return moveList; b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square()); @@@ -262,65 -173,27 +260,68 @@@ } - template - ExtMove* generate_moves(const Position& pos, ExtMove* moveList, PieceType pt, Bitboard target) { - template - ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard piecesToMove, Bitboard target) { ++ template ++ ExtMove* generate_moves(const Position& pos, ExtMove* moveList, PieceType Pt, Bitboard piecesToMove, Bitboard target) { ++ ++ assert(Pt != KING && Pt != PAWN); + - static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()"); ++ Color us = pos.side_to_move(); - assert(pt != KING && pt != PAWN); + Bitboard bb = piecesToMove & pos.pieces(Pt); - Bitboard bb = pos.pieces(Us, pt); + if (!bb) + return moveList; + + [[maybe_unused]] const Bitboard checkSquares = pos.check_squares(Pt); while (bb) { Square from = pop_lsb(&bb); - // Avoid generating discovered checks twice - if (Checks && (pos.blockers_for_king(~Us) & from)) - continue; - - Bitboard b1 = ( (pos.attacks_from(Us, pt, from) & pos.pieces()) - | (pos.moves_from(Us, pt, from) & ~pos.pieces())) & target; - PieceType prom_pt = pos.promoted_piece_type(pt); - Bitboard b2 = prom_pt && (!pos.promotion_limit(prom_pt) || pos.promotion_limit(prom_pt) > pos.count(Us, prom_pt)) ? b1 : Bitboard(0); - Bitboard b = attacks_bb(from, pos.pieces()) & target; - if constexpr (Checks) - b &= checkSquares; ++ Bitboard b1 = ( (pos.attacks_from(us, Pt, from) & pos.pieces()) ++ | (pos.moves_from(us, Pt, from) & ~pos.pieces())) & target; ++ PieceType promPt = pos.promoted_piece_type(Pt); ++ Bitboard b2 = promPt && (!pos.promotion_limit(promPt) || pos.promotion_limit(promPt) > pos.count(us, promPt)) ? b1 : Bitboard(0); + Bitboard b3 = pos.piece_demotion() && pos.is_promoted(from) ? b1 : Bitboard(0); - while (b) - *moveList++ = make_move(from, pop_lsb(&b)); + if (Checks) + { - b1 &= pos.check_squares(pt); ++ b1 &= checkSquares; + if (b2) - b2 &= pos.check_squares(pos.promoted_piece_type(pt)); ++ b2 &= pos.check_squares(pos.promoted_piece_type(Pt)); + if (b3) + b3 &= pos.check_squares(type_of(pos.unpromoted_piece_on(from))); + } + + // Restrict target squares considering promotion zone + if (b2 | b3) + { - Bitboard promotion_zone = zone_bb(Us, pos.promotion_rank(), pos.max_rank()); ++ Bitboard promotion_zone = zone_bb(us, pos.promotion_rank(), pos.max_rank()); + if (pos.mandatory_piece_promotion()) + b1 &= (promotion_zone & from ? Bitboard(0) : ~promotion_zone) | (pos.piece_promotion_on_capture() ? ~pos.pieces() : Bitboard(0)); + // Exclude quiet promotions/demotions + if (pos.piece_promotion_on_capture()) + { + b2 &= pos.pieces(); + b3 &= pos.pieces(); + } + // Consider promotions/demotions into promotion zone + if (!(promotion_zone & from)) + { + b2 &= promotion_zone; + b3 &= promotion_zone; + } + } + + while (b1) - moveList = make_move_and_gating(pos, moveList, Us, from, pop_lsb(&b1)); ++ moveList = make_move_and_gating(pos, moveList, us, from, pop_lsb(&b1)); + + // Shogi-style piece promotions + while (b2) + *moveList++ = make(from, pop_lsb(&b2)); + + // Piece demotions + while (b3) + *moveList++ = make(from, pop_lsb(&b3)); } return moveList; @@@ -358,31 -229,20 +365,29 @@@ case NON_EVASIONS: target = ~pos.pieces(Us); break; - default: - static_assert(true, "Unsupported type in generate_all()"); } + target &= pos.board_bb(); moveList = generate_pawn_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, piecesToMove, target); - moveList = generate_moves(pos, moveList, piecesToMove, target); - moveList = generate_moves< ROOK, Checks>(pos, moveList, piecesToMove, target); - moveList = generate_moves< QUEEN, Checks>(pos, moveList, piecesToMove, target); - - if (Type != QUIET_CHECKS && Type != EVASIONS) + for (PieceType pt : pos.piece_types()) + if (pt != PAWN && pt != KING) - moveList = generate_moves(pos, moveList, pt, target); ++ moveList = generate_moves(pos, moveList, pt, piecesToMove, target); + // generate drops + if (pos.piece_drops() && Type != CAPTURES && pos.count_in_hand(Us, ALL_PIECES)) + for (PieceType pt : pos.piece_types()) + moveList = generate_drops(pos, moveList, pt, target & ~pos.pieces(~Us)); + + if (Type != QUIET_CHECKS && Type != EVASIONS && pos.count(Us)) { Square ksq = pos.square(Us); - Bitboard b = attacks_bb(ksq) & target; + Bitboard b = ( (pos.attacks_from(Us, KING, ksq) & pos.pieces()) + | (pos.moves_from(Us, KING, ksq) & ~pos.pieces())) & target; while (b) - *moveList++ = make_move(ksq, pop_lsb(&b)); + moveList = make_move_and_gating(pos, moveList, Us, ksq, pop_lsb(&b)); + + // Passing move by king + if (pos.pass()) + *moveList++ = make(ksq, ksq); if ((Type != CAPTURES) && pos.can_castle(Us & ANY_CASTLING)) for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) diff --cc src/movepick.h index f488615,ea599cd..889d292 --- a/src/movepick.h +++ b/src/movepick.h @@@ -84,7 -84,7 +84,7 @@@ enum StatsType { NoCaptures, Captures } /// unsuccessful during the current search, and is used for reduction and move /// ordering decisions. It uses 2 tables (one for each color) indexed by /// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards - typedef Stats ButterflyHistory; -typedef Stats ButterflyHistory; ++typedef Stats ButterflyHistory; /// At higher depths LowPlyHistory records successful quiet moves near the root /// and quiet moves which are/were in the PV (ttPv). It is cleared with each new @@@ -105,16 -105,15 +105,16 @@@ typedef Stats ContinuationHistory; +typedef Stats ContinuationHistory; +int history_slot(Piece pc); - /// MovePicker class is used to pick one pseudo legal move at a time from the + /// MovePicker class is used to pick one pseudo-legal move at a time from the /// current position. The most important method is next_move(), which returns a - /// new pseudo legal move each time it is called, until there are no moves left, - /// when MOVE_NONE is returned. In order to improve the efficiency of the alpha - /// beta algorithm, MovePicker attempts to return the moves which are most likely - /// to get a cut-off first. + /// new pseudo-legal move each time it is called, until there are no moves left, + /// when MOVE_NONE is returned. In order to improve the efficiency of the + /// alpha-beta algorithm, MovePicker attempts to return the moves which are most + /// likely to get a cut-off first. class MovePicker { enum PickType { Next, Best }; diff --cc src/parser.cpp index d45c763,0000000..ddb29c1 mode 100644,000000..100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@@ -1,388 -1,0 +1,388 @@@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish - Copyright (C) 2018-2020 Fabian Fichter ++ Copyright (C) 2018-2021 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "parser.h" +#include "piece.h" +#include "types.h" + +namespace { + + template bool set(const std::string& value, T& target) + { + std::stringstream ss(value); + ss >> target; + return !ss.fail(); + } + + template <> bool set(const std::string& value, Rank& target) { + std::stringstream ss(value); + int i; + ss >> i; + target = Rank(i - 1); + return !ss.fail() && target >= RANK_1 && target <= RANK_MAX; + } + + template <> bool set(const std::string& value, File& target) { + std::stringstream ss(value); + if (isdigit(ss.peek())) + { + int i; + ss >> i; + target = File(i - 1); + } + else + { + char c; + ss >> c; + target = File(c - 'a'); + } + return !ss.fail() && target >= FILE_A && target <= FILE_MAX; + } + + template <> bool set(const std::string& value, std::string& target) { + target = value; + return true; + } + + template <> bool set(const std::string& value, bool& target) { + target = value == "true"; + return value == "true" || value == "false"; + } + + template <> bool set(const std::string& value, Value& target) { + target = value == "win" ? VALUE_MATE + : value == "loss" ? -VALUE_MATE + : value == "draw" ? VALUE_DRAW + : VALUE_NONE; + return value == "win" || value == "loss" || value == "draw" || value == "none"; + } + + template <> bool set(const std::string& value, MaterialCounting& target) { + target = value == "janggi" ? JANGGI_MATERIAL + : value == "unweighted" ? UNWEIGHTED_MATERIAL + : value == "whitedrawodds" ? WHITE_DRAW_ODDS + : value == "blackdrawodds" ? BLACK_DRAW_ODDS + : NO_MATERIAL_COUNTING; + return value == "janggi" || value == "unweighted" + || value == "whitedrawodds" || value == "blackdrawodds" || value == "none"; + } + + template <> bool set(const std::string& value, CountingRule& target) { + target = value == "makruk" ? MAKRUK_COUNTING + : value == "asean" ? ASEAN_COUNTING + : NO_COUNTING; + return value == "makruk" || value == "asean" || value == "none"; + } + + template <> bool set(const std::string& value, EnclosingRule& target) { + target = value == "reversi" ? REVERSI + : value == "ataxx" ? ATAXX + : NO_ENCLOSING; + return value == "reversi" || value == "ataxx" || value == "none"; + } + + template <> bool set(const std::string& value, Bitboard& target) { + char file; + int rank; + std::stringstream ss(value); + target = 0; + while (!ss.eof() && ss >> file && ss >> rank) + target |= file == '*' ? rank_bb(Rank(rank - 1)) : square_bb(make_square(File(tolower(file) - 'a'), Rank(rank - 1))); + return !ss.fail(); + } + +} // namespace + +template +template void VariantParser::parse_attribute(const std::string& key, T& target) { + const auto& it = config.find(key); + if (it != config.end()) + { + bool valid = set(it->second, target); + if (DoCheck && !valid) + { + std::string typeName = std::is_same() ? "int" + : std::is_same() ? "Rank" + : std::is_same() ? "File" + : std::is_same() ? "bool" + : std::is_same() ? "Value" + : std::is_same() ? "MaterialCounting" + : std::is_same() ? "CountingRule" + : std::is_same() ? "Bitboard" + : typeid(T).name(); + std::cerr << key << " - Invalid value " << it->second << " for type " << typeName << std::endl; + } + } +} + +template +void VariantParser::parse_attribute(const std::string& key, PieceType& target, std::string pieceToChar) { + const auto& it = config.find(key); + if (it != config.end()) + { + char token; + size_t idx; + std::stringstream ss(it->second); + if (ss >> token && (idx = pieceToChar.find(toupper(token))) != std::string::npos) + target = PieceType(idx); + else if (DoCheck) + std::cerr << key << " - Invalid piece type: " << token << std::endl; + } +} + +template +Variant* VariantParser::parse() { + Variant* v = new Variant(); + v->reset_pieces(); + v->promotionPieceTypes = {}; + return parse(v); +} + +template +Variant* VariantParser::parse(Variant* v) { + // piece types + for (const auto& pieceInfo : pieceMap) + { + // piece char + const auto& keyValue = config.find(pieceInfo.second->name); + if (keyValue != config.end() && !keyValue->second.empty()) + { + if (isalpha(keyValue->second.at(0))) + v->add_piece(pieceInfo.first, keyValue->second.at(0)); + else + { + if (DoCheck && keyValue->second.at(0) != '-') + std::cerr << pieceInfo.second->name << " - Invalid letter: " << keyValue->second.at(0) << std::endl; + v->remove_piece(pieceInfo.first); + } + } + // mobility region + std::string capitalizedPiece = pieceInfo.second->name; + capitalizedPiece[0] = toupper(capitalizedPiece[0]); + for (Color c : {WHITE, BLACK}) + { + std::string color = c == WHITE ? "White" : "Black"; + parse_attribute("mobilityRegion" + color + capitalizedPiece, v->mobilityRegion[c][pieceInfo.first]); + } + } + parse_attribute("variantTemplate", v->variantTemplate); + parse_attribute("pieceToCharTable", v->pieceToCharTable); + parse_attribute("pocketSize", v->pocketSize); + parse_attribute("maxRank", v->maxRank); + parse_attribute("maxFile", v->maxFile); + parse_attribute("chess960", v->chess960); + parse_attribute("twoBoards", v->twoBoards); + parse_attribute("startFen", v->startFen); + parse_attribute("promotionRank", v->promotionRank); + // promotion piece types + const auto& it_prom = config.find("promotionPieceTypes"); + if (it_prom != config.end()) + { + v->promotionPieceTypes = {}; + char token; + size_t idx = 0; + std::stringstream ss(it_prom->second); + while (ss >> token && ((idx = v->pieceToChar.find(toupper(token))) != std::string::npos)) + v->promotionPieceTypes.insert(PieceType(idx)); + if (DoCheck && idx == std::string::npos && token != '-') + std::cerr << "promotionPieceTypes - Invalid piece type: " << token << std::endl; + } + parse_attribute("sittuyinPromotion", v->sittuyinPromotion); + // promotion limit + const auto& it_prom_limit = config.find("promotionLimit"); + if (it_prom_limit != config.end()) + { + char token; + size_t idx = 0; + std::stringstream ss(it_prom_limit->second); + while (!ss.eof() && ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos + && ss >> token && ss >> v->promotionLimit[idx]) {} + if (DoCheck && idx == std::string::npos) + std::cerr << "promotionLimit - Invalid piece type: " << token << std::endl; + else if (DoCheck && !ss.eof()) + std::cerr << "promotionLimit - Invalid piece count for type: " << v->pieceToChar[idx] << std::endl; + } + // promoted piece types + const auto& it_prom_pt = config.find("promotedPieceType"); + if (it_prom_pt != config.end()) + { + char token; + size_t idx = 0, idx2 = 0; + std::stringstream ss(it_prom_pt->second); + while ( ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos && ss >> token + && ss >> token && (idx2 = (token == '-' ? 0 : v->pieceToChar.find(toupper(token)))) != std::string::npos) + v->promotedPieceType[idx] = PieceType(idx2); + if (DoCheck && (idx == std::string::npos || idx2 == std::string::npos)) + std::cerr << "promotedPieceType - Invalid piece type: " << token << std::endl; + } + parse_attribute("piecePromotionOnCapture", v->piecePromotionOnCapture); + parse_attribute("mandatoryPawnPromotion", v->mandatoryPawnPromotion); + parse_attribute("mandatoryPiecePromotion", v->mandatoryPiecePromotion); + parse_attribute("pieceDemotion", v->pieceDemotion); + parse_attribute("blastOnCapture", v->blastOnCapture); + parse_attribute("endgameEval", v->endgameEval); + parse_attribute("doubleStep", v->doubleStep); + parse_attribute("doubleStepRank", v->doubleStepRank); + parse_attribute("doubleStepRankMin", v->doubleStepRankMin); + parse_attribute("enPassantRegion", v->enPassantRegion); + parse_attribute("castling", v->castling); + parse_attribute("castlingDroppedPiece", v->castlingDroppedPiece); + parse_attribute("castlingKingsideFile", v->castlingKingsideFile); + parse_attribute("castlingQueensideFile", v->castlingQueensideFile); + parse_attribute("castlingRank", v->castlingRank); + parse_attribute("castlingRookPiece", v->castlingRookPiece, v->pieceToChar); + parse_attribute("kingType", v->kingType, v->pieceToChar); + parse_attribute("checking", v->checking); + parse_attribute("dropChecks", v->dropChecks); + parse_attribute("mustCapture", v->mustCapture); + parse_attribute("mustDrop", v->mustDrop); + parse_attribute("mustDropType", v->mustDropType, v->pieceToChar); + parse_attribute("pieceDrops", v->pieceDrops); + parse_attribute("dropLoop", v->dropLoop); + parse_attribute("capturesToHand", v->capturesToHand); + parse_attribute("firstRankPawnDrops", v->firstRankPawnDrops); + parse_attribute("promotionZonePawnDrops", v->promotionZonePawnDrops); + parse_attribute("dropOnTop", v->dropOnTop); + parse_attribute("enclosingDrop", v->enclosingDrop); + parse_attribute("enclosingDropStart", v->enclosingDropStart); + parse_attribute("whiteDropRegion", v->whiteDropRegion); + parse_attribute("blackDropRegion", v->blackDropRegion); + parse_attribute("sittuyinRookDrop", v->sittuyinRookDrop); + parse_attribute("dropOppositeColoredBishop", v->dropOppositeColoredBishop); + parse_attribute("dropPromoted", v->dropPromoted); + parse_attribute("shogiDoubledPawn", v->shogiDoubledPawn); + parse_attribute("immobilityIllegal", v->immobilityIllegal); + parse_attribute("gating", v->gating); + parse_attribute("arrowGating", v->arrowGating); + parse_attribute("seirawanGating", v->seirawanGating); + parse_attribute("cambodianMoves", v->cambodianMoves); + parse_attribute("diagonalLines", v->diagonalLines); + parse_attribute("pass", v->pass); + parse_attribute("passOnStalemate", v->passOnStalemate); + parse_attribute("makpongRule", v->makpongRule); + parse_attribute("flyingGeneral", v->flyingGeneral); + parse_attribute("soldierPromotionRank", v->soldierPromotionRank); + parse_attribute("flipEnclosedPieces", v->flipEnclosedPieces); + // game end + parse_attribute("nMoveRule", v->nMoveRule); + parse_attribute("nFoldRule", v->nFoldRule); + parse_attribute("nFoldValue", v->nFoldValue); + parse_attribute("nFoldValueAbsolute", v->nFoldValueAbsolute); + parse_attribute("perpetualCheckIllegal", v->perpetualCheckIllegal); + parse_attribute("moveRepetitionIllegal", v->moveRepetitionIllegal); + parse_attribute("stalemateValue", v->stalemateValue); + parse_attribute("stalematePieceCount", v->stalematePieceCount); + parse_attribute("checkmateValue", v->checkmateValue); + parse_attribute("shogiPawnDropMateIllegal", v->shogiPawnDropMateIllegal); + parse_attribute("shatarMateRule", v->shatarMateRule); + parse_attribute("bikjangRule", v->bikjangRule); + parse_attribute("extinctionValue", v->extinctionValue); + parse_attribute("extinctionClaim", v->extinctionClaim); + // extinction piece types + const auto& it_ext = config.find("extinctionPieceTypes"); + if (it_ext != config.end()) + { + v->extinctionPieceTypes = {}; + char token; + size_t idx = 0; + std::stringstream ss(it_ext->second); + while (ss >> token && (idx = token == '*' ? size_t(ALL_PIECES) : v->pieceToChar.find(toupper(token))) != std::string::npos) + v->extinctionPieceTypes.insert(PieceType(idx)); + if (DoCheck && idx == std::string::npos) + std::cerr << "extinctionPieceTypes - Invalid piece type: " << token << std::endl; + } + parse_attribute("extinctionPieceCount", v->extinctionPieceCount); + parse_attribute("extinctionOpponentPieceCount", v->extinctionOpponentPieceCount); + parse_attribute("flagPiece", v->flagPiece, v->pieceToChar); + parse_attribute("whiteFlag", v->whiteFlag); + parse_attribute("blackFlag", v->blackFlag); + parse_attribute("flagMove", v->flagMove); + parse_attribute("checkCounting", v->checkCounting); + parse_attribute("connectN", v->connectN); + parse_attribute("materialCounting", v->materialCounting); + parse_attribute("countingRule", v->countingRule); + // Report invalid options + if (DoCheck) + { + const std::set& parsedKeys = config.get_comsumed_keys(); + for (const auto& it : config) + if (parsedKeys.find(it.first) == parsedKeys.end()) + std::cerr << "Invalid option: " << it.first << std::endl; + } + // Check consistency + if (DoCheck) + { + // startFen + std::string fenBoard = v->startFen.substr(0, v->startFen.find(' ')); + std::stringstream ss(fenBoard); + char token; + ss >> std::noskipws; + + while (ss >> token) + { + if (token == '+') + { + ss >> token; + size_t idx = v->pieceToChar.find(toupper(token)); + if (idx == std::string::npos || !v->promotedPieceType[idx]) + std::cerr << "startFen - Invalid piece type: +" << token << std::endl; + } + else if (isalpha(token) && v->pieceToChar.find(toupper(token)) == std::string::npos) + std::cerr << "startFen - Invalid piece type: " << token << std::endl; + } + + // pieceToCharTable + if (v->pieceToCharTable != "-") + { + ss = std::stringstream(v->pieceToCharTable); + while (ss >> token) + if (isalpha(token) && v->pieceToChar.find(toupper(token)) == std::string::npos) + std::cerr << "pieceToCharTable - Invalid piece type: " << token << std::endl; + for (PieceType pt : v->pieceTypes) + { + char ptl = tolower(v->pieceToChar[pt]); + if (v->pieceToCharTable.find(ptl) == std::string::npos && fenBoard.find(ptl) != std::string::npos) + std::cerr << "pieceToCharTable - Missing piece type: " << ptl << std::endl; + char ptu = toupper(v->pieceToChar[pt]); + if (v->pieceToCharTable.find(ptu) == std::string::npos && fenBoard.find(ptu) != std::string::npos) + std::cerr << "pieceToCharTable - Missing piece type: " << ptu << std::endl; + } + } + + // Check for limitations + + // Options incompatible with royal kings + if (v->pieceTypes.find(KING) != v->pieceTypes.end()) + { + if (v->blastOnCapture) + std::cerr << "Can not use kings with blastOnCapture" << std::endl; + if (v->flipEnclosedPieces) + std::cerr << "Can not use kings with flipEnclosedPieces" << std::endl; + } + + } + return v; +} + +template Variant* VariantParser::parse(); +template Variant* VariantParser::parse(); +template Variant* VariantParser::parse(Variant* v); +template Variant* VariantParser::parse(Variant* v); diff --cc src/parser.h index a5eca49,0000000..247027a mode 100644,000000..100644 --- a/src/parser.h +++ b/src/parser.h @@@ -1,55 -1,0 +1,55 @@@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish - Copyright (C) 2018-2020 Fabian Fichter ++ Copyright (C) 2018-2021 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PARSER_H_INCLUDED +#define PARSER_H_INCLUDED + +#include + +#include "variant.h" + +class Config : public std::map { +public: + Config::iterator find (const std::string& s) { + constexpr bool PrintOptions = false; // print config options? + if (PrintOptions) + std::cout << s << std::endl; + consumedKeys.insert(s); + return std::map::find(s); + } + const std::set& get_comsumed_keys() { + return consumedKeys; + } +private: + std::set consumedKeys = {}; +}; + +template +class VariantParser { +public: + VariantParser(const Config& c) : config (c) {}; + Variant* parse(); + Variant* parse(Variant* v); + +private: + Config config; + template void parse_attribute(const std::string& key, T& target); + void parse_attribute(const std::string& key, PieceType& target, std::string pieceToChar); +}; + +#endif // #ifndef PARSER_H_INCLUDED diff --cc src/partner.cpp index 21bed35,0000000..306b6e2 mode 100644,000000..100644 --- a/src/partner.cpp +++ b/src/partner.cpp @@@ -1,154 -1,0 +1,154 @@@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish - Copyright (C) 2018-2020 Fabian Fichter ++ Copyright (C) 2018-2021 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "partner.h" +#include "thread.h" +#include "uci.h" + +PartnerHandler Partner; // Global object + +void PartnerHandler::reset() { + fast = sitRequested = partnerDead = weDead = weWin = false; + time = opptime = 0; +} + +template +void PartnerHandler::ptell(const std::string& message) { + if (p == ALL_PARTNERS || (p == FAIRY && isFairy) || (p == HUMAN && !isFairy)) + sync_cout << "tellics ptell " << message << sync_endl; +} + +void PartnerHandler::parse_partner(std::istringstream& is) { + std::string token; + if (is >> token) + // handshake to identify Fairy-Stockfish + ptell("partner Fairy-Stockfish is an engine. Ask it 'help' for supported commands."); + else + isFairy = false; +} + +void PartnerHandler::parse_ptell(std::istringstream& is, const Position& pos) { + std::string token; + is >> token; + if (token == "partner") + { + // handshake to identify Fairy-Stockfish + if (is >> token && token == "Fairy-Stockfish") + isFairy = true; + } + else if (token == "help") + { + if (!(is >> token)) + { + ptell("I listen to the commands help, sit, go, move, fast, slow, dead, x, time, and otim."); + ptell("Tell 'help sit', etc. for details."); + } + else if (token == "sit") + ptell("After receiving 'sit', I stop moving. Also see 'go'."); + else if (token == "go") + ptell("After receiving 'go', I will no longer sit."); + else if (token == "move") + { + ptell("After receiving 'move', I will move immediately." ); + ptell("If you specify a valid move, e.g., 'move e2e4', I will play it."); + } + else if (token == "fast") + ptell("After receiving 'go', I will play fast."); + else if (token == "slow") + ptell("After receiving 'slow', I will play at normal speed."); + else if (token == "dead") + ptell("After receiving 'dead', I assume you are dead and I play fast."); + else if (token == "x") + ptell("After receiving 'x', I assume I can play normally again."); + else if (token == "time") + { + ptell("'time' together with your time in centiseconds allows me to consider your time."); + ptell("E.g., 'time 1000' for 10 seconds."); + } + else if (token == "otim") + ptell("'otim' together with your opponent's time in centiseconds allows me to consider his time."); + } + else if (!pos.two_boards()) + return; + else if (token == "sit") + { + // Avoid deadlocking sit + if (!isFairy || !weWin) + sitRequested = true; + ptell("I sit, tell me 'go' to continue"); + } + else if (token == "go") + { + sitRequested = false; + Threads.stop = true; + } + else if (token == "move") + { + if (is >> token) + { + // if the given move is valid and we can still abort the search, play it + Move move = UCI::to_move(pos, token); + if (move && !Threads.abort.exchange(true)) + moveRequested = move; + else + ptell("sorry, not possible"); + } + else + // Move immediately on request + Threads.stop = true; + } + else if (token == "fast") + { + fast = true; + ptell("I play fast, tell me 'slow' to play normally again"); + } + else if (token == "slow") + { + fast = false; + ptell("I play at normal speed again."); + } + else if (token == "dead") + { + partnerDead = true; + ptell("I play fast, tell me 'x' if you are no longer dead."); + } + else if (token == "x") + { + partnerDead = false; + sitRequested = false; + ptell("I play normally again"); + } + else if (token == "time") + { + int value; + time = (is >> value) ? value : 0; + } + else if (token == "otim") + { + int value; + opptime = (is >> value) ? value : 0; + } +} + +template void PartnerHandler::ptell(const std::string&); +template void PartnerHandler::ptell(const std::string&); +template void PartnerHandler::ptell(const std::string&); diff --cc src/partner.h index 25c4f49,0000000..01b68e6 mode 100644,000000..100644 --- a/src/partner.h +++ b/src/partner.h @@@ -1,51 -1,0 +1,51 @@@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish - Copyright (C) 2018-2020 Fabian Fichter ++ Copyright (C) 2018-2021 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PARTNER_H_INCLUDED +#define PARTNER_H_INCLUDED + +#include +#include + +#include "position.h" + +/// PartnerHandler manages the communication with the partner +/// in games played on two boards, such as bughouse. + +enum PartnerType { + HUMAN, + FAIRY, + ALL_PARTNERS +}; + +struct PartnerHandler { + void reset(); + template + void ptell(const std::string& message); + void parse_partner(std::istringstream& is); + void parse_ptell(std::istringstream& is, const Position& pos); + + std::atomic isFairy; + std::atomic fast, sitRequested, partnerDead, weDead, weWin; + std::atomic time, opptime; + Move moveRequested; +}; + +extern PartnerHandler Partner; + +#endif // #ifndef PARTNER_H_INCLUDED diff --cc src/pawns.cpp index 76f0907,5d6770e..56b1f0e --- a/src/pawns.cpp +++ b/src/pawns.cpp @@@ -30,14 -30,15 +30,15 @@@ namespace #define S(mg, eg) make_score(mg, eg) // Pawn penalties - constexpr Score Backward = S( 8, 25); - constexpr Score Doubled = S(10, 55); - constexpr Score Isolated = S( 3, 15); - constexpr Score WeakLever = S( 3, 55); - constexpr Score WeakUnopposed = S(13, 25); + constexpr Score Backward = S( 6, 23); + constexpr Score Doubled = S(13, 53); + constexpr Score DoubledEarly = S(20, 10); + constexpr Score Isolated = S( 2, 15); + constexpr Score WeakLever = S( 5, 57); + constexpr Score WeakUnopposed = S(16, 22); // Bonus for blocked pawns at 5th or 6th rank - constexpr Score BlockedPawn[2] = { S(-15, -3), S(-6, 3) }; + constexpr Score BlockedPawn[RANK_NB - 5] = { S(-15, -3), S(-6, 3) }; constexpr Score BlockedStorm[RANK_NB] = { S(0, 0), S(0, 0), S(75, 78), S(-8, 16), S(-6, 10), S(-6, 6), S(0, 2) @@@ -69,14 -70,9 +70,14 @@@ // KingOnFile[semi-open Us][semi-open Them] contains bonuses/penalties // for king when the king is on a semi-open or open file. - constexpr Score KingOnFile[2][2] = {{ S(-19,12), S(-6, 7) }, - { S( 0, 2), S( 6,-5) }}; + constexpr Score KingOnFile[2][2] = {{ S(-21,10), S(-7, 1) }, + { S( 0,-3), S( 9,-4) }}; + + // Variant bonuses + constexpr int HordeConnected[2][RANK_NB] = {{ 5, 10, 20, 55, 55, 100, 80 }, + { -10, 5, -10, 5, 25, 40, 30 }}; + #undef S #undef V @@@ -119,20 -116,26 +121,27 @@@ // Flag the pawn opposed = theirPawns & forward_file_bb(Us, s); - blocked = theirPawns & (s + Up); + blocked = is_ok(s + Up) ? theirPawns & (s + Up) : Bitboard(0); stoppers = theirPawns & passed_pawn_span(Us, s); lever = theirPawns & pawn_attacks_bb(Us, s); - leverPush = theirPawns & pawn_attacks_bb(Us, s + Up); - doubled = ourPawns & (s - Up); + leverPush = relative_rank(Them, s, pos.max_rank()) > RANK_1 ? theirPawns & pawn_attacks_bb(Us, s + Up) : Bitboard(0); + doubled = r > RANK_1 ? ourPawns & (s - Up) : Bitboard(0); neighbours = ourPawns & adjacent_files_bb(s); phalanx = neighbours & rank_bb(s); - support = neighbours & rank_bb(s - Up); + support = r > RANK_1 ? neighbours & rank_bb(s - Up) : Bitboard(0); + if (doubled) + { + // Additional doubled penalty if none of their pawns is fixed + if (!(ourPawns & shift(theirPawns | pawn_attacks_bb(theirPawns)))) + score -= DoubledEarly; + } + // A pawn is backward when it is behind all pawns of the same color on // the adjacent files and cannot safely advance. - backward = !(neighbours & forward_ranks_bb(Them, s + Up)) - && (leverPush | blocked); + backward = is_ok(s + Up) + && !(neighbours & forward_ranks_bb(Them, s + Up)) + && (stoppers & blocked); // Compute additional span if pawn is not backward nor blocked if (!backward && !blocked) diff --cc src/piece.cpp index 515e2ae,0000000..48bd240 mode 100644,000000..100644 --- a/src/piece.cpp +++ b/src/piece.cpp @@@ -1,435 -1,0 +1,435 @@@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish - Copyright (C) 2018-2020 Fabian Fichter ++ Copyright (C) 2018-2021 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "types.h" +#include "piece.h" + +PieceMap pieceMap; // Global object + +void PieceInfo::merge(const PieceInfo* pi) { + stepsQuiet.insert(stepsQuiet.end(), pi->stepsQuiet.begin(), pi->stepsQuiet.end()); + stepsCapture.insert(stepsCapture.end(), pi->stepsCapture.begin(), pi->stepsCapture.end()); + sliderQuiet.insert(sliderQuiet.end(), pi->sliderQuiet.begin(), pi->sliderQuiet.end()); + sliderCapture.insert(sliderCapture.end(), pi->sliderCapture.begin(), pi->sliderCapture.end()); +} + +namespace { + PieceInfo* pawn_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "pawn"; + p->betza = "fmWfceF"; + p->stepsQuiet = {NORTH}; + p->stepsCapture = {NORTH_WEST, NORTH_EAST}; + return p; + } + PieceInfo* knight_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "knight"; + p->betza = "N"; + p->stepsQuiet = {2 * SOUTH + WEST, 2 * SOUTH + EAST, SOUTH + 2 * WEST, SOUTH + 2 * EAST, + NORTH + 2 * WEST, NORTH + 2 * EAST, 2 * NORTH + WEST, 2 * NORTH + EAST }; + p->stepsCapture = {2 * SOUTH + WEST, 2 * SOUTH + EAST, SOUTH + 2 * WEST, SOUTH + 2 * EAST, + NORTH + 2 * WEST, NORTH + 2 * EAST, 2 * NORTH + WEST, 2 * NORTH + EAST }; + return p; + } + PieceInfo* bishop_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "bishop"; + p->betza = "B"; + p->sliderQuiet = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; + p->sliderCapture = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; + return p; + } + PieceInfo* rook_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "rook"; + p->betza = "R"; + p->sliderQuiet = {NORTH, EAST, SOUTH, WEST}; + p->sliderCapture = {NORTH, EAST, SOUTH, WEST}; + return p; + } + PieceInfo* queen_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "queen"; + p->betza = "RB"; + p->sliderQuiet = {NORTH, EAST, SOUTH, WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; + p->sliderCapture = {NORTH, EAST, SOUTH, WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; + return p; + } + PieceInfo* king_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "king"; + p->betza = "K"; + p->stepsQuiet = {SOUTH_WEST, SOUTH, SOUTH_EAST, WEST, EAST, NORTH_WEST, NORTH, NORTH_EAST}; + p->stepsCapture = {SOUTH_WEST, SOUTH, SOUTH_EAST, WEST, EAST, NORTH_WEST, NORTH, NORTH_EAST}; + return p; + } + PieceInfo* commoner_piece() { + PieceInfo* p = king_piece(); + p->name = "commoner"; + return p; + } + PieceInfo* fers_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "fers"; + p->betza = "F"; + p->stepsQuiet = {SOUTH_WEST, SOUTH_EAST, NORTH_WEST, NORTH_EAST}; + p->stepsCapture = {SOUTH_WEST, SOUTH_EAST, NORTH_WEST, NORTH_EAST}; + return p; + } + PieceInfo* wazir_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "wazir"; + p->betza = "W"; + p->stepsQuiet = {SOUTH, WEST, EAST, NORTH}; + p->stepsCapture = {SOUTH, WEST, EAST, NORTH}; + return p; + } + PieceInfo* alfil_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "alfil"; + p->betza = "A"; + p->stepsQuiet = {2 * SOUTH_WEST, 2 * SOUTH_EAST, 2 * NORTH_WEST, 2 * NORTH_EAST}; + p->stepsCapture = {2 * SOUTH_WEST, 2 * SOUTH_EAST, 2 * NORTH_WEST, 2 * NORTH_EAST}; + return p; + } + PieceInfo* fers_alfil_piece() { + PieceInfo* p = fers_piece(); + p->name = "fersAlfil"; + p->betza = "FA"; + PieceInfo* p2 = alfil_piece(); + p->merge(p2); + delete p2; + return p; + } + PieceInfo* silver_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "silver"; + p->betza = "FfW"; + p->stepsQuiet = {SOUTH_WEST, SOUTH_EAST, NORTH_WEST, NORTH, NORTH_EAST}; + p->stepsCapture = {SOUTH_WEST, SOUTH_EAST, NORTH_WEST, NORTH, NORTH_EAST}; + return p; + } + PieceInfo* aiwok_piece() { + PieceInfo* p = rook_piece(); + p->name = "aiwok"; + p->betza = "RNF"; + PieceInfo* p2 = knight_piece(); + PieceInfo* p3 = fers_piece(); + p->merge(p2); + p->merge(p3); + delete p2; + delete p3; + return p; + } + PieceInfo* bers_piece() { + PieceInfo* p = rook_piece(); + p->name = "bers"; + p->betza = "RF"; + PieceInfo* p2 = fers_piece(); + p->merge(p2); + delete p2; + return p; + } + PieceInfo* archbishop_piece() { + PieceInfo* p = bishop_piece(); + p->name = "archbishop"; + p->betza = "BN"; + PieceInfo* p2 = knight_piece(); + p->merge(p2); + delete p2; + return p; + } + PieceInfo* chancellor_piece() { + PieceInfo* p = rook_piece(); + p->name = "chancellor"; + p->betza = "RN"; + PieceInfo* p2 = knight_piece(); + p->merge(p2); + delete p2; + return p; + } + PieceInfo* amazon_piece() { + PieceInfo* p = queen_piece(); + p->name = "amazon"; + p->betza = "RBN"; + PieceInfo* p2 = knight_piece(); + p->merge(p2); + delete p2; + return p; + } + PieceInfo* knibis_piece() { + PieceInfo* p = bishop_piece(); + p->name = "knibis"; + p->betza = "mNcB"; + p->sliderQuiet.clear(); + PieceInfo* p2 = knight_piece(); + p2->stepsCapture.clear(); + p->merge(p2); + delete p2; + return p; + } + PieceInfo* biskni_piece() { + PieceInfo* p = bishop_piece(); + p->name = "biskni"; + p->betza = "mBcN"; + p->sliderCapture.clear(); + PieceInfo* p2 = knight_piece(); + p2->stepsQuiet.clear(); + p->merge(p2); + delete p2; + return p; + } + PieceInfo* kniroo_piece() { + PieceInfo* p = rook_piece(); + p->name = "kniroo"; + p->betza = "mNcR"; + p->sliderQuiet.clear(); + PieceInfo* p2 = knight_piece(); + p2->stepsCapture.clear(); + p->merge(p2); + delete p2; + return p; + } + PieceInfo* rookni_piece() { + PieceInfo* p = rook_piece(); + p->name = "rookni"; + p->betza = "mRcN"; + p->sliderCapture.clear(); + PieceInfo* p2 = knight_piece(); + p2->stepsQuiet.clear(); + p->merge(p2); + delete p2; + return p; + } + PieceInfo* shogi_pawn_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "shogiPawn"; + p->betza = "fW"; + p->stepsQuiet = {NORTH}; + p->stepsCapture = {NORTH}; + return p; + } + PieceInfo* lance_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "lance"; + p->betza = "fR"; + p->sliderQuiet = {NORTH}; + p->sliderCapture = {NORTH}; + return p; + } + PieceInfo* shogi_knight_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "shogiKnight"; + p->betza = "fN"; + p->stepsQuiet = {2 * NORTH + WEST, 2 * NORTH + EAST}; + p->stepsCapture = {2 * NORTH + WEST, 2 * NORTH + EAST}; + return p; + } + PieceInfo* euroshogi_knight_piece() { + PieceInfo* p = shogi_knight_piece(); + p->name = "euroshogiKnight"; + p->betza = "fNsW"; + p->stepsQuiet.push_back(WEST); + p->stepsQuiet.push_back(EAST); + p->stepsCapture.push_back(WEST); + p->stepsCapture.push_back(EAST); + return p; + } + PieceInfo* gold_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "gold"; + p->betza = "WfF"; + p->stepsQuiet = {SOUTH, WEST, EAST, NORTH_WEST, NORTH, NORTH_EAST}; + p->stepsCapture = {SOUTH, WEST, EAST, NORTH_WEST, NORTH, NORTH_EAST}; + return p; + } + PieceInfo* dragon_horse_piece() { + PieceInfo* p = bishop_piece(); + p->name = "dragonHorse"; + p->betza = "BW"; + PieceInfo* p2 = wazir_piece(); + p->merge(p2); + delete p2; + return p; + } + PieceInfo* clobber_piece() { + PieceInfo* p = wazir_piece(); + p->name = "clobber"; + p->betza = "cW"; + p->stepsQuiet.clear(); + return p; + } + PieceInfo* breakthrough_piece() { + PieceInfo* p = pawn_piece(); + p->name = "breakthrough"; + p->betza = "fWfFcF"; + p->stepsQuiet.push_back(NORTH_WEST); + p->stepsQuiet.push_back(NORTH_EAST); + return p; + } + PieceInfo* immobile_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "immobile"; + return p; + } + PieceInfo* ataxx_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "ataxx"; + p->betza = "mDNA"; + p->stepsQuiet = {2 * NORTH_WEST, 2 * NORTH + WEST, 2 * NORTH, 2 * NORTH + EAST, 2 * NORTH_EAST, + NORTH + 2 * WEST, NORTH + 2 * EAST, 2 * WEST, 2 * EAST, SOUTH + 2 * WEST, SOUTH + 2 * EAST, + 2 * SOUTH_WEST, 2 * SOUTH + WEST, 2 * SOUTH, 2 * SOUTH + EAST, 2 * SOUTH_EAST}; + return p; + } + PieceInfo* quiet_queen_piece() { + PieceInfo* p = queen_piece(); + p->name = "quietQueen"; + p->betza = "mQ"; + p->sliderCapture.clear(); + return p; + } + PieceInfo* cannon_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "cannon"; + p->betza = "mRcpR"; + p->sliderQuiet = {NORTH, EAST, SOUTH, WEST}; + p->hopperCapture = {NORTH, EAST, SOUTH, WEST}; + return p; + } + PieceInfo* janggi_cannon_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "janggiCannon"; + p->betza = "pR"; + p->hopperQuiet = {NORTH, EAST, SOUTH, WEST}; + p->hopperCapture = {NORTH, EAST, SOUTH, WEST}; + return p; + } + PieceInfo* soldier_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "soldier"; + p->betza = "fsW"; + p->stepsQuiet = {NORTH, WEST, EAST}; + p->stepsCapture = {NORTH, WEST, EAST}; + return p; + } + PieceInfo* horse_piece() { + PieceInfo* p = knight_piece(); + p->name = "horse"; + p->betza = "nN"; + p->lameLeaper = true; + return p; + } + PieceInfo* elephant_piece() { + PieceInfo* p = alfil_piece(); + p->name = "elephant"; + p->betza = "nA"; + p->lameLeaper = true; + return p; + } + PieceInfo* janggi_elephant_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "janggiElephant"; + p->betza = "mafsmafW"; + p->stepsQuiet = {SOUTH + 2 * SOUTH_WEST, SOUTH + 2 * SOUTH_EAST, + WEST + 2 * SOUTH_WEST, EAST + 2 * SOUTH_EAST, + WEST + 2 * NORTH_WEST, EAST + 2 * NORTH_EAST, + NORTH + 2 * NORTH_WEST, NORTH + 2 * NORTH_EAST}; + p->stepsCapture = {SOUTH + 2 * SOUTH_WEST, SOUTH + 2 * SOUTH_EAST, + WEST + 2 * SOUTH_WEST, EAST + 2 * SOUTH_EAST, + WEST + 2 * NORTH_WEST, EAST + 2 * NORTH_EAST, + NORTH + 2 * NORTH_WEST, NORTH + 2 * NORTH_EAST}; + p->lameLeaper = true; + return p; + } + PieceInfo* banner_piece() { + PieceInfo* p = rook_piece(); + p->name = "banner"; + p->betza = "RcpRnN"; + PieceInfo* p2 = horse_piece(); + p->merge(p2); + delete p2; + p->hopperCapture = {NORTH, EAST, SOUTH, WEST}; + p->lameLeaper = true; + return p; + } + PieceInfo* centaur_piece() { + PieceInfo* p = commoner_piece(); + p->name = "centaur"; + p->betza = "KN"; + PieceInfo* p2 = knight_piece(); + p->merge(p2); + delete p2; + return p; + } +} + +void PieceMap::init() { + add(PAWN, pawn_piece()); + add(KNIGHT, knight_piece()); + add(BISHOP, bishop_piece()); + add(ROOK, rook_piece()); + add(QUEEN, queen_piece()); + add(FERS, fers_piece()); + add(ALFIL, alfil_piece()); + add(FERS_ALFIL, fers_alfil_piece()); + add(SILVER, silver_piece()); + add(AIWOK, aiwok_piece()); + add(BERS, bers_piece()); + add(ARCHBISHOP, archbishop_piece()); + add(CHANCELLOR, chancellor_piece()); + add(AMAZON, amazon_piece()); + add(KNIBIS, knibis_piece()); + add(BISKNI, biskni_piece()); + add(KNIROO, kniroo_piece()); + add(ROOKNI, rookni_piece()); + add(SHOGI_PAWN, shogi_pawn_piece()); + add(LANCE, lance_piece()); + add(SHOGI_KNIGHT, shogi_knight_piece()); + add(EUROSHOGI_KNIGHT, euroshogi_knight_piece()); + add(GOLD, gold_piece()); + add(DRAGON_HORSE, dragon_horse_piece()); + add(CLOBBER_PIECE, clobber_piece()); + add(BREAKTHROUGH_PIECE, breakthrough_piece()); + add(IMMOBILE_PIECE, immobile_piece()); + add(ATAXX_PIECE, ataxx_piece()); + add(QUIET_QUEEN, quiet_queen_piece()); + add(CANNON, cannon_piece()); + add(JANGGI_CANNON, janggi_cannon_piece()); + add(SOLDIER, soldier_piece()); + add(HORSE, horse_piece()); + add(ELEPHANT, elephant_piece()); + add(JANGGI_ELEPHANT, janggi_elephant_piece()); + add(BANNER, banner_piece()); + add(WAZIR, wazir_piece()); + add(COMMONER, commoner_piece()); + add(CENTAUR, centaur_piece()); + add(KING, king_piece()); +} + +void PieceMap::add(PieceType pt, const PieceInfo* p) { + insert(std::pair(pt, p)); +} + +void PieceMap::clear_all() { + for (auto const& element : *this) + delete element.second; + clear(); +} diff --cc src/piece.h index c93e1d5,0000000..61670bc mode 100644,000000..100644 --- a/src/piece.h +++ b/src/piece.h @@@ -1,53 -1,0 +1,53 @@@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish - Copyright (C) 2018-2020 Fabian Fichter ++ Copyright (C) 2018-2021 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PIECE_H_INCLUDED +#define PIECE_H_INCLUDED + +#include +#include +#include + +#include "types.h" + + +/// PieceInfo struct stores information about the piece movements. + +struct PieceInfo { + std::string name = ""; + std::string betza = ""; + std::vector stepsQuiet = {}; + std::vector stepsCapture = {}; + std::vector sliderQuiet = {}; + std::vector sliderCapture = {}; + std::vector hopperQuiet = {}; + std::vector hopperCapture = {}; + bool lameLeaper = false; + + void merge(const PieceInfo* pi); +}; + +struct PieceMap : public std::map { + void init(); + void add(PieceType pt, const PieceInfo* v); + void clear_all(); +}; + +extern PieceMap pieceMap; + +#endif // #ifndef PIECE_H_INCLUDED diff --cc src/position.cpp index 43a9f71,6a5d09e..fbb09f6 --- a/src/position.cpp +++ b/src/position.cpp @@@ -929,9 -505,9 +929,9 @@@ bool Position::legal(Move m) const // En passant captures are a tricky special case. Because they are rather // uncommon, we do it simply by testing whether the king is attacked after // the move is made. - if (type_of(m) == ENPASSANT) + if (type_of(m) == EN_PASSANT) { - Square ksq = square(us); + Square ksq = count(us) ? square(us) : SQ_NONE; Square capsq = to - pawn_push(us); Bitboard occupied = (pieces() ^ from ^ capsq) | to; @@@ -1252,16 -715,8 +1252,16 @@@ void Position::do_move(Move m, StateInf Color them = ~us; Square from = from_sq(m); Square to = to_sq(m); - Piece pc = piece_on(from); + Piece pc = moved_piece(m); - Piece captured = type_of(m) == ENPASSANT ? make_piece(them, PAWN) : piece_on(to); + Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); + if (to == from) + { + assert((type_of(m) == PROMOTION && sittuyin_promotion()) || (is_pass(m) && pass())); + captured = NO_PIECE; + } + st->capturedpromoted = is_promoted(to); + st->unpromotedCapturedPiece = captured ? unpromoted_piece_on(to) : NO_PIECE; + st->pass = is_pass(m); assert(color_of(pc) == us); assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); @@@ -1317,20 -767,10 +1317,21 @@@ } // Update board and piece lists + bool capturedPromoted = is_promoted(capsq); + Piece unpromotedCaptured = unpromoted_piece_on(capsq); remove_piece(capsq); - if (type_of(m) == ENPASSANT) + + if (type_of(m) == EN_PASSANT) board[capsq] = NO_PIECE; + if (captures_to_hand()) + { + Piece pieceToHand = !capturedPromoted || drop_loop() ? ~captured + : unpromotedCaptured ? ~unpromotedCaptured + : make_piece(~color_of(captured), PAWN); + add_to_hand(pieceToHand); + k ^= Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)] - 1] + ^ Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]]; + } // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[captured][capsq]; @@@ -1456,10 -815,8 +1457,10 @@@ // If the moving piece is a pawn do some special extra work if (type_of(pc) == PAWN) { - // Set en-passant square if the moved pawn can be captured + // Set en passant square if the moved pawn can be captured - if ( (int(to) ^ int(from)) == 16 + if ( type_of(m) != DROP + && std::abs(int(to) - int(from)) == 2 * NORTH + && (var->enPassantRegion & (to - pawn_push(us))) && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) { st->epSquare = to - pawn_push(us); @@@ -1990,8 -1067,8 +1991,8 @@@ bool Position::see_ge(Move m, Value thr assert(is_ok(m)); - // Only deal with normal moves, assume others pass a simple see + // Only deal with normal moves, assume others pass a simple SEE - if (type_of(m) != NORMAL) + if (type_of(m) != NORMAL && type_of(m) != DROP && type_of(m) != PIECE_PROMOTION) return VALUE_ZERO >= threshold; Square from = from_sq(m), to = to_sq(m); diff --cc src/position.h index 0cc9915,3624e29..8719ef7 --- a/src/position.h +++ b/src/position.h @@@ -27,9 -26,8 +27,10 @@@ #include "bitboard.h" #include "evaluate.h" + #include "psqt.h" #include "types.h" +#include "variant.h" +#include "movegen.h" #include "nnue/nnue_accumulator.h" @@@ -319,513 -198,11 +320,509 @@@ private Score psq; Thread* thisThread; StateInfo* st; + + // variant-specific + const Variant* var; + bool tsumeMode; bool chess960; + int pieceCountInHand[COLOR_NB][PIECE_TYPE_NB]; + Bitboard promotedPieces; + void add_to_hand(Piece pc); + void remove_from_hand(Piece pc); + void drop_piece(Piece pc_hand, Piece pc_drop, Square s); + void undrop_piece(Piece pc_hand, Square s); }; - namespace PSQT { - extern Score psq[PIECE_NB][SQUARE_NB + 1]; - } - extern std::ostream& operator<<(std::ostream& os, const Position& pos); +inline const Variant* Position::variant() const { + assert(var != nullptr); + return var; +} + +inline Rank Position::max_rank() const { + assert(var != nullptr); + return var->maxRank; +} + +inline File Position::max_file() const { + assert(var != nullptr); + return var->maxFile; +} + +inline bool Position::two_boards() const { + assert(var != nullptr); + return var->twoBoards; +} + +inline Bitboard Position::board_bb() const { + assert(var != nullptr); + return board_size_bb(var->maxFile, var->maxRank); +} + +inline Bitboard Position::board_bb(Color c, PieceType pt) const { + assert(var != nullptr); + return var->mobilityRegion[c][pt] ? var->mobilityRegion[c][pt] & board_bb() : board_bb(); +} + +inline const std::set& Position::piece_types() const { + assert(var != nullptr); + return var->pieceTypes; +} + +inline const std::string& Position::piece_to_char() const { + assert(var != nullptr); + return var->pieceToChar; +} + +inline const std::string& Position::piece_to_char_synonyms() const { + assert(var != nullptr); + return var->pieceToCharSynonyms; +} + +inline Rank Position::promotion_rank() const { + assert(var != nullptr); + return var->promotionRank; +} + +inline const std::set >& Position::promotion_piece_types() const { + assert(var != nullptr); + return var->promotionPieceTypes; +} + +inline bool Position::sittuyin_promotion() const { + assert(var != nullptr); + return var->sittuyinPromotion; +} + +inline int Position::promotion_limit(PieceType pt) const { + assert(var != nullptr); + return var->promotionLimit[pt]; +} + +inline PieceType Position::promoted_piece_type(PieceType pt) const { + assert(var != nullptr); + return var->promotedPieceType[pt]; +} + +inline bool Position::piece_promotion_on_capture() const { + assert(var != nullptr); + return var->piecePromotionOnCapture; +} + +inline bool Position::mandatory_pawn_promotion() const { + assert(var != nullptr); + return var->mandatoryPawnPromotion; +} + +inline bool Position::mandatory_piece_promotion() const { + assert(var != nullptr); + return var->mandatoryPiecePromotion; +} + +inline bool Position::piece_demotion() const { + assert(var != nullptr); + return var->pieceDemotion; +} + +inline bool Position::blast_on_capture() const { + assert(var != nullptr); + return var->blastOnCapture; +} + +inline bool Position::endgame_eval() const { + assert(var != nullptr); + return var->endgameEval && !count_in_hand(WHITE, ALL_PIECES) && !count_in_hand(BLACK, ALL_PIECES); +} + +inline bool Position::double_step_enabled() const { + assert(var != nullptr); + return var->doubleStep; +} + +inline Rank Position::double_step_rank_max() const { + assert(var != nullptr); + return var->doubleStepRank; +} + +inline Rank Position::double_step_rank_min() const { + assert(var != nullptr); + return var->doubleStepRankMin; +} + +inline bool Position::castling_enabled() const { + assert(var != nullptr); + return var->castling; +} + +inline bool Position::castling_dropped_piece() const { + assert(var != nullptr); + return var->castlingDroppedPiece; +} + +inline File Position::castling_kingside_file() const { + assert(var != nullptr); + return var->castlingKingsideFile; +} + +inline File Position::castling_queenside_file() const { + assert(var != nullptr); + return var->castlingQueensideFile; +} + +inline Rank Position::castling_rank(Color c) const { + assert(var != nullptr); + return relative_rank(c, var->castlingRank, max_rank()); +} + +inline PieceType Position::castling_rook_piece() const { + assert(var != nullptr); + return var->castlingRookPiece; +} + +inline PieceType Position::king_type() const { + assert(var != nullptr); + return var->kingType; +} + +inline bool Position::checking_permitted() const { + assert(var != nullptr); + return var->checking; +} + +inline bool Position::drop_checks() const { + assert(var != nullptr); + return var->dropChecks; +} + +inline bool Position::must_capture() const { + assert(var != nullptr); + return var->mustCapture; +} + +inline bool Position::has_capture() const { + return st->legalCapture == VALUE_TRUE || (st->legalCapture == NO_VALUE && MoveList(*this).size()); +} + +inline bool Position::must_drop() const { + assert(var != nullptr); + return var->mustDrop; +} + +inline bool Position::piece_drops() const { + assert(var != nullptr); + return var->pieceDrops; +} + +inline bool Position::drop_loop() const { + assert(var != nullptr); + return var->dropLoop; +} + +inline bool Position::captures_to_hand() const { + assert(var != nullptr); + return var->capturesToHand; +} + +inline bool Position::first_rank_pawn_drops() const { + assert(var != nullptr); + return var->firstRankPawnDrops; +} + +inline bool Position::drop_on_top() const { + assert(var != nullptr); + return var->dropOnTop; +} + +inline EnclosingRule Position::enclosing_drop() const { + assert(var != nullptr); + return var->enclosingDrop; +} + +inline Bitboard Position::drop_region(Color c) const { + assert(var != nullptr); + return c == WHITE ? var->whiteDropRegion : var->blackDropRegion; +} + +inline Bitboard Position::drop_region(Color c, PieceType pt) const { + Bitboard b = drop_region(c) & board_bb(c, pt); + + // Connect4-style drops + if (drop_on_top()) + b &= shift(pieces()) | Rank1BB; + // Pawns on back ranks + if (pt == PAWN) + { + if (!var->promotionZonePawnDrops) + b &= ~zone_bb(c, promotion_rank(), max_rank()); + if (!first_rank_pawn_drops()) + b &= ~rank_bb(relative_rank(c, RANK_1, max_rank())); + } + // Doubled shogi pawns + if (pt == SHOGI_PAWN && !shogi_doubled_pawn()) + for (File f = FILE_A; f <= max_file(); ++f) + if (file_bb(f) & pieces(c, pt)) + b &= ~file_bb(f); + // Sittuyin rook drops + if (pt == ROOK && sittuyin_rook_drop()) + b &= rank_bb(relative_rank(c, RANK_1, max_rank())); + + // Filter out squares where the drop does not enclose at least one opponent's piece + if (enclosing_drop()) + { + // Reversi start + if (var->enclosingDropStart & ~pieces()) + b &= var->enclosingDropStart; + else + { + if (enclosing_drop() == REVERSI) + { + Bitboard theirs = pieces(~c); + b &= shift(theirs) | shift(theirs) + | shift(theirs) | shift(theirs) + | shift(theirs) | shift(theirs) + | shift(theirs) | shift(theirs); + Bitboard b2 = b; + while (b2) + { + Square s = pop_lsb(&b2); + if (!(attacks_bb(c, QUEEN, s, board_bb() & ~pieces(~c)) & ~PseudoAttacks[c][KING][s] & pieces(c))) + b ^= s; + } + } + else + { + assert(enclosing_drop() == ATAXX); + Bitboard ours = pieces(c); + b &= shift(ours) | shift(ours) + | shift(ours) | shift(ours) + | shift(ours) | shift(ours) + | shift(ours) | shift(ours); + } + } + } + + return b; +} + +inline bool Position::sittuyin_rook_drop() const { + assert(var != nullptr); + return var->sittuyinRookDrop; +} + +inline bool Position::drop_opposite_colored_bishop() const { + assert(var != nullptr); + return var->dropOppositeColoredBishop; +} + +inline bool Position::drop_promoted() const { + assert(var != nullptr); + return var->dropPromoted; +} + +inline bool Position::shogi_doubled_pawn() const { + assert(var != nullptr); + return var->shogiDoubledPawn; +} + +inline bool Position::immobility_illegal() const { + assert(var != nullptr); + return var->immobilityIllegal; +} + +inline bool Position::gating() const { + assert(var != nullptr); + return var->gating; +} + +inline bool Position::arrow_gating() const { + assert(var != nullptr); + return var->arrowGating; +} + +inline bool Position::seirawan_gating() const { + assert(var != nullptr); + return var->seirawanGating; +} + +inline bool Position::cambodian_moves() const { + assert(var != nullptr); + return var->cambodianMoves; +} + +inline Bitboard Position::diagonal_lines() const { + assert(var != nullptr); + return var->diagonalLines; +} + +inline bool Position::pass() const { + assert(var != nullptr); + return var->pass || var->passOnStalemate; +} + +inline bool Position::pass_on_stalemate() const { + assert(var != nullptr); + return var->passOnStalemate; +} + +inline Bitboard Position::promoted_soldiers(Color c) const { + assert(var != nullptr); + return pieces(c, SOLDIER) & zone_bb(c, var->soldierPromotionRank, max_rank()); +} + +inline bool Position::makpong() const { + assert(var != nullptr); + return var->makpongRule; +} + +inline int Position::n_move_rule() const { + assert(var != nullptr); + return var->nMoveRule; +} + +inline int Position::n_fold_rule() const { + assert(var != nullptr); + return var->nFoldRule; +} + +inline EnclosingRule Position::flip_enclosed_pieces() const { + assert(var != nullptr); + return var->flipEnclosedPieces; +} + +inline Value Position::stalemate_value(int ply) const { + assert(var != nullptr); + if (var->stalematePieceCount) + { + int c = count(sideToMove) - count(~sideToMove); + return c == 0 ? VALUE_DRAW : convert_mate_value(c < 0 ? var->stalemateValue : -var->stalemateValue, ply); + } + return convert_mate_value(var->stalemateValue, ply); +} + +inline Value Position::checkmate_value(int ply) const { + assert(var != nullptr); + // Check for illegal mate by shogi pawn drop + if ( var->shogiPawnDropMateIllegal + && !(checkers() & ~pieces(SHOGI_PAWN)) + && !st->capturedPiece + && st->pliesFromNull > 0 + && (st->materialKey != st->previous->materialKey)) + { + return mate_in(ply); + } + // Check for shatar mate rule + if (var->shatarMateRule) + { + // Mate by knight is illegal + if (!(checkers() & ~pieces(KNIGHT))) + return mate_in(ply); + + StateInfo* stp = st; + while (stp->checkersBB) + { + // Return mate score if there is at least one shak in series of checks + if (stp->shak) + return convert_mate_value(var->checkmateValue, ply); + + if (stp->pliesFromNull < 2) + break; + + stp = stp->previous->previous; + } + // Niol + return VALUE_DRAW; + } + // Return mate value + return convert_mate_value(var->checkmateValue, ply); +} + +inline Value Position::extinction_value(int ply) const { + assert(var != nullptr); + return convert_mate_value(var->extinctionValue, ply); +} + +inline bool Position::extinction_claim() const { + assert(var != nullptr); + return var->extinctionClaim; +} + +inline const std::set& Position::extinction_piece_types() const { + assert(var != nullptr); + return var->extinctionPieceTypes; +} + +inline bool Position::extinction_single_piece() const { + assert(var != nullptr); + return var->extinctionValue == -VALUE_MATE + && std::any_of(var->extinctionPieceTypes.begin(), + var->extinctionPieceTypes.end(), + [](PieceType pt) { return pt != ALL_PIECES; }); +} + +inline int Position::extinction_piece_count() const { + assert(var != nullptr); + return var->extinctionPieceCount; +} + +inline int Position::extinction_opponent_piece_count() const { + assert(var != nullptr); + return var->extinctionOpponentPieceCount; +} + +inline PieceType Position::capture_the_flag_piece() const { + assert(var != nullptr); + return var->flagPiece; +} + +inline Bitboard Position::capture_the_flag(Color c) const { + assert(var != nullptr); + return c == WHITE ? var->whiteFlag : var->blackFlag; +} + +inline bool Position::flag_move() const { + assert(var != nullptr); + return var->flagMove; +} + +inline bool Position::check_counting() const { + assert(var != nullptr); + return var->checkCounting; +} + +inline int Position::connect_n() const { + assert(var != nullptr); + return var->connectN; +} + +inline CheckCount Position::checks_remaining(Color c) const { + return st->checksRemaining[c]; +} + +inline MaterialCounting Position::material_counting() const { + assert(var != nullptr); + return var->materialCounting; +} + +inline CountingRule Position::counting_rule() const { + assert(var != nullptr); + return var->countingRule; +} + +inline bool Position::is_immediate_game_end() const { + Value result; + return is_immediate_game_end(result); +} + +inline bool Position::is_optional_game_end() const { + Value result; + return is_optional_game_end(result); +} + +inline bool Position::is_game_end(Value& result, int ply) const { + return is_immediate_game_end(result, ply) || is_optional_game_end(result, ply); +} + inline Color Position::side_to_move() const { return sideToMove; } @@@ -1011,8 -301,8 +1008,8 @@@ inline Bitboard Position::check_squares return st->checkSquares[pt]; } - inline bool Position::is_discovery_check_on_king(Color c, Move m) const { + inline bool Position::is_discovered_check_on_king(Color c, Move m) const { - return st->blockersForKing[c] & from_sq(m); + return is_ok(from_sq(m)) && st->blockersForKing[c] & from_sq(m); } inline bool Position::pawn_passed(Color c, Square s) const { @@@ -1020,9 -310,8 +1017,9 @@@ } inline bool Position::advanced_pawn_push(Move m) const { - return type_of(moved_piece(m)) == PAWN - && relative_rank(sideToMove, to_sq(m)) > RANK_5; + return ( type_of(moved_piece(m)) == PAWN + && relative_rank(sideToMove, to_sq(m), max_rank()) > (max_rank() + 1) / 2) - || type_of(m) == ENPASSANT; ++ || type_of(m) == EN_PASSANT; } inline int Position::pawns_on_same_color_squares(Color c, Square s) const { @@@ -1081,13 -363,13 +1079,13 @@@ inline bool Position::is_chess960() con inline bool Position::capture_or_promotion(Move m) const { assert(is_ok(m)); - return type_of(m) == PROMOTION || type_of(m) == ENPASSANT || (type_of(m) != CASTLING && !empty(to_sq(m))); - return type_of(m) != NORMAL ? type_of(m) != CASTLING : !empty(to_sq(m)); ++ return type_of(m) == PROMOTION || type_of(m) == EN_PASSANT || (type_of(m) != CASTLING && !empty(to_sq(m))); } inline bool Position::capture(Move m) const { assert(is_ok(m)); // Castling is encoded as "king captures rook" - return (!empty(to_sq(m)) && type_of(m) != CASTLING && from_sq(m) != to_sq(m)) || type_of(m) == ENPASSANT; - return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT; ++ return (!empty(to_sq(m)) && type_of(m) != CASTLING && from_sq(m) != to_sq(m)) || type_of(m) == EN_PASSANT; } inline Piece Position::captured_piece() const { diff --cc src/psqt.cpp index 0fa7ec7,46605d5..c9e7d59 --- a/src/psqt.cpp +++ b/src/psqt.cpp @@@ -16,27 -16,23 +16,32 @@@ along with this program. If not, see . */ + + #include "psqt.h" + #include +#include - #include "types.h" #include "bitboard.h" + #include "types.h" + +#include "piece.h" +#include "variant.h" +#include "misc.h" + +Value EvalPieceValue[PHASE_NB][PIECE_NB]; +Value CapturePieceValue[PHASE_NB][PIECE_NB]; + - namespace PSQT { + - #define S(mg, eg) make_score(mg, eg) - // Bonus[PieceType][Square / 2] contains Piece-Square scores. For each piece - // type on a given square a (middlegame, endgame) score pair is assigned. Table - // is defined for files A..D and white side: it is symmetric for black side and - // second half of the files. + namespace + { + + auto constexpr S = make_score; + + // 'Bonus' contains Piece-Square parameters. + // Scores are explicit for files A to D, implicitly mirrored for E to H. -constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { +constexpr Score Bonus[PIECE_TYPE_NB][RANK_NB][int(FILE_NB) / 2] = { { }, { }, { // Knight @@@ -103,9 -98,13 +108,13 @@@ constexpr Score PBonus[RANK_NB][FILE_NB { S( -7, 0), S( 7,-11), S( -3, 12), S(-13, 21), S( 5, 25), S(-16, 19), S( 10, 4), S( -8, 7) } }; - #undef S + } // namespace + + + namespace PSQT + { -Score psq[PIECE_NB][SQUARE_NB]; +Score psq[PIECE_NB][SQUARE_NB + 1]; // PSQT::init() initializes piece-square tables: the white halves of the tables are // copied from Bonus[] and PBonus[], adding the piece value, then the black halves of diff --cc src/psqt.h index 0000000,8b4fd6e..c0030de mode 000000,100644..100644 --- a/src/psqt.h +++ b/src/psqt.h @@@ -1,0 -1,38 +1,39 @@@ + /* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + + + #ifndef PSQT_H_INCLUDED + #define PSQT_H_INCLUDED + + + #include "types.h" + ++#include "variant.h" + + namespace PSQT + { + -extern Score psq[PIECE_NB][SQUARE_NB]; ++extern Score psq[PIECE_NB][SQUARE_NB + 1]; + + // Fill psqt array from a set of internally linked parameters -extern void init(); ++extern void init(const Variant*); + + } // namespace PSQT + + + #endif // PSQT_H_INCLUDED diff --cc src/search.cpp index 943baf9,6cc115f..3c9036c --- a/src/search.cpp +++ b/src/search.cpp @@@ -913,15 -829,10 +903,15 @@@ namespace ? ss->staticEval > (ss-4)->staticEval || (ss-4)->staticEval == VALUE_NONE : ss->staticEval > (ss-2)->staticEval; + // Skip early pruning in case of mandatory capture + if (pos.must_capture() && pos.has_capture()) + goto moves_loop; + - // Step 8. Futility pruning: child node (~50 Elo) + // Step 7. Futility pruning: child node (~50 Elo) if ( !PvNode - && depth < 8 + && depth < 9 - && eval - futility_margin(depth, improving) >= beta + && !(pos.capture_the_flag_piece() && !pos.checking_permitted()) + && eval - futility_margin(depth, improving) * (1 + pos.check_counting() + 2 * pos.must_capture() + pos.extinction_single_piece()) >= beta && eval < VALUE_KNOWN_WIN) // Do not return unproven wins return eval; @@@ -977,9 -886,9 +967,9 @@@ } } - probCutBeta = beta + (183 + 20 * !!pos.capture_the_flag_piece() + 50 * pos.captures_to_hand()) * (1 + pos.check_counting() + pos.extinction_single_piece()) - 49 * improving; - probCutBeta = beta + 194 - 49 * improving; ++ probCutBeta = beta + (194 + 20 * !!pos.capture_the_flag_piece() + 50 * pos.captures_to_hand()) * (1 + pos.check_counting() + pos.extinction_single_piece()) - 49 * improving; - // Step 10. ProbCut (~10 Elo) + // Step 9. ProbCut (~10 Elo) // If we have a good enough capture and a reduced search returns a value // much above beta, we can (almost) safely prune the previous move. if ( !PvNode @@@ -1119,9 -1028,9 +1109,9 @@@ moves_loop: // When in check, search st // Calculate new depth for this move newDepth = depth - 1; - // Step 13. Pruning at shallow depth (~200 Elo) + // Step 12. Pruning at shallow depth (~200 Elo) if ( !rootNode - && pos.non_pawn_material(us) + && (pos.non_pawn_material(us) || pos.count(us) == pos.count(us)) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) { // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold @@@ -1131,9 -1039,20 +1121,24 @@@ // Reduced depth of the next LMR search int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0); - if ( !captureOrPromotion - && !givesCheck - && !(pos.must_capture() && pos.attackers_to(to_sq(move), ~us))) ++ if (pos.must_capture() && (captureOrPromotion || givesCheck || pos.attackers_to(to_sq(move), ~us))) ++ {} ++ else ++ + if ( captureOrPromotion + || givesCheck) + { + // Capture history based pruning when the move doesn't give check + if ( !givesCheck + && lmrDepth < 1 + && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] < 0) + continue; + + // SEE based pruning - if (!pos.see_ge(move, Value(-218) * depth)) // (~25 Elo) ++ if (!pos.see_ge(move, Value(-218 - 120 * pos.captures_to_hand()) * depth)) // (~25 Elo) + continue; + } + else { // Countermoves based pruning (~20 Elo) if ( lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1) @@@ -1144,34 -1063,20 +1149,22 @@@ // Futility pruning: parent node (~5 Elo) if ( lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 254 + 159 * lmrDepth <= alpha - && (*contHist[0])[movedPiece][to_sq(move)] - + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - + (*contHist[5])[movedPiece][to_sq(move)] / 2 < 26394) + && !( pos.extinction_value() == -VALUE_MATE + && pos.extinction_piece_types().find(ALL_PIECES) == pos.extinction_piece_types().end()) - && ss->staticEval + (266 + 170 * lmrDepth) * (1 + pos.check_counting()) <= alpha ++ && ss->staticEval + (254 + 159 * lmrDepth) * (1 + pos.check_counting()) <= alpha + && (*contHist[0])[history_slot(movedPiece)][to_sq(move)] + + (*contHist[1])[history_slot(movedPiece)][to_sq(move)] + + (*contHist[3])[history_slot(movedPiece)][to_sq(move)] - + (*contHist[5])[history_slot(movedPiece)][to_sq(move)] / 2 < 27376) ++ + (*contHist[5])[history_slot(movedPiece)][to_sq(move)] / 2 < 26394) continue; // Prune moves with negative SEE (~20 Elo) - if (!pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth))) + if (!pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.capture_the_flag_piece()) * lmrDepth * lmrDepth))) continue; } - else if (!pos.must_capture()) - { - // Capture history based pruning when the move doesn't give check - if ( !givesCheck - && lmrDepth < 1 - && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] < 0) - continue; - - // SEE based pruning - if (!pos.see_ge(move, Value(-213 - 120 * pos.captures_to_hand()) * depth)) // (~25 Elo) - continue; - } } - // Step 14. Extensions (~75 Elo) + // Step 13. Extensions (~75 Elo) // Singular extension search (~70 Elo). If all moves but one fail low on a // search of (alpha-s, beta-s), and just one fails high on (alpha, beta), @@@ -1230,38 -1135,26 +1223,31 @@@ && pos.non_pawn_material() <= 2 * RookValueMg) extension = 1; - // Late irreversible move extension - if ( move == ttMove - && pos.rule50_count() > 80 - && (captureOrPromotion || type_of(movedPiece) == PAWN)) - extension = 2; - + // Losing chess capture extension + else if ( pos.must_capture() + && pos.capture(move) + && (ss->inCheck || MoveList(pos).size() == 1)) + extension = 1; + // Add extension to new depth newDepth += extension; // Speculative prefetch as early as possible -- prefetch(TT.first_entry(pos.key_after(move))); -- // Update the current move (this must be done after singular extension search) ss->currentMove = move; ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] [captureOrPromotion] - [movedPiece] + [history_slot(movedPiece)] [to_sq(move)]; - // Step 15. Make the move + // Step 14. Make the move pos.do_move(move, st, givesCheck); - // Step 16. Reduced depth search (LMR, ~200 Elo). If the move fails high it will be + // Step 15. Reduced depth search (LMR, ~200 Elo). If the move fails high it will be // re-searched at full depth. if ( depth >= 3 && moveCount > 1 + 2 * rootNode ++ && !(pos.must_capture() && (givesCheck || pos.has_capture())) && ( !captureOrPromotion || moveCountPruning || ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha @@@ -1333,14 -1233,13 +1326,13 @@@ r++; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / (14884 - 4434 * pos.captures_to_hand()); - } - else - { - // Unless giving check, this capture is likely bad - if ( !givesCheck - && ss->staticEval + PieceValue[EG][pos.captured_piece()] + 210 * depth <= alpha) - r++; + // If we are not in check use statScore, if we are in check + // use sum of main history and first continuation history with an offset + if (ss->inCheck) + r -= (thisThread->mainHistory[us][from_to(move)] - + (*contHist[0])[movedPiece][to_sq(move)] - 4333) / 16384; ++ + (*contHist[0])[history_slot(movedPiece)][to_sq(move)] - 4333) / 16384; + else - r -= ss->statScore / 14884; ++ r -= ss->statScore / (14884 - 4434 * pos.captures_to_hand()); } Depth d = std::clamp(newDepth - r, 1, newDepth); diff --cc src/types.h index 728762e,d270384..715bba8 --- a/src/types.h +++ b/src/types.h @@@ -242,22 -121,16 +242,22 @@@ constexpr int MAX_PLY = 246 enum Move : int { MOVE_NONE, - MOVE_NULL = 65 + MOVE_NULL = 1 + (1 << SQUARE_BITS) }; -enum MoveType { +enum MoveType : int { NORMAL, - ENPASSANT = 1 << (2 * SQUARE_BITS), - PROMOTION = 1 << 14, - EN_PASSANT = 2 << 14, - CASTLING = 3 << 14 ++ EN_PASSANT = 1 << (2 * SQUARE_BITS), + CASTLING = 2 << (2 * SQUARE_BITS), + PROMOTION = 3 << (2 * SQUARE_BITS), + DROP = 4 << (2 * SQUARE_BITS), + PIECE_PROMOTION = 5 << (2 * SQUARE_BITS), + PIECE_DEMOTION = 6 << (2 * SQUARE_BITS), + SPECIAL = 7 << (2 * SQUARE_BITS), }; +constexpr int MOVE_TYPE_BITS = 4; + enum Color { WHITE, BLACK, COLOR_NB = 2 }; diff --cc src/variant.cpp index aef593d,0000000..1909e5b mode 100644,000000..100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@@ -1,1170 -1,0 +1,1170 @@@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish - Copyright (C) 2018-2020 Fabian Fichter ++ Copyright (C) 2018-2021 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include "parser.h" +#include "piece.h" +#include "variant.h" + +using std::string; + +VariantMap variants; // Global object + +namespace { + // Define variant rules + Variant* fairy_variant_base() { + Variant* v = new Variant(); + v->pieceToCharTable = "PNBRQ................Kpnbrq................k"; + v->endgameEval = false; + return v; + } + Variant* chess_variant() { + Variant* v = fairy_variant_base(); + v->endgameEval = true; + return v; + } + Variant* chess960_variant() { + Variant* v = chess_variant(); + v->chess960 = true; + return v; + } + Variant* nocastle_variant() { + Variant* v = chess_variant(); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"; + v->castling = false; + return v; + } + // Armageddon Chess + // https://en.wikipedia.org/wiki/Fast_chess#Armageddon + Variant* armageddon_variant() { + Variant* v = fairy_variant_base(); + v->materialCounting = BLACK_DRAW_ODDS; + return v; + } + Variant* fairy_variant() { + Variant* v = chess_variant(); + v->add_piece(SILVER, 's'); + v->add_piece(FERS, 'f'); + return v; + } + // Makruk (Thai Chess) + Variant* makruk_variant() { + Variant* v = chess_variant(); + v->variantTemplate = "makruk"; + v->pieceToCharTable = "PN.R.M....SKpn.r.m....sk"; + v->remove_piece(BISHOP); + v->remove_piece(QUEEN); + v->add_piece(KHON, 's'); + v->add_piece(MET, 'm'); + v->startFen = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w - - 0 1"; + v->promotionRank = RANK_6; + v->promotionPieceTypes = {MET}; + v->doubleStep = false; + v->castling = false; + v->nMoveRule = 0; + v->countingRule = MAKRUK_COUNTING; + return v; + } + // Makpong (Defensive Chess) + // A Makruk variant used for tie-breaks + // https://www.mayhematics.com/v/vol8/vc64b.pdf, p. 177 + Variant* makpong_variant() { + Variant* v = makruk_variant(); + v->makpongRule = true; + return v; + } + Variant* cambodian_variant() { + Variant* v = makruk_variant(); + v->startFen = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w DEde - 0 1"; + v->gating = true; + v->cambodianMoves = true; + return v; + } + Variant* karouk_variant() { + Variant* v = cambodian_variant(); + v->checkCounting = true; + return v; + } + Variant* asean_variant() { + Variant* v = chess_variant(); + v->remove_piece(BISHOP); + v->remove_piece(QUEEN); + v->add_piece(KHON, 'b'); + v->add_piece(MET, 'q'); + v->startFen = "rnbqkbnr/8/pppppppp/8/8/PPPPPPPP/8/RNBQKBNR w - - 0 1"; + v->promotionPieceTypes = {ROOK, KNIGHT, KHON, MET}; + v->doubleStep = false; + v->castling = false; + v->countingRule = ASEAN_COUNTING; + return v; + } + Variant* aiwok_variant() { + Variant* v = makruk_variant(); + v->pieceToCharTable = "PN.R...A..SKpn.r...a..sk"; + v->remove_piece(MET); + v->add_piece(AIWOK, 'a'); + v->startFen = "rnsaksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKASNR w - - 0 1"; + v->promotionPieceTypes = {AIWOK}; + return v; + } + Variant* shatranj_variant() { + Variant* v = fairy_variant_base(); + v->variantTemplate = "shatranj"; + v->pieceToCharTable = "PN.R.QB....Kpn.r.qb....k"; + v->remove_piece(BISHOP); + v->remove_piece(QUEEN); + v->add_piece(ALFIL, 'b'); + v->add_piece(FERS, 'q'); + v->startFen = "rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBKQBNR w - - 0 1"; + v->promotionPieceTypes = {FERS}; + v->doubleStep = false; + v->castling = false; + v->extinctionValue = -VALUE_MATE; + v->extinctionClaim = true; + v->extinctionPieceTypes = {ALL_PIECES}; + v->extinctionPieceCount = 1; + v->extinctionOpponentPieceCount = 2; + v->stalemateValue = -VALUE_MATE; + v->nMoveRule = 70; + return v; + } + // Chaturanga + // https://en.wikipedia.org/wiki/Chaturanga + // Rules as used on chess.com + Variant* chaturanga_variant() { + Variant* v = shatranj_variant(); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"; + v->extinctionValue = VALUE_NONE; + return v; + } + Variant* amazon_variant() { + Variant* v = chess_variant(); + v->pieceToCharTable = "PNBR..............AKpnbr..............ak"; + v->remove_piece(QUEEN); + v->add_piece(AMAZON, 'a'); + v->startFen = "rnbakbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBAKBNR w KQkq - 0 1"; + v->promotionPieceTypes = {AMAZON, ROOK, BISHOP, KNIGHT}; + return v; + } + Variant* hoppelpoppel_variant() { + Variant* v = chess_variant(); + v->remove_piece(KNIGHT); + v->remove_piece(BISHOP); + v->add_piece(KNIBIS, 'n'); + v->add_piece(BISKNI, 'b'); + v->promotionPieceTypes = {QUEEN, ROOK, BISKNI, KNIBIS}; + return v; + } + Variant* newzealand_variant() { + Variant* v = chess_variant(); + v->remove_piece(ROOK); + v->remove_piece(KNIGHT); + v->add_piece(ROOKNI, 'r'); + v->add_piece(KNIROO, 'n'); + v->castlingRookPiece = ROOKNI; + v->promotionPieceTypes = {QUEEN, ROOKNI, BISHOP, KNIROO}; + return v; + } + Variant* kingofthehill_variant() { + Variant* v = fairy_variant_base(); + v->flagPiece = KING; + v->whiteFlag = (Rank4BB | Rank5BB) & (FileDBB | FileEBB); + v->blackFlag = (Rank4BB | Rank5BB) & (FileDBB | FileEBB); + v->flagMove = false; + return v; + } + Variant* racingkings_variant() { + Variant* v = fairy_variant_base(); + v->startFen = "8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1"; + v->flagPiece = KING; + v->whiteFlag = Rank8BB; + v->blackFlag = Rank8BB; + v->flagMove = true; + v->castling = false; + v->checking = false; + return v; + } + Variant* knightmate_variant() { + Variant* v = fairy_variant_base(); + v->add_piece(COMMONER, 'm'); + v->remove_piece(KNIGHT); + v->startFen = "rmbqkbmr/pppppppp/8/8/8/8/PPPPPPPP/RMBQKBMR w KQkq - 0 1"; + v->kingType = KNIGHT; + v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP}; + return v; + } + Variant* losers_variant() { + Variant* v = fairy_variant_base(); + v->checkmateValue = VALUE_MATE; + v->stalemateValue = VALUE_MATE; + v->extinctionValue = VALUE_MATE; + v->extinctionPieceTypes = {ALL_PIECES}; + v->extinctionPieceCount = 1; + v->mustCapture = true; + return v; + } + Variant* giveaway_variant() { + Variant* v = fairy_variant_base(); + v->variantTemplate = "giveaway"; + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT}; + v->stalemateValue = VALUE_MATE; + v->extinctionValue = VALUE_MATE; + v->extinctionPieceTypes = {ALL_PIECES}; + v->mustCapture = true; + return v; + } + Variant* antichess_variant() { + Variant* v = giveaway_variant(); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"; + v->castling = false; + return v; + } + Variant* suicide_variant() { + Variant* v = antichess_variant(); + v->stalematePieceCount = true; + return v; + } + Variant* codrus_variant() { + Variant* v = giveaway_variant(); + v->promotionPieceTypes = {QUEEN, ROOK, BISHOP, KNIGHT}; + v->extinctionPieceTypes = {COMMONER}; + return v; + } + Variant* extinction_variant() { + Variant* v = fairy_variant_base(); + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT}; + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT, PAWN}; + return v; + } + Variant* kinglet_variant() { + Variant* v = extinction_variant(); + v->promotionPieceTypes = {COMMONER}; + v->extinctionPieceTypes = {PAWN}; + return v; + } + // Three Kings Chess + // https://github.com/cutechess/cutechess/blob/master/projects/lib/src/board/threekingsboard.h + Variant* threekings_variant() { + Variant* v = fairy_variant_base(); + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->startFen = "knbqkbnk/pppppppp/8/8/8/8/PPPPPPPP/KNBQKBNK w - - 0 1"; + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = {COMMONER}; + v->extinctionPieceCount = 2; + return v; + } + // Horde chess + // https://en.wikipedia.org/wiki/Dunsany%27s_chess#Horde_chess + Variant* horde_variant() { + Variant* v = fairy_variant_base(); + v->startFen = "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1"; + v->doubleStepRankMin = RANK_1; + v->enPassantRegion = Rank3BB | Rank6BB; // exclude en passant on second rank + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = {ALL_PIECES}; + return v; + } + // Atomic chess without checks (ICC rules) + // https://www.chessclub.com/help/atomic + Variant* nocheckatomic_variant() { + Variant* v = fairy_variant_base(); + v->variantTemplate = "atomic"; + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = {COMMONER}; + v->blastOnCapture = true; + return v; + } + // Atomic chess + // https://en.wikipedia.org/wiki/Atomic_chess + Variant* atomic_variant() { + Variant* v = nocheckatomic_variant(); + // TODO: castling, check(-mate), stalemate are not yet properly implemented + v->extinctionPseudoRoyal = true; + return v; + } + Variant* threecheck_variant() { + Variant* v = fairy_variant_base(); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 3+3 0 1"; + v->checkCounting = true; + return v; + } + Variant* fivecheck_variant() { + Variant* v = threecheck_variant(); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 5+5 0 1"; + return v; + } + Variant* crazyhouse_variant() { + Variant* v = fairy_variant_base(); + v->variantTemplate = "crazyhouse"; + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 0 1"; + v->pieceDrops = true; + v->capturesToHand = true; + return v; + } + Variant* loop_variant() { + Variant* v = crazyhouse_variant(); + v->dropLoop = true; + return v; + } + Variant* chessgi_variant() { + Variant* v = loop_variant(); + v->firstRankPawnDrops = true; + return v; + } + Variant* bughouse_variant() { + Variant* v = crazyhouse_variant(); + v->variantTemplate = "bughouse"; + v->twoBoards = true; + v->capturesToHand = false; + v->stalemateValue = -VALUE_MATE; + return v; + } + // Koedem (Bughouse variant) + // http://schachclub-oetigheim.de/wp-content/uploads/2016/04/Koedem-rules.pdf + Variant* koedem_variant() { + Variant* v = bughouse_variant(); + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->mustDrop = true; + v->mustDropType = COMMONER; + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = {COMMONER}; + v->extinctionOpponentPieceCount = 2; // own all kings/commoners + return v; + } + Variant* pocketknight_variant() { + Variant* v = chess_variant(); + v->variantTemplate = "bughouse"; + v->pocketSize = 2; + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[Nn] w KQkq - 0 1"; + v->pieceDrops = true; + v->capturesToHand = false; + return v; + } + Variant* placement_variant() { + Variant* v = chess_variant(); + v->variantTemplate = "bughouse"; + v->startFen = "8/pppppppp/8/8/8/8/PPPPPPPP/8[KQRRBBNNkqrrbbnn] w - - 0 1"; + v->mustDrop = true; + v->pieceDrops = true; + v->capturesToHand = false; + v->whiteDropRegion = Rank1BB; + v->blackDropRegion = Rank8BB; + v->dropOppositeColoredBishop = true; + v->castlingDroppedPiece = true; + return v; + } + Variant* sittuyin_variant() { + Variant* v = makruk_variant(); + v->variantTemplate = "bughouse"; + v->pieceToCharTable = "PN.R.F....SKpn.r.f....sk"; + v->startFen = "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[KFRRSSNNkfrrssnn] w - - 0 1"; + v->remove_piece(MET); + v->add_piece(MET, 'f'); + v->mustDrop = true; + v->pieceDrops = true; + v->capturesToHand = false; + v->whiteDropRegion = Rank1BB | Rank2BB | Rank3BB; + v->blackDropRegion = Rank8BB | Rank7BB | Rank6BB; + v->sittuyinRookDrop = true; + v->promotionRank = RANK_1; // no regular promotions + v->sittuyinPromotion = true; + v->promotionLimit[FERS] = 1; + v->immobilityIllegal = false; + v->countingRule = ASEAN_COUNTING; + v->nMoveRule = 50; + return v; + } + Variant* seirawan_variant() { + Variant* v = chess_variant(); + v->variantTemplate = "seirawan"; + v->pieceToCharTable = "PNBRQ.E..........H...Kpnbrq.e..........h...k"; + v->add_piece(ARCHBISHOP, 'h'); + v->add_piece(CHANCELLOR, 'e'); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1"; + v->gating = true; + v->seirawanGating = true; + v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; + return v; + } + Variant* shouse_variant() { + Variant* v = seirawan_variant(); + v->variantTemplate = "crazyhouse"; + v->pieceDrops = true; + v->capturesToHand = true; + return v; + } + Variant* minishogi_variant_base() { + Variant* v = fairy_variant_base(); + v->variantTemplate = "shogi"; + v->maxRank = RANK_5; + v->maxFile = FILE_E; + v->reset_pieces(); + v->add_piece(SHOGI_PAWN, 'p'); + v->add_piece(SILVER, 's'); + v->add_piece(GOLD, 'g'); + v->add_piece(BISHOP, 'b'); + v->add_piece(DRAGON_HORSE, 'h'); + v->add_piece(ROOK, 'r'); + v->add_piece(DRAGON, 'd'); + v->add_piece(KING, 'k'); + v->startFen = "rbsgk/4p/5/P4/KGSBR[-] w 0 1"; + v->pieceDrops = true; + v->capturesToHand = true; + v->promotionRank = RANK_5; + v->promotionPieceTypes = {}; + v->doubleStep = false; + v->castling = false; + v->promotedPieceType[SHOGI_PAWN] = GOLD; + v->promotedPieceType[SILVER] = GOLD; + v->promotedPieceType[BISHOP] = DRAGON_HORSE; + v->promotedPieceType[ROOK] = DRAGON; + v->shogiDoubledPawn = false; + v->immobilityIllegal = true; + v->shogiPawnDropMateIllegal = true; + v->stalemateValue = -VALUE_MATE; + v->nFoldRule = 4; + v->nMoveRule = 0; + v->perpetualCheckIllegal = true; + return v; + } + Variant* minishogi_variant() { + Variant* v = minishogi_variant_base(); + v->pieceToCharTable = "P.BR.S...G.+.++.+Kp.br.s...g.+.++.+k"; + v->pocketSize = 5; + v->nFoldValue = -VALUE_MATE; + v->nFoldValueAbsolute = true; + return v; + } + Variant* kyotoshogi_variant() { + Variant* v = minishogi_variant_base(); + v->add_piece(LANCE, 'l'); + v->add_piece(SHOGI_KNIGHT, 'n'); + v->startFen = "p+nks+l/5/5/5/+LSK+NP[-] w 0 1"; + v->promotionRank = RANK_1; + v->mandatoryPiecePromotion = true; + v->pieceDemotion = true; + v->dropPromoted = true; + v->promotedPieceType[LANCE] = GOLD; + v->promotedPieceType[SILVER] = BISHOP; + v->promotedPieceType[SHOGI_KNIGHT] = GOLD; + v->promotedPieceType[SHOGI_PAWN] = ROOK; + v->promotedPieceType[GOLD] = NO_PIECE_TYPE; + v->promotedPieceType[BISHOP] = NO_PIECE_TYPE; + v->promotedPieceType[ROOK] = NO_PIECE_TYPE; + v->immobilityIllegal = false; + v->shogiPawnDropMateIllegal = false; + v->shogiDoubledPawn = true; + return v; + } + Variant* microshogi_variant() { + Variant* v = kyotoshogi_variant(); + v->maxFile = FILE_D; + v->startFen = "kb+r+l/p3/4/3P/+L+RBK[-] w 0 1"; + v->promotionRank = RANK_1; + v->piecePromotionOnCapture = true; + v->promotedPieceType[LANCE] = SILVER; + v->promotedPieceType[BISHOP] = GOLD; + v->promotedPieceType[ROOK] = GOLD; + v->promotedPieceType[SHOGI_PAWN] = SHOGI_KNIGHT; + v->promotedPieceType[SILVER] = NO_PIECE_TYPE; + v->promotedPieceType[GOLD] = NO_PIECE_TYPE; + v->promotedPieceType[SHOGI_KNIGHT] = NO_PIECE_TYPE; + return v; + } + Variant* dobutsu_variant() { + Variant* v = minishogi_variant_base(); + v->pieceToCharTable = "C....E...G.+.....Lc....e...g.+.....l"; + v->pocketSize = 3; + v->maxRank = RANK_4; + v->maxFile = FILE_C; + v->reset_pieces(); + v->add_piece(SHOGI_PAWN, 'c'); + v->add_piece(GOLD, 'h'); + v->add_piece(FERS, 'e'); + v->add_piece(WAZIR, 'g'); + v->add_piece(KING, 'l'); + v->startFen = "gle/1c1/1C1/ELG[-] w 0 1"; + v->promotionRank = RANK_4; + v->immobilityIllegal = false; + v->shogiPawnDropMateIllegal = false; + v->flagPiece = KING; + v->whiteFlag = Rank4BB; + v->blackFlag = Rank1BB; + v->shogiDoubledPawn = true; + return v; + } + Variant* gorogoroshogi_variant() { + Variant* v = minishogi_variant_base(); + v->pieceToCharTable = "P....S...G.+....+Kp....s...g.+....+k"; + v->pocketSize = 3; + v->maxRank = RANK_6; + v->maxFile = FILE_E; + v->startFen = "sgkgs/5/1ppp1/1PPP1/5/SGKGS[-] w 0 1"; + v->promotionRank = RANK_5; + return v; + } + Variant* judkinsshogi_variant() { + Variant* v = minishogi_variant_base(); + v->pieceToCharTable = "PNBR.S...G.++++.+Kpnbr.s...g.++++.+k"; + v->maxRank = RANK_6; + v->maxFile = FILE_F; + v->add_piece(SHOGI_KNIGHT, 'n'); + v->startFen = "rbnsgk/5p/6/6/P5/KGSNBR[-] w 0 1"; + v->promotionRank = RANK_5; + v->promotedPieceType[SHOGI_KNIGHT] = GOLD; + return v; + } + Variant* euroshogi_variant() { + Variant* v = minishogi_variant_base(); + v->pieceToCharTable = "PNBR.....G.++++Kpnbr.....g.++++k"; + v->maxRank = RANK_8; + v->maxFile = FILE_H; + v->add_piece(EUROSHOGI_KNIGHT, 'n'); + v->startFen = "1nbgkgn1/1r4b1/pppppppp/8/8/PPPPPPPP/1B4R1/1NGKGBN1[-] w 0 1"; + v->promotionRank = RANK_6; + v->promotedPieceType[EUROSHOGI_KNIGHT] = GOLD; + v->mandatoryPiecePromotion = true; + return v; + } + Variant* losalamos_variant() { + Variant* v = fairy_variant_base(); + v->pieceToCharTable = "PN.RQ................Kpn.rq................k"; + v->maxRank = RANK_6; + v->maxFile = FILE_F; + v->remove_piece(BISHOP); + v->startFen = "rnqknr/pppppp/6/6/PPPPPP/RNQKNR w - - 0 1"; + v->promotionRank = RANK_6; + v->promotionPieceTypes = {QUEEN, ROOK, KNIGHT}; + v->doubleStep = false; + v->castling = false; + return v; + } + Variant* gardner_variant() { + Variant* v = fairy_variant_base(); + v->maxRank = RANK_5; + v->maxFile = FILE_E; + v->startFen = "rnbqk/ppppp/5/PPPPP/RNBQK w - - 0 1"; + v->promotionRank = RANK_5; + v->doubleStep = false; + v->castling = false; + return v; + } + Variant* almost_variant() { + Variant* v = chess_variant(); + v->pieceToCharTable = "PNBR............CKpnbr............ck"; + v->remove_piece(QUEEN); + v->add_piece(CHANCELLOR, 'c'); + v->startFen = "rnbckbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBCKBNR w KQkq - 0 1"; + v->promotionPieceTypes = {CHANCELLOR, ROOK, BISHOP, KNIGHT}; + return v; + } + Variant* chigorin_variant() { + Variant* v = chess_variant(); + v->pieceToCharTable = "PNBR............CKpnbrq............k"; + v->add_piece(CHANCELLOR, 'c'); + v->startFen = "rbbqkbbr/pppppppp/8/8/8/8/PPPPPPPP/RNNCKNNR w KQkq - 0 1"; + v->promotionPieceTypes = {QUEEN, CHANCELLOR, ROOK, BISHOP, KNIGHT}; + return v; + } + Variant* shatar_variant() { + Variant* v = chess_variant(); + v->pieceToCharTable = "PNBR..........J......Kpnbr..........j......k"; + v->remove_piece(QUEEN); + v->add_piece(BERS, 'j'); + v->startFen = "rnbjkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBJKBNR w - - 0 1"; + v->promotionPieceTypes = {BERS}; + v->doubleStep = false; + v->castling = false; + v->extinctionValue = VALUE_DRAW; // Robado + v->extinctionPieceTypes = {ALL_PIECES}; + v->extinctionPieceCount = 1; + v->shatarMateRule = true; + return v; + } + Variant* clobber_variant() { + Variant* v = fairy_variant_base(); + v->pieceToCharTable = "P.................p................."; + v->maxRank = RANK_6; + v->maxFile = FILE_E; + v->reset_pieces(); + v->add_piece(CLOBBER_PIECE, 'p'); + v->startFen = "PpPpP/pPpPp/PpPpP/pPpPp/PpPpP/pPpPp w 0 1"; + v->promotionPieceTypes = {}; + v->doubleStep = false; + v->castling = false; + v->stalemateValue = -VALUE_MATE; + v->immobilityIllegal = false; + return v; + } + Variant* breakthrough_variant() { + Variant* v = fairy_variant_base(); + v->pieceToCharTable = "P.................p................."; + v->reset_pieces(); + v->add_piece(BREAKTHROUGH_PIECE, 'p'); + v->startFen = "pppppppp/pppppppp/8/8/8/8/PPPPPPPP/PPPPPPPP w 0 1"; + v->promotionPieceTypes = {}; + v->doubleStep = false; + v->castling = false; + v->stalemateValue = -VALUE_MATE; + v->flagPiece = BREAKTHROUGH_PIECE; + v->whiteFlag = Rank8BB; + v->blackFlag = Rank1BB; + return v; + } + Variant* ataxx_variant() { + Variant* v = fairy_variant_base(); + v->pieceToCharTable = "P.................p................."; + v->maxRank = RANK_7; + v->maxFile = FILE_G; + v->reset_pieces(); + v->add_piece(ATAXX_PIECE, 'p'); + v->startFen = "P5p/7/7/7/7/7/p5P[PPPPPPPPPPPPPPPPPPPPPPPPPppppppppppppppppppppppppp] w 0 1"; + v->promotionPieceTypes = {}; + v->pieceDrops = true; + v->doubleStep = false; + v->castling = false; + v->immobilityIllegal = false; + v->stalemateValue = -VALUE_MATE; + v->stalematePieceCount = true; + v->passOnStalemate = true; + v->enclosingDrop = ATAXX; + v->flipEnclosedPieces = ATAXX; + v->materialCounting = UNWEIGHTED_MATERIAL; + return v; + } + Variant* minixiangqi_variant() { + Variant* v = fairy_variant_base(); + v->variantTemplate = "xiangqi"; + v->pieceToCharTable = "PN.R.....K.C.pn.r.....k.c."; + v->maxRank = RANK_7; + v->maxFile = FILE_G; + v->reset_pieces(); + v->add_piece(ROOK, 'r'); + v->add_piece(HORSE, 'n', 'h'); + v->add_piece(KING, 'k'); + v->add_piece(CANNON, 'c'); + v->add_piece(SOLDIER, 'p'); + v->startFen = "rcnkncr/p1ppp1p/7/7/7/P1PPP1P/RCNKNCR w - - 0 1"; + v->mobilityRegion[WHITE][KING] = (Rank1BB | Rank2BB | Rank3BB) & (FileCBB | FileDBB | FileEBB); + v->mobilityRegion[BLACK][KING] = (Rank5BB | Rank6BB | Rank7BB) & (FileCBB | FileDBB | FileEBB); + v->kingType = WAZIR; + v->promotionPieceTypes = {}; + v->doubleStep = false; + v->castling = false; + v->stalemateValue = -VALUE_MATE; + //v->nFoldValue = VALUE_MATE; + v->perpetualCheckIllegal = true; + v->flyingGeneral = true; + return v; + } +#ifdef LARGEBOARDS + Variant* shogi_variant() { + Variant* v = minishogi_variant_base(); + v->maxRank = RANK_9; + v->maxFile = FILE_I; + v->add_piece(LANCE, 'l'); + v->add_piece(SHOGI_KNIGHT, 'n'); + v->startFen = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w 0 1"; + v->promotionRank = RANK_7; + v->promotedPieceType[LANCE] = GOLD; + v->promotedPieceType[SHOGI_KNIGHT] = GOLD; + return v; + } + Variant* capablanca_variant() { + Variant* v = chess_variant(); + v->pieceToCharTable = "PNBRQ..AC............Kpnbrq..ac............k"; + v->maxRank = RANK_8; + v->maxFile = FILE_J; + v->castlingKingsideFile = FILE_I; + v->castlingQueensideFile = FILE_C; + v->add_piece(ARCHBISHOP, 'a'); + v->add_piece(CHANCELLOR, 'c'); + v->startFen = "rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR w KQkq - 0 1"; + v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; + return v; + } + Variant* capahouse_variant() { + Variant* v = capablanca_variant(); + v->startFen = "rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR[] w KQkq - 0 1"; + v->pieceDrops = true; + v->capturesToHand = true; + v->endgameEval = false; + return v; + } + Variant* caparandom_variant() { + Variant* v = capablanca_variant(); + v->chess960 = true; + return v; + } + Variant* gothic_variant() { + Variant* v = capablanca_variant(); + v->startFen = "rnbqckabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBQCKABNR w KQkq - 0 1"; + return v; + } + Variant* janus_variant() { + Variant* v = chess_variant(); + v->pieceToCharTable = "PNBRQ............J...Kpnbrq............j...k"; + v->maxRank = RANK_8; + v->maxFile = FILE_J; + v->castlingKingsideFile = FILE_I; + v->castlingQueensideFile = FILE_B; + v->add_piece(ARCHBISHOP, 'j'); + v->startFen = "rjnbkqbnjr/pppppppppp/10/10/10/10/PPPPPPPPPP/RJNBKQBNJR w KQkq - 0 1"; + v->promotionPieceTypes = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT}; + return v; + } + Variant* modern_variant() { + Variant* v = chess_variant(); + v->pieceToCharTable = "PNBRQ..M.............Kpnbrq..m.............k"; + v->maxRank = RANK_9; + v->maxFile = FILE_I; + v->promotionRank = RANK_9; + v->castlingKingsideFile = FILE_G; + v->castlingQueensideFile = FILE_C; + v->add_piece(ARCHBISHOP, 'm'); + v->startFen = "rnbqkmbnr/ppppppppp/9/9/9/9/9/PPPPPPPPP/RNBMKQBNR w KQkq - 0 1"; + v->promotionPieceTypes = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT}; + return v; + } + Variant* chancellor_variant() { + Variant* v = chess_variant(); + v->pieceToCharTable = "PNBRQ...........CKpnbrq...........ck"; + v->maxRank = RANK_9; + v->maxFile = FILE_I; + v->promotionRank = RANK_9; + v->castlingKingsideFile = FILE_G; + v->castlingQueensideFile = FILE_C; + v->add_piece(CHANCELLOR, 'c'); + v->startFen = "rnbqkcnbr/ppppppppp/9/9/9/9/9/PPPPPPPPP/RNBQKCNBR w KQkq - 0 1"; + v->promotionPieceTypes = {CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; + return v; + } + Variant* embassy_variant() { + Variant* v = capablanca_variant(); + v->castlingKingsideFile = FILE_H; + v->castlingQueensideFile = FILE_B; + v->startFen = "rnbqkcabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBQKCABNR w KQkq - 0 1"; + return v; + } + Variant* centaur_variant() { + Variant* v = chess_variant(); + v->pieceToCharTable = "PNBRQ...............CKpnbrq...............ck"; + v->maxRank = RANK_8; + v->maxFile = FILE_J; + v->castlingKingsideFile = FILE_I; + v->castlingQueensideFile = FILE_C; + v->add_piece(CENTAUR, 'c'); + v->startFen = "rcnbqkbncr/pppppppppp/10/10/10/10/PPPPPPPPPP/RCNBQKBNCR w KQkq - 0 1"; + v->promotionPieceTypes = {CENTAUR, QUEEN, ROOK, BISHOP, KNIGHT}; + return v; + } + Variant* jesonmor_variant() { + Variant* v = fairy_variant_base(); + v->maxRank = RANK_9; + v->maxFile = FILE_I; + v->reset_pieces(); + v->add_piece(KNIGHT, 'n'); + v->startFen = "nnnnnnnnn/9/9/9/9/9/9/9/NNNNNNNNN w - - 0 1"; + v->promotionPieceTypes = {}; + v->doubleStep = false; + v->castling = false; + v->stalemateValue = -VALUE_MATE; + v->flagPiece = KNIGHT; + v->whiteFlag = make_bitboard(SQ_E5); + v->blackFlag = make_bitboard(SQ_E5); + v->flagMove = true; + return v; + } + Variant* courier_variant() { + Variant* v = fairy_variant_base(); + v->maxRank = RANK_8; + v->maxFile = FILE_L; + v->remove_piece(QUEEN); + v->add_piece(ALFIL, 'e'); + v->add_piece(FERS, 'f'); + v->add_piece(COMMONER, 'm'); + v->add_piece(WAZIR, 'w'); + v->startFen = "rnebmk1wbenr/1ppppp1pppp1/6f5/p5p4p/P5P4P/6F5/1PPPPP1PPPP1/RNEBMK1WBENR w - - 0 1"; + v->promotionPieceTypes = {FERS}; + v->doubleStep = false; + v->castling = false; + v->extinctionValue = -VALUE_MATE; + v->extinctionClaim = true; + v->extinctionPieceTypes = {ALL_PIECES}; + v->extinctionPieceCount = 1; + v->extinctionOpponentPieceCount = 2; + v->stalemateValue = -VALUE_MATE; + return v; + } + Variant* grand_variant() { + Variant* v = fairy_variant_base(); + v->pieceToCharTable = "PNBRQ..AC............Kpnbrq..ac............k"; + v->maxRank = RANK_10; + v->maxFile = FILE_J; + v->add_piece(ARCHBISHOP, 'a'); + v->add_piece(CHANCELLOR, 'c'); + v->startFen = "r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R w - - 0 1"; + v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; + v->promotionRank = RANK_8; + v->promotionLimit[ARCHBISHOP] = 1; + v->promotionLimit[CHANCELLOR] = 1; + v->promotionLimit[QUEEN] = 1; + v->promotionLimit[ROOK] = 2; + v->promotionLimit[BISHOP] = 2; + v->promotionLimit[KNIGHT] = 2; + v->mandatoryPawnPromotion = false; + v->immobilityIllegal = true; + v->doubleStepRank = RANK_3; + v->doubleStepRankMin = RANK_3; + v->castling = false; + return v; + } + Variant* shako_variant() { + Variant* v = fairy_variant_base(); + v->pieceToCharTable = "PNBRQ.E....C.........Kpnbrq.e....c.........k"; + v->maxRank = RANK_10; + v->maxFile = FILE_J; + v->add_piece(FERS_ALFIL, 'e'); + v->add_piece(CANNON, 'c'); + v->startFen = "c8c/ernbqkbnre/pppppppppp/10/10/10/10/PPPPPPPPPP/ERNBQKBNRE/C8C w KQkq - 0 1"; + v->promotionPieceTypes = { QUEEN, ROOK, BISHOP, KNIGHT, CANNON, FERS_ALFIL }; + v->promotionRank = RANK_10; + v->castlingKingsideFile = FILE_H; + v->castlingQueensideFile = FILE_D; + v->castlingRank = RANK_2; + v->doubleStepRank = RANK_3; + v->doubleStepRankMin = RANK_3; + return v; + } + Variant* clobber10_variant() { + Variant* v = clobber_variant(); + v->maxRank = RANK_10; + v->maxFile = FILE_J; + v->startFen = "PpPpPpPpPp/pPpPpPpPpP/PpPpPpPpPp/pPpPpPpPpP/PpPpPpPpPp/" + "pPpPpPpPpP/PpPpPpPpPp/pPpPpPpPpP/PpPpPpPpPp/pPpPpPpPpP w 0 1"; + return v; + } +#ifdef ALLVARS + // Game of the Amazons + // https://en.wikipedia.org/wiki/Game_of_the_Amazons + Variant* amazons_variant() { + Variant* v = fairy_variant_base(); + v->pieceToCharTable = "P...Q.................p...q................."; + v->maxRank = RANK_10; + v->maxFile = FILE_J; + v->reset_pieces(); + v->add_piece(QUIET_QUEEN, 'q'); + v->add_piece(IMMOBILE_PIECE, 'p'); + v->startFen = "3q2q3/10/10/q8q/10/10/Q8Q/10/10/3Q2Q3[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppppppppppppppppp] w - - 0 1"; + v->stalemateValue = -VALUE_MATE; + v->arrowGating = true; + return v; + } +#endif + Variant* xiangqi_variant() { + Variant* v = minixiangqi_variant(); + v->pieceToCharTable = "PN.R.AB..K.C..........pn.r.ab..k.c.........."; + v->maxRank = RANK_10; + v->maxFile = FILE_I; + v->add_piece(ELEPHANT, 'b', 'e'); + v->add_piece(FERS, 'a'); + v->startFen = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1"; + v->mobilityRegion[WHITE][KING] = (Rank1BB | Rank2BB | Rank3BB) & (FileDBB | FileEBB | FileFBB); + v->mobilityRegion[BLACK][KING] = (Rank8BB | Rank9BB | Rank10BB) & (FileDBB | FileEBB | FileFBB); + v->mobilityRegion[WHITE][FERS] = v->mobilityRegion[WHITE][KING]; + v->mobilityRegion[BLACK][FERS] = v->mobilityRegion[BLACK][KING]; + v->mobilityRegion[WHITE][ELEPHANT] = Rank1BB | Rank2BB | Rank3BB | Rank4BB | Rank5BB; + v->mobilityRegion[BLACK][ELEPHANT] = Rank6BB | Rank7BB | Rank8BB | Rank9BB | Rank10BB; + v->soldierPromotionRank = RANK_6; + return v; + } + // Manchu/Yitong chess + // https://en.wikipedia.org/wiki/Manchu_chess + Variant* manchu_variant() { + Variant* v = xiangqi_variant(); + v->pieceToCharTable = "PN.R.AB..K.C....M.....pn.r.ab..k.c.........."; + v->add_piece(BANNER, 'm'); + v->startFen = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/9/9/M1BAKAB2 w - - 0 1"; + return v; + } + // Supply chess + // https://en.wikipedia.org/wiki/Xiangqi#Variations + Variant* supply_variant() { + Variant* v = xiangqi_variant(); + v->variantTemplate = "bughouse"; + v->startFen = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR[] w - - 0 1"; + v->twoBoards = true; + v->pieceDrops = true; + v->dropChecks = false; + v->whiteDropRegion = v->mobilityRegion[WHITE][ELEPHANT]; + v->blackDropRegion = v->mobilityRegion[BLACK][ELEPHANT]; + return v; + } + // Janggi (Korean chess) + // https://en.wikipedia.org/wiki/Janggi + // Official tournament rules with bikjang and material counting. + Variant* janggi_variant() { + Variant* v = xiangqi_variant(); + v->variantTemplate = "janggi"; + v->pieceToCharTable = ".N.R.AB.P..C.........K.n.r.ab.p..c.........k"; + v->remove_piece(FERS); + v->remove_piece(CANNON); + v->remove_piece(ELEPHANT); + v->add_piece(WAZIR, 'a'); + v->add_piece(JANGGI_CANNON, 'c'); + v->add_piece(JANGGI_ELEPHANT, 'b', 'e'); + v->startFen = "rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR w - - 0 1"; + v->mobilityRegion[WHITE][WAZIR] = v->mobilityRegion[WHITE][KING]; + v->mobilityRegion[BLACK][WAZIR] = v->mobilityRegion[BLACK][KING]; + v->soldierPromotionRank = RANK_1; + v->flyingGeneral = false; + v->bikjangRule = true; + v->materialCounting = JANGGI_MATERIAL; + v->diagonalLines = make_bitboard(SQ_D1, SQ_F1, SQ_E2, SQ_D3, SQ_F3, + SQ_D8, SQ_F8, SQ_E9, SQ_D10, SQ_F10); + v->pass = true; + v->nFoldValue = VALUE_DRAW; + v->perpetualCheckIllegal = true; + return v; + } + // Traditional rules of Janggi, where bikjang is a draw + Variant* janggi_traditional_variant() { + Variant* v = janggi_variant(); + v->bikjangRule = true; + v->materialCounting = NO_MATERIAL_COUNTING; + return v; + } + // Modern rules of Janggi, where bikjang is not considered, but material counting is. + // The repetition rules are also adjusted for better compatibility with Kakao Janggi. + Variant* janggi_modern_variant() { + Variant* v = janggi_variant(); + v->bikjangRule = false; + v->materialCounting = JANGGI_MATERIAL; + v->moveRepetitionIllegal = true; + v->nFoldRule = 4; // avoid nFold being triggered before move repetition + v->nMoveRule = 100; // avoid adjudication before reaching 200 half-moves + return v; + } + // Casual rules of Janggi, where bikjang and material counting are not considered + Variant* janggi_casual_variant() { + Variant* v = janggi_variant(); + v->bikjangRule = false; + v->materialCounting = NO_MATERIAL_COUNTING; + return v; + } +#endif + +} // namespace + + +/// VariantMap::init() is called at startup to initialize all predefined variants + +void VariantMap::init() { + // Add to UCI_Variant option + add("chess", chess_variant()->conclude()); + add("normal", chess_variant()->conclude()); + add("fischerandom", chess960_variant()->conclude()); + add("nocastle", nocastle_variant()->conclude()); + add("armageddon", armageddon_variant()->conclude()); + add("fairy", fairy_variant()->conclude()); // fairy variant used for endgame code initialization + add("makruk", makruk_variant()->conclude()); + add("makpong", makpong_variant()->conclude()); + add("cambodian", cambodian_variant()->conclude()); + add("karouk", karouk_variant()->conclude()); + add("asean", asean_variant()->conclude()); + add("ai-wok", aiwok_variant()->conclude()); + add("shatranj", shatranj_variant()->conclude()); + add("chaturanga", chaturanga_variant()->conclude()); + add("amazon", amazon_variant()->conclude()); + add("hoppelpoppel", hoppelpoppel_variant()->conclude()); + add("newzealand", newzealand_variant()->conclude()); + add("kingofthehill", kingofthehill_variant()->conclude()); + add("racingkings", racingkings_variant()->conclude()); + add("knightmate", knightmate_variant()->conclude()); + add("losers", losers_variant()->conclude()); + add("giveaway", giveaway_variant()->conclude()); + add("antichess", antichess_variant()->conclude()); + add("suicide", suicide_variant()->conclude()); + add("codrus", codrus_variant()->conclude()); + add("extinction", extinction_variant()->conclude()); + add("kinglet", kinglet_variant()->conclude()); + add("threekings", threekings_variant()->conclude()); + add("horde", horde_variant()->conclude()); + add("nocheckatomic", nocheckatomic_variant()->conclude()); + add("atomic", atomic_variant()->conclude()); + add("3check", threecheck_variant()->conclude()); + add("5check", fivecheck_variant()->conclude()); + add("crazyhouse", crazyhouse_variant()->conclude()); + add("loop", loop_variant()->conclude()); + add("chessgi", chessgi_variant()->conclude()); + add("bughouse", bughouse_variant()->conclude()); + add("koedem", koedem_variant()->conclude()); + add("pocketknight", pocketknight_variant()->conclude()); + add("placement", placement_variant()->conclude()); + add("sittuyin", sittuyin_variant()->conclude()); + add("seirawan", seirawan_variant()->conclude()); + add("shouse", shouse_variant()->conclude()); + add("minishogi", minishogi_variant()->conclude()); + add("mini", minishogi_variant()->conclude()); + add("kyotoshogi", kyotoshogi_variant()->conclude()); + add("micro", microshogi_variant()->conclude()); + add("dobutsu", dobutsu_variant()->conclude()); + add("gorogoro", gorogoroshogi_variant()->conclude()); + add("judkins", judkinsshogi_variant()->conclude()); + add("euroshogi", euroshogi_variant()->conclude()); + add("losalamos", losalamos_variant()->conclude()); + add("gardner", gardner_variant()->conclude()); + add("almost", almost_variant()->conclude()); + add("chigorin", chigorin_variant()->conclude()); + add("shatar", shatar_variant()->conclude()); + add("clobber", clobber_variant()->conclude()); + add("breakthrough", breakthrough_variant()->conclude()); + add("ataxx", ataxx_variant()->conclude()); + add("minixiangqi", minixiangqi_variant()->conclude()); +#ifdef LARGEBOARDS + add("shogi", shogi_variant()->conclude()); + add("capablanca", capablanca_variant()->conclude()); + add("capahouse", capahouse_variant()->conclude()); + add("caparandom", caparandom_variant()->conclude()); + add("gothic", gothic_variant()->conclude()); + add("janus", janus_variant()->conclude()); + add("modern", modern_variant()->conclude()); + add("chancellor", chancellor_variant()->conclude()); + add("embassy", embassy_variant()->conclude()); + add("centaur", centaur_variant()->conclude()); + add("jesonmor", jesonmor_variant()->conclude()); + add("courier", courier_variant()->conclude()); + add("grand", grand_variant()->conclude()); + add("shako", shako_variant()->conclude()); + add("clobber10", clobber10_variant()->conclude()); +#ifdef ALLVARS + add("amazons", amazons_variant()->conclude()); +#endif + add("xiangqi", xiangqi_variant()->conclude()); + add("manchu", manchu_variant()->conclude()); + add("supply", supply_variant()->conclude()); + add("janggi", janggi_variant()->conclude()); + add("janggitraditional", janggi_traditional_variant()->conclude()); + add("janggimodern", janggi_modern_variant()->conclude()); + add("janggicasual", janggi_casual_variant()->conclude()); +#endif +} + + +/// VariantMap::parse_istream reads variants from an INI-style configuration input stream. + +template +void VariantMap::parse_istream(std::istream& file) { + std::string variant, variant_template, key, value, input; + while (file.peek() != '[' && std::getline(file, input)) {} + + std::vector varsToErase = {}; + while (file.get() && std::getline(std::getline(file, variant, ']'), input)) + { + // Extract variant template, if specified + if (!std::getline(std::getline(std::stringstream(variant), variant, ':'), variant_template)) + variant_template = ""; + + // Read variant rules + Config attribs = {}; + while (file.peek() != '[' && std::getline(file, input)) + { + std::stringstream ss(input); + if (ss.peek() != '#' && std::getline(std::getline(ss, key, '=') >> std::ws, value) && !key.empty()) + attribs[key.erase(key.find_last_not_of(" ") + 1)] = value; + } + + // Create variant + if (variants.find(variant) != variants.end()) + std::cerr << "Variant '" << variant << "' already exists." << std::endl; + else if (!variant_template.empty() && variants.find(variant_template) == variants.end()) + std::cerr << "Variant template '" << variant_template << "' does not exist." << std::endl; + else + { + if (DoCheck) + std::cerr << "Parsing variant: " << variant << std::endl; + Variant* v = !variant_template.empty() ? VariantParser(attribs).parse(new Variant(*variants.find(variant_template)->second)) + : VariantParser(attribs).parse(); + if (v->maxFile <= FILE_MAX && v->maxRank <= RANK_MAX) + { + add(variant, v->conclude()); + // In order to allow inheritance, we need to temporarily add configured variants + // even when only checking them, but we remove them later after parsing is finished. + if (DoCheck) + varsToErase.push_back(variant); + } + else + delete v; + } + } + // Clean up temporary variants + for (std::string tempVar : varsToErase) + { + delete variants[tempVar]; + variants.erase(tempVar); + } +} + +/// VariantMap::parse reads variants from an INI-style configuration file. + +template +void VariantMap::parse(std::string path) { + if (path.empty() || path == "") + return; + std::ifstream file(path); + if (!file.is_open()) + { + std::cerr << "Unable to open file " << path << std::endl; + return; + } + parse_istream(file); + file.close(); +} + +template void VariantMap::parse(std::string path); +template void VariantMap::parse(std::string path); + +void VariantMap::add(std::string s, const Variant* v) { + insert(std::pair(s, v)); +} + +void VariantMap::clear_all() { + for (auto const& element : *this) + delete element.second; + clear(); +} + +std::vector VariantMap::get_keys() { + std::vector keys; + for (auto const& element : *this) + keys.push_back(element.first); + return keys; +} diff --cc src/variant.h index f6da799,0000000..539f627 mode 100644,000000..100644 --- a/src/variant.h +++ b/src/variant.h @@@ -1,180 -1,0 +1,180 @@@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish - Copyright (C) 2018-2020 Fabian Fichter ++ Copyright (C) 2018-2021 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef VARIANT_H_INCLUDED +#define VARIANT_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "types.h" +#include "bitboard.h" + + +/// Variant struct stores information needed to determine the rules of a variant. + +struct Variant { + std::string variantTemplate = "fairy"; + std::string pieceToCharTable = "-"; + int pocketSize = 0; + Rank maxRank = RANK_8; + File maxFile = FILE_H; + bool chess960 = false; + bool twoBoards = false; + std::set pieceTypes = { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING }; + std::string pieceToChar = " PNBRQ" + std::string(KING - QUEEN - 1, ' ') + "K" + std::string(PIECE_TYPE_NB - KING - 1, ' ') + + " pnbrq" + std::string(KING - QUEEN - 1, ' ') + "k" + std::string(PIECE_TYPE_NB - KING - 1, ' '); + std::string pieceToCharSynonyms = std::string(PIECE_NB, ' '); + std::string startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + Bitboard mobilityRegion[COLOR_NB][PIECE_TYPE_NB] = {}; + Rank promotionRank = RANK_8; + std::set > promotionPieceTypes = { QUEEN, ROOK, BISHOP, KNIGHT }; + bool sittuyinPromotion = false; + int promotionLimit[PIECE_TYPE_NB] = {}; // 0 means unlimited + PieceType promotedPieceType[PIECE_TYPE_NB] = {}; + bool piecePromotionOnCapture = false; + bool mandatoryPawnPromotion = true; + bool mandatoryPiecePromotion = false; + bool pieceDemotion = false; + bool blastOnCapture = false; + bool endgameEval = false; + bool doubleStep = true; + Rank doubleStepRank = RANK_2; + Rank doubleStepRankMin = RANK_2; + Bitboard enPassantRegion = AllSquares; + bool castling = true; + bool castlingDroppedPiece = false; + File castlingKingsideFile = FILE_G; + File castlingQueensideFile = FILE_C; + Rank castlingRank = RANK_1; + PieceType castlingRookPiece = ROOK; + PieceType kingType = KING; + bool checking = true; + bool dropChecks = true; + bool mustCapture = false; + bool mustDrop = false; + PieceType mustDropType = ALL_PIECES; + bool pieceDrops = false; + bool dropLoop = false; + bool capturesToHand = false; + bool firstRankPawnDrops = false; + bool promotionZonePawnDrops = false; + bool dropOnTop = false; + EnclosingRule enclosingDrop = NO_ENCLOSING; + Bitboard enclosingDropStart = 0; + Bitboard whiteDropRegion = AllSquares; + Bitboard blackDropRegion = AllSquares; + bool sittuyinRookDrop = false; + bool dropOppositeColoredBishop = false; + bool dropPromoted = false; + bool shogiDoubledPawn = true; + bool immobilityIllegal = false; + bool gating = false; + bool arrowGating = false; + bool seirawanGating = false; + bool cambodianMoves = false; + Bitboard diagonalLines = 0; + bool pass = false; + bool passOnStalemate = false; + bool makpongRule = false; + bool flyingGeneral = false; + Rank soldierPromotionRank = RANK_1; + EnclosingRule flipEnclosedPieces = NO_ENCLOSING; + // game end + int nMoveRule = 50; + int nFoldRule = 3; + Value nFoldValue = VALUE_DRAW; + bool nFoldValueAbsolute = false; + bool perpetualCheckIllegal = false; + bool moveRepetitionIllegal = false; + Value stalemateValue = VALUE_DRAW; + bool stalematePieceCount = false; // multiply stalemate value by sign(count(~stm) - count(stm)) + Value checkmateValue = -VALUE_MATE; + bool shogiPawnDropMateIllegal = false; + bool shatarMateRule = false; + bool bikjangRule = false; + Value extinctionValue = VALUE_NONE; + bool extinctionClaim = false; + bool extinctionPseudoRoyal = false; // TODO: implementation incomplete + std::set extinctionPieceTypes = {}; + int extinctionPieceCount = 0; + int extinctionOpponentPieceCount = 0; + PieceType flagPiece = NO_PIECE_TYPE; + Bitboard whiteFlag = 0; + Bitboard blackFlag = 0; + bool flagMove = false; + bool checkCounting = false; + int connectN = 0; + MaterialCounting materialCounting = NO_MATERIAL_COUNTING; + CountingRule countingRule = NO_COUNTING; + + // Derived properties + bool isFairy = true; + bool isRestricted = true; + + void add_piece(PieceType pt, char c, char c2 = ' ') { + pieceToChar[make_piece(WHITE, pt)] = toupper(c); + pieceToChar[make_piece(BLACK, pt)] = tolower(c); + pieceToCharSynonyms[make_piece(WHITE, pt)] = toupper(c2); + pieceToCharSynonyms[make_piece(BLACK, pt)] = tolower(c2); + pieceTypes.insert(pt); + } + + void remove_piece(PieceType pt) { + pieceToChar[make_piece(WHITE, pt)] = ' '; + pieceToChar[make_piece(BLACK, pt)] = ' '; + pieceToCharSynonyms[make_piece(WHITE, pt)] = ' '; + pieceToCharSynonyms[make_piece(BLACK, pt)] = ' '; + pieceTypes.erase(pt); + } + + void reset_pieces() { + pieceToChar = std::string(PIECE_NB, ' '); + pieceToCharSynonyms = std::string(PIECE_NB, ' '); + pieceTypes.clear(); + } + + // Pre-calculate derived properties + Variant* conclude() { + isFairy = std::any_of(pieceTypes.begin(), pieceTypes.end(), [](PieceType pt) { return pt >= FAIRY_PIECES && pt < KING; }); + isRestricted = std::any_of(pieceTypes.begin(), pieceTypes.end(), + [this](PieceType pt) { + return mobilityRegion[WHITE][pt] || mobilityRegion[BLACK][pt]; + }); + return this; + } +}; + +class VariantMap : public std::map { +public: + void init(); + template void parse(std::string path); + template void parse_istream(std::istream& file); + void clear_all(); + std::vector get_keys(); + +private: + void add(std::string s, const Variant* v); +}; + +extern VariantMap variants; + +#endif // #ifndef VARIANT_H_INCLUDED diff --cc src/variants.ini index 7375492,0000000..5087ec3 mode 100644,000000..100644 --- a/src/variants.ini +++ b/src/variants.ini @@@ -1,405 -1,0 +1,405 @@@ +# Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish - # Copyright (C) 2018-2020 Fabian Fichter ++# Copyright (C) 2018-2021 Fabian Fichter +# +# Fairy-Stockfish is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Fairy-Stockfish is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This is a configuration file to add user-defined variants to Fairy-Stockfish. + +################################################ +### Usage: +# Add "load" and the file path to the SF call (e.g., "./stockfish load variants.ini") +# or set the UCI option "VariantPath" to the path of this file in order to load it. +# In order to validate the configuration without actually loading the variants +# run "./stockfish check variants.ini", which reports potential config errors. + +################################################ +### Variant configuration: +# The variant name needs to be specified as a section in square brackets, +# followed by its rule configurations as key-value pairs as described below. +# If you encounter problems configuring variants, please report them at: +# https://github.com/ianfab/Fairy-Stockfish/issues + +### Inheritance +# If a variant is similar to a previously defined variant, +# inheritance can be used to simplify the definition. To inherit from the +# configuration of an existing variant, specify the parent variant after the child +# variant name separated by a colon, e.g., [gothic:capablanca]. +# When inheritance is used, only the differences to the parent variant need to be defined, +# see the examples in this file, e.g., 3check-crazyhouse. +# When no inheritance is used, the default template applies, +# which is basically standard chess but without any predefined pieces. + +### Piece types +# Firstly, the piece types for a variant need to be defined. +# For that, specify the letter used for each piece type, e.g.: +# pawn = p +# +# See the list below for all available piece types: +# pawn +# knight +# bishop +# rook +# queen +# fers +# alfil +# fersAlfil (=fers+alfil) +# silver +# aiwok (=rook+knight+fers) +# bers (=rook+fers) +# archbishop (=bishop+knight) +# chancellor (=rook+knight) +# amazon (=queen+knight) +# knibis +# biskni +# kniroo +# rookni +# shogiPawn +# lance +# shogiKnight +# euroshogiKnight +# gold +# dragonHorse +# clobber +# breakthrough +# immobile (piece without moves) +# ataxx (mDNA) +# quietQueen +# cannon +# janggiCannon +# soldier +# horse +# elephant +# janggiElephant +# banner (=rook+cannon+horse) +# wazir +# commoner (non-royal king) +# centaur (=knight+commoner) +# king + +### Option types +# [bool]: boolean flag to enable/disable a feature [true, false] +# [Rank]: denotes a rank of the board [1-10] +# [File]: denotes a file of the board [1-12, a-i] +# [int]: any natural number [0, 1, ...] +# [PieceType]: a piece type [letters defined for pieces, e.g., p] +# [Bitboard]: list of squares [e.g., d4 e4 d5 e5]. * can be used as wildcard for files (e.g., *1 is the first rank) +# [Value]: game result for the side to move [win, loss, draw] +# [MaterialCounting]: material couting rules for adjudication [janggi, unweighted, whitedrawodds, blackdrawodds, none] +# [CountingRule]: makruk or ASEAN counting rules [makruk, asean, none] +# [EnclosingRule]: reversi or ataxx enclosing rules [reversi, ataxx, none] + +### Rule definition options +# variantTemplate: only relevant for usage in XBoard/WinBoard GUI [values: fairy, shogi] (default: fairy) +# pieceToCharTable: mapping of piece characters to images for XBoard/WinBoard GUI (default: -) +# pocketSize: number of pockets shown by XBoard/WinBoard for drop variants [int] (default: 0) +# maxRank: maximum rank [Rank] (default: 8) +# maxFile: maximum file [File] (default: 8) +# chess960: allow chess960 castling [bool] (default: false) +# twoBoards: the game is influenced by a second board (e.g., bughouse) [bool] (default: false) +# startFen: FEN of starting position (default: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1) +# mobilityRegion: the mobility area can be defined via options specific to color and piece, +# .e.g., mobilityRegionWhiteRook, mobilityRegionBlackJanggiElephant, etc. [Bitboard] +# promotionRank: relative rank required to reach for promotion [Rank] (default: 8) +# promotionPieceTypes: pawn promotion options using their one-letter representations (default: nbrq) +# sittuyinPromotion: enable Sittuyin-style pawn promotion [bool] (default: false) +# promotionLimit: maximum number of pieces of a type, e.g., q:1 r:2 (default: ) +# promotedPieceType: mapping between unpromoted and promoted non-pawn piece types, e.g., p:g s:g (default: ) +# piecePromotionOnCapture: piece promotion only allowed on captures (e.g., micro shogi) [bool] (default: false) +# mandatoryPawnPromotion: pawn promotion is mandatory [bool] (default: true) +# mandatoryPiecePromotion: piece promotion (and demotion if enabled) is mandatory [bool] (default: false) +# pieceDemotion: enable demotion of pieces (e.g., Kyoto shogi) [bool] (default: false) +# blastOnCapture: captures explode all adjacent non-pawn pieces (e.g., atomic chess) [bool] (default: false) +# endgameEval: enable special endgame evaluation (for very chess-like variants only) [bool] (default: false) +# doubleStep: enable pawn double step [bool] (default: true) +# doubleStepRank: relative rank from where pawn double steps are allowed [Rank] (default: 2) +# doubleStepRankMin: earlist relative rank from where pawn double steps are allowed [Rank] (default: 2) +# enPassantRegion: define region (target squares) where en passant is allowed after double steps [Bitboard] +# castling: enable castling [bool] (default: true) +# castlingDroppedPiece: enable castling with dropped rooks/kings [bool] (default: false) +# castlingKingsideFile: destination file of king after kingside castling [File] (default: g) +# castlingQueensideFile: destination file of king after queenside castling [File] (default: c) +# castlingRank: relative rank of castling [Rank] (default: 1) +# castlingRookPiece: piece type that participates in castling [PieceType] (default: r) +# kingType: piece type defining moves of king/royal piece (default: k) +# checking: allow checks [bool] (default: true) +# dropChecks: allow checks by piece drops [bool] (default: true) +# mustCapture: captures are mandatory (check evasion still takes precedence) [bool] (default: false) +# mustDrop: drops are mandatory (e.g., for Sittuyin setup phase) [bool] (default: false) +# mustDropType: piece type for which piece drops are mandatory [PieceType] (default: *) +# pieceDrops: enable piece drops [bool] (default: false) +# dropLoop: captures promoted pieces are not demoted [bool] (default: false) +# capturesToHand: captured pieces are go to opponent's hand [bool] (default: false) +# firstRankPawnDrops: allow pawn drops to first rank [bool] (default: false) +# promotionZonePawnDrops: allow pawn drops in promotion zone [bool] (default: false) +# dropOnTop: piece drops need to be on top of pieces on board (e.g., for connect4) [bool] (default: false) +# enclosingDrop: require piece drop to enclose pieces [EnclosingRule] (default: none) +# enclosingDropStart: drop region for starting phase disregarding enclosingDrop (e.g., for reversi) [Bitboard] +# whiteDropRegion: restrict region for piece drops of all white pieces [Bitboard] +# blackDropRegion: restrict region for piece drops of all black pieces [Bitboard] +# sittuyinRookDrop: restrict region of rook drops to first rank [bool] (default: false) +# dropOppositeColoredBishop: dropped bishops have to be on opposite-colored squares [bool] (default: false) +# dropPromoted: pieces may be dropped in promoted state [bool] (default: false) +# shogiDoubledPawn: allow shogi pawns to be doubled [bool] (default: true) +# immobilityIllegal: pieces may not move to squares where they can never move from [bool] (default: false) +# gating: maintain squares on backrank with extra rights in castling field of FEN [bool] (default: false) +# arrowGating: allow gating in Game of the Amazons style [bool] (default: false) +# seirawanGating: allow gating of pieces in hand like in S-Chess, requires "gating = true" [bool] (default: false) +# cambodianMoves: enable special moves of cambodian chess, requires "gating = true" [bool] (default: false) +# diagonalLines: enable special moves along diagonal for specific squares (Janggi) [Bitboard] +# pass: allow passing [bool] (default: false) +# passOnStalemate: allow passing in case of stalemate [bool] (default: false) +# makpongRule: the king may not move away from check [bool] (default: false) +# flyingGeneral: disallow general face-off like in xiangqi [bool] (default: false) +# soldierPromotionRank: restrict soldier to shogi pawn movements until reaching n-th rank [bool] (default: 1) +# flipEnclosedPieces: change color of pieces that are enclosed by a drop [EnclosingRule] (default: none) +# nMoveRule: move count for 50/n-move rule [int] (default: 50) +# nFoldRule: move count for 3/n-fold repetition rule [int] (default: 3) +# nFoldValue: result in case of 3/n-fold repetition [Value] (default: draw) +# nFoldValueAbsolute: result in case of 3/n-fold repetition is from white's point of view [bool] (default: false) +# perpetualCheckIllegal: prohibit perpetual checks [bool] (default: false) +# moveRepetitionIllegal: prohibit moving back and forth with the same piece nFoldRule-1 times [bool] (default: false) +# stalemateValue: result in case of stalemate [Value] (default: draw) +# stalematePieceCount: count material in case of stalemate [bool] (default: false) +# checkmateValue: result in case of checkmate [Value] (default: loss) +# shogiPawnDropMateIllegal: prohibit checkmate via shogi pawn drops [bool] (default: false) +# shatarMateRule: enable shatar mating rules [bool] (default: false) +# bikjangRule: consider Janggi bikjang (facing kings) rule [bool] (default: false) +# extinctionValue: result when one of extinctionPieceTypes is extinct [Value] (default: none) +# extinctionClaim: extinction of opponent pieces can only be claimed as side to move [bool] (default: false) +# extinctionPieceTypes: list of piece types for extinction rules, e.g., pnbrq (* means all) (default: ) +# extinctionPieceCount: piece count at which the game is decided by extinction rule (default: 0) +# extinctionOpponentPieceCount: opponent piece count required to adjudicate by extinction rule (default: 0) +# flagPiece: piece type for capture the flag win rule [PieceType] (default: -) +# whiteFlag: white's target region for capture the flag win rule [Bitboard] (default: ) +# blackFlag: black's target region for capture the flag win rule [Bitboard] (default: ) +# flagMove: black gets one more move after white captures the flag [bool] (default: false) +# checkCounting: enable check count win rule (check count is communicated via FEN, see 3check) [bool] (default: false) +# connectN: number of aligned pieces for win [int] (default: 0) +# materialCounting: enable material counting rules [MaterialCounting] (default: none) +# countingRule: enable counting rules [CountingRule] (default: none) + +################################################ +### Example for minishogi configuration that would be equivalent to the built-in variant: + +# [minishogi] +# variantTemplate = shogi +# maxRank = 5 +# maxFile = 5 +# shogiPawn = p +# silver = s +# gold = g +# bishop = b +# dragonHorse = h +# rook = r +# bers = d +# king = k +# startFen = rbsgk/4p/5/P4/KGSBR[-] w 0 1 +# pieceDrops = true +# capturesToHand = true +# promotionRank = 5 +# doubleStep = false +# castling = false +# promotedPieceType = p:g s:g b:h r:d +# shogiDoubledPawn = false +# immobilityIllegal = true +# shogiPawnDropMateIllegal = true +# stalemateValue = loss +# nFoldRule = 4 +# nMoveRule = 0 +# perpetualCheckIllegal = true +# pocketSize = 5 +# nFoldValue = loss +# nFoldValueAbsolute = true + +# Hybrid variant of three-check chess and crazyhouse, using crazyhouse as a template +[3check-crazyhouse:crazyhouse] +startFen = rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1 +checkCounting = true + +# Crazyhouse with mandatory captures, using crazyhouse as a template +[coffeehouse:crazyhouse] +mustCapture = true + +# Hybrid variant of makruk and crazyhouse +[makrukhouse:makruk] +startFen = rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR[] w - - 0 1 +pieceDrops = true +capturesToHand = true + +# Hybrid variant of xiangqi and crazyhouse +[xiangqihouse:xiangqi] +startFen = rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR[] w - - 0 1 +pieceDrops = true +capturesToHand = true +dropChecks = false +whiteDropRegion = *1 *2 *3 *4 *5 +blackDropRegion = *6 *7 *8 *9 *10 + +# Hybrid variant of janggi and crazyhouse +[janggihouse:janggi] +startFen = rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR[] w - - 0 1 +pieceDrops = true +capturesToHand = true + +# Hybrid variant of antichess and losalamos +[anti-losalamos:losalamos] +king = - +commoner = k +promotionPieceTypes = nrqk +mustCapture = true +stalemateValue = win +extinctionValue = win +extinctionPieceTypes = * + +# Indian great chess +# https://www.chessvariants.com/historic.dir/indiangr1.html +[indiangreat] +pieceToCharTable = PNBRQ..VW.........G..Kpnbrq..vw.........g..k +pawn = p +knight = n +bishop = b +rook = r +queen = q +king = k +archbishop = v +chancellor = w +amazon = g +maxRank = 10 +maxFile = 10 +startFen = rnbqkgvbnr/ppppwwpppp/4pp4/10/10/10/10/4PP4/PPPPWWPPPP/RNBVGKQBNR w - - 0 1 +promotionRank = 10 +promotionPieceTypes = q +doubleStep = false +castling = false + +# Upside-down +[upsidedown:chess] +startFen = RNBKQBNR/PPPPPPPP/8/8/8/8/pppppppp/rnbkqbnr w - - 0 1 + +# Peasant revolt +# https://www.chessvariants.com/large.dir/peasantrevolt.html +[peasant:chess] +startFen = 1nn1k1n1/4p3/8/8/8/8/PPPPPPPP/4K3 w - - 0 1 + +# https://www.chessvariants.com/unequal.dir/weak.html +[weak:chess] +startFen = nnnnknnn/pppppppp/2p2p2/1pppppp1/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1 + +[semitorpedo:chess] +doubleStepRank = 3 + +# This variant is similar to Capablanca Chess, but with two archbishops and no chancellor piece. +[gemini:janus] +startFen = rnbaqkabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBAQKABNR w KQkq - 0 1 +archbishop = a +pieceToCharTable = PNBRQ............A...Kpnbrq............a...k +castlingKingsideFile = i +castlingQueensideFile = c + +# https://www.chessvariants.com/diffsetup.dir/pawnsonly.html +[pawnsonly] +pawn = p +queen = q +startFen = 8/pppppppp/8/8/8/8/PPPPPPPP/8 w - - 0 1 +promotionPieceTypes = q +castling = false +stalemateValue = loss +flagPiece = q +whiteFlag = *8 +blackFlag = *1 + +[tictactoe] +maxRank = 3 +maxFile = 3 +immobile = p +startFen = 3/3/3[PPPPPpppp] w - - 0 1 +pieceDrops = true +doubleStep = false +castling = false +stalemateValue = draw +immobilityIllegal = false +connectN = 3 + +[cfour] +maxRank = 6 +maxFile = 7 +immobile = p +startFen = 7/7/7/7/7/7[PPPPPPPPPPPPPPPPPPPPPppppppppppppppppppppp] w - - 0 1 +pieceDrops = true +dropOnTop = true +doubleStep = false +castling = false +stalemateValue = draw +immobilityIllegal = false +connectN = 4 +nMoveRule = 0 + +[flipersi] +immobile = p +startFen = 8/8/8/8/8/8/8/8[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppp] w 0 1 +pieceDrops = true +promotionPieceTypes = - +doubleStep = false +castling = false +stalemateValue = loss +stalematePieceCount = true +materialCounting = unweighted +enclosingDrop = reversi +enclosingDropStart = d4 e4 d5 e5 +immobilityIllegal = false +flipEnclosedPieces = reversi +passOnStalemate = false + +[flipello:flipersi] +startFen = 8/8/8/3pP3/3Pp3/8/8/8[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppp] w 0 1 +passOnStalemate = true + +[grandhouse:grand] +startFen = r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R[] w - - 0 1 +pieceDrops = true +capturesToHand = true + +[shogun:crazyhouse] +variantTemplate = shogi +pieceToCharTable = PNBR.F.....++++.+Kpnbr.f.....++++.+k +pocketSize = 8 +startFen = rnb+fkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB+FKBNR[] w KQkq - 0 1 +commoner = c +centaur = g +archbishop = a +chancellor = m +fers = f +promotionRank = 6 +promotionLimit = g:1 a:1 m:1 q:1 +promotionPieceTypes = - +promotedPieceType = p:c n:g b:a r:m f:q +mandatoryPawnPromotion = false +firstRankPawnDrops = true +promotionZonePawnDrops = true +whiteDropRegion = *1 *2 *3 *4 *5 +blackDropRegion = *4 *5 *6 *7 *8 +immobilityIllegal = true + +# Asymmetric variant with one army using pieces that move like knights but attack like other pieces (kniroo and knibis) +[orda:chess] +pieceToCharTable = PNBRQ..AH...........LKp...q..ah.y.........lk +centaur = h +knibis = a +kniroo = l +silver = y +promotionPieceTypes = qh +startFen = lhaykahl/8/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1 +flagPiece = k +whiteFlag = *8 +blackFlag = *1 diff --cc src/xboard.cpp index 4f9619a,0000000..f88ce58 mode 100644,000000..100644 --- a/src/xboard.cpp +++ b/src/xboard.cpp @@@ -1,481 -1,0 +1,481 @@@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish - Copyright (C) 2018-2020 Fabian Fichter ++ Copyright (C) 2018-2021 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "evaluate.h" +#include "misc.h" +#include "partner.h" +#include "search.h" +#include "thread.h" +#include "types.h" +#include "uci.h" +#include "xboard.h" + +namespace { + + const Search::LimitsType analysisLimits = []{ + Search::LimitsType limits; + limits.infinite = 1; + return limits; + }(); + +} // namespace + +namespace XBoard { + + StateMachine* stateMachine = nullptr; + + // go() starts the search for game play, analysis, or perft. + + void StateMachine::go(Search::LimitsType searchLimits, bool ponder) { + + searchLimits.startTime = now(); // As early as possible! + + Threads.start_thinking(pos, states, searchLimits, ponder); + } + + // ponder() starts a ponder search + + void StateMachine::ponder() { + + sync_cout << "Hint: " << UCI::move(pos, ponderMove) << sync_endl; + ponderHighlight = highlight(UCI::square(pos, from_sq(ponderMove))); + do_move(ponderMove); + ponderMove = MOVE_NONE; + go(limits, true); + } + + // stop() stops an ongoing search (if any) + // and does not print/apply a move if aborted + + void StateMachine::stop(bool abort) { + + if (abort) + Threads.abort = true; + Threads.stop = true; + Threads.main()->wait_for_search_finished(); + // Ensure that current position does not get out of sync with GUI + if (Threads.main()->ponder) + { + assert(moveList.size()); + undo_move(); + Threads.main()->ponder = false; + } + } + + // setboard() is called when engine receives the "setboard" XBoard command. + + void StateMachine::setboard(std::string fen) { + + if (fen.empty()) + fen = variants.find(Options["UCI_Variant"])->second->startFen; + + states = StateListPtr(new std::deque(1)); // Drop old and create a new one + moveList.clear(); + pos.set(variants.find(Options["UCI_Variant"])->second, fen, Options["UCI_Chess960"], &states->back(), Threads.main()); + } + + // do_move() is called when engine needs to apply a move when using XBoard protocol. + + void StateMachine::do_move(Move m) { + + // transfer states back + if (Threads.setupStates.get()) + states = std::move(Threads.setupStates); + + if (m == MOVE_NONE) + return; + moveList.push_back(m); + states->emplace_back(); + pos.do_move(m, states->back()); + } + + // undo_move() is called when the engine receives the undo command in XBoard protocol. + + void StateMachine::undo_move() { + + // transfer states back + if (Threads.setupStates.get()) + states = std::move(Threads.setupStates); + + pos.undo_move(moveList.back()); + states->pop_back(); + moveList.pop_back(); + } + + std::string StateMachine::highlight(std::string square) { + Bitboard promotions = 0, captures = 0, quiets = 0; + // Collect targets + for (const auto& m : MoveList(pos)) + { + Square from = from_sq(m), to = to_sq(m); + if (is_ok(from) && UCI::square(pos, from) == square && !is_pass(m)) + { + if (type_of(m) == PROMOTION) + promotions |= to; + else if (pos.capture(m)) + captures |= to; + else + { + if (type_of(m) == CASTLING && !pos.is_chess960()) + to = make_square(to > from ? pos.castling_kingside_file() + : pos.castling_queenside_file(), rank_of(from)); + quiets |= to; + } + } + } + // Generate color FEN + int emptyCnt; + std::ostringstream ss; + for (Rank r = pos.max_rank(); r >= RANK_1; --r) + { + for (File f = FILE_A; f <= pos.max_file(); ++f) + { + for (emptyCnt = 0; f <= pos.max_file() && !((promotions | captures | quiets) & make_square(f, r)); ++f) + ++emptyCnt; + + if (emptyCnt) + ss << emptyCnt; + + if (f <= pos.max_file()) + ss << (promotions & make_square(f, r) ? "M" : captures & make_square(f, r) ? "R" : "Y"); + } + + if (r > RANK_1) + ss << '/'; + } + return ss.str(); + } + +/// StateMachine::process_command() processes commands of the XBoard protocol. + +void StateMachine::process_command(std::string token, std::istringstream& is) { + if (token == "protover") + { + std::string vars = "chess"; + for (std::string v : variants.get_keys()) + if (v != "chess") + vars += "," + v; + sync_cout << "feature setboard=1 usermove=1 time=1 memory=1 smp=1 colors=0 draw=0 " + << "highlight=1 name=0 sigint=0 ping=1 myname=\"" + << engine_info(false, true) << "\" " << "variants=\"" << vars << "\"" + << Options << sync_endl; + sync_cout << "feature done=1" << sync_endl; + } + else if (token == "accepted" || token == "rejected") {} + else if (token == "hover" || token == "put") {} + else if (token == "lift") + { + if (is >> token) + { + if (Threads.main()->ponder) + { + if (token == UCI::square(pos, from_sq(moveList.back()))) + sync_cout << "highlight " << ponderHighlight << sync_endl; + else + { + Move currentPonderMove = moveList.back(); + stop(); + sync_cout << "highlight " << highlight(token) << sync_endl; + // Restart ponder search with random guess + auto moves = MoveList(pos); + std::vector filteredMoves; + copy_if(moves.begin(), moves.end(), back_inserter(filteredMoves), [&](const Move m) { + return is_ok(from_sq(m)) && UCI::square(pos, from_sq(m)) == token; + }); + if (filteredMoves.size()) + { + static PRNG rng(now()); + ponderMove = filteredMoves.at(rng.rand() % filteredMoves.size()); + } + else + ponderMove = currentPonderMove; + ponder(); + } + } + else + sync_cout << "highlight " << highlight(token) << sync_endl; + } + } + else if (token == "ping") + { + if (!(is >> token)) + token = ""; + sync_cout << "pong " << token << sync_endl; + } + else if (token == "new") + { + stop(); + Search::clear(); + setboard(); + // play second by default + playColor = ~pos.side_to_move(); + Threads.sit = false; + Partner.reset(); + } + else if (token == "variant") + { + stop(); + if (is >> token) + Options["UCI_Variant"] = token; + setboard(); + } + else if (token == "force" || token == "result") + { + stop(); + playColor = COLOR_NB; + } + else if (token == "?") + { + if (!Threads.main()->ponder) + stop(false); + } + else if (token == "go") + { + stop(); + playColor = pos.side_to_move(); + go(limits); + moveAfterSearch = true; + } + else if (token == "level" || token == "st" || token == "sd" || token == "time" || token == "otim") + { + int num; + if (token == "level") + { + // moves to go + is >> limits.movestogo; + // base time + is >> token; + size_t idx = token.find(":"); + if (idx != std::string::npos) + num = std::stoi(token.substr(0, idx)) * 60 + std::stoi(token.substr(idx + 1)); + else + num = std::stoi(token) * 60; + limits.time[WHITE] = num * 1000; + limits.time[BLACK] = num * 1000; + // increment + is >> num; + limits.inc[WHITE] = num * 1000; + limits.inc[BLACK] = num * 1000; + } + else if (token == "sd") + is >> limits.depth; + else if (token == "st") + { + is >> num; + limits.movetime = num * 1000; + limits.time[WHITE] = limits.time[BLACK] = 0; + } + // Note: time/otim are in centi-, not milliseconds + else if (token == "time") + { + is >> num; + Color us = playColor != COLOR_NB ? playColor : pos.side_to_move(); + if (limits.time[us]) + limits.time[us] = num * 10; + } + else if (token == "otim") + { + is >> num; + Color them = playColor != COLOR_NB ? ~playColor : ~pos.side_to_move(); + if (limits.time[them]) + limits.time[them] = num * 10; + } + } + else if (token == "setboard") + { + stop(); + std::string fen; + std::getline(is >> std::ws, fen); + // Check if setboard actually indicates a passing move + // to avoid unnecessarily clearing the move history + if (pos.pass()) + { + StateInfo st; + Position p; + p.set(pos.variant(), fen, pos.is_chess960(), &st, pos.this_thread()); + Move m; + std::string passMove = "@@@@"; + if ((m = UCI::to_move(pos, passMove)) != MOVE_NONE) + do_move(m); + // apply setboard if passing does not lead to a match + if (pos.key() != p.key()) + setboard(fen); + } + else + setboard(fen); + // Winboard sends setboard after passing moves + if (Options["UCI_AnalyseMode"]) + go(analysisLimits); + else if (pos.side_to_move() == playColor) + { + go(limits); + moveAfterSearch = true; + } + } + else if (token == "cores") + { + stop(); + if (is >> token) + Options["Threads"] = token; + } + else if (token == "memory") + { + stop(); + if (is >> token) + Options["Hash"] = token; + } + else if (token == "hard" || token == "easy") + Options["Ponder"] = token == "hard"; + else if (token == "option") + { + std::string name, value; + is.get(); + std::getline(is, name, '='); + std::getline(is, value); + if (Options.count(name)) + { + if (Options[name].get_type() == "check") + value = value == "1" ? "true" : "false"; + Options[name] = value; + } + } + else if (token == "analyze") + { + stop(); + Options["UCI_AnalyseMode"] = std::string("true"); + go(analysisLimits); + } + else if (token == "exit") + { + stop(); + Options["UCI_AnalyseMode"] = std::string("false"); + } + else if (token == "undo") + { + stop(); + if (moveList.size()) + { + undo_move(); + if (Options["UCI_AnalyseMode"]) + go(analysisLimits); + } + } + // Bughouse commands + else if (token == "partner") + Partner.parse_partner(is); + else if (token == "ptell") + { + Partner.parse_ptell(is, pos); + // play move requested by partner + // Partner.moveRequested can only be set if search was successfully aborted + if (moveAfterSearch && Partner.moveRequested) + { + assert(Threads.abort); + stop(); + sync_cout << "move " << UCI::move(pos, Partner.moveRequested) << sync_endl; + do_move(Partner.moveRequested); + moveAfterSearch = false; + Partner.moveRequested = MOVE_NONE; + } + } + else if (token == "holding") + { + stop(); + // holding [] [] + std::string white_holdings, black_holdings; + if ( std::getline(is, token, '[') && std::getline(is, white_holdings, ']') + && std::getline(is, token, '[') && std::getline(is, black_holdings, ']')) + { + std::string fen; + char color, pieceType; + // Use the obtained holding if available to avoid race conditions + if (is >> color && is >> pieceType) + { + fen = pos.fen(); + fen.insert(fen.find(']'), 1, toupper(color) == 'B' ? tolower(pieceType) : toupper(pieceType)); + } + else + { + std::transform(black_holdings.begin(), black_holdings.end(), black_holdings.begin(), ::tolower); + fen = pos.fen(false, false, 0, white_holdings + black_holdings); + } + setboard(fen); + } + // restart search + if (moveAfterSearch) + go(limits); + } + // Additional custom non-XBoard commands + else if (token == "perft") + { + stop(); + Search::LimitsType perft_limits; + is >> perft_limits.perft; + go(perft_limits); + } + else if (token == "d") + sync_cout << pos << sync_endl; + else if (token == "eval") + sync_cout << Eval::trace(pos) << sync_endl; + // Move strings and unknown commands + else + { + bool isMove = false; + + if (token == "usermove") + { + is >> token; + isMove = true; + } + + // Handle pondering + if (Threads.main()->ponder) + { + assert(moveList.size()); + if (token == UCI::move(pos, moveList.back())) + { + // ponderhit + moveAfterSearch = true; + Threads.main()->ponder = false; + return; + } + } + stop(false); + + // Apply move + Move m; + if ((m = UCI::to_move(pos, token)) != MOVE_NONE) + do_move(m); + else + sync_cout << (isMove ? "Illegal move: " : "Error (unknown command): ") << token << sync_endl; + + // Restart search if applicable + if (Options["UCI_AnalyseMode"]) + go(analysisLimits); + else if (pos.side_to_move() == playColor) + { + moveAfterSearch = true; + go(limits); + } + } +} + +} // namespace XBoard diff --cc src/xboard.h index db312ce,0000000..48f7679 mode 100644,000000..100644 --- a/src/xboard.h +++ b/src/xboard.h @@@ -1,67 -1,0 +1,67 @@@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish - Copyright (C) 2018-2020 Fabian Fichter ++ Copyright (C) 2018-2021 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef XBOARD_H_INCLUDED +#define XBOARD_H_INCLUDED + +#include +#include +#include + +#include "types.h" + +class Position; + +namespace XBoard { + +/// StateMachine class maintains the states required by XBoard protocol + +class StateMachine { +public: + StateMachine(Position& uciPos, StateListPtr& uciPosStates) : pos(uciPos), states(uciPosStates) { + moveList = std::deque(); + moveAfterSearch = false; + playColor = COLOR_NB; + ponderMove = MOVE_NONE; + ponderHighlight = ""; + } + void go(Search::LimitsType searchLimits, bool ponder = false); + void ponder(); + void stop(bool abort = true); + void setboard(std::string fen = ""); + void do_move(Move m); + void undo_move(); + std::string highlight(std::string square); + void process_command(std::string token, std::istringstream& is); + bool moveAfterSearch; + Move ponderMove; + +private: + Position& pos; + StateListPtr& states; + std::deque moveList; + Search::LimitsType limits; + Color playColor; + std::string ponderHighlight; +}; + +extern StateMachine* stateMachine; + +} // namespace XBoard + +#endif // #ifndef XBOARD_H_INCLUDED