From: Fabian Fichter Date: Sun, 6 Jun 2021 16:55:34 +0000 (+0200) Subject: Merge official-stockfish/master X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=545154573b0b682129db1123264ad74c1ca76c44;p=fairystockfish.git Merge official-stockfish/master bench: 5392690 --- 545154573b0b682129db1123264ad74c1ca76c44 diff --cc AUTHORS index ee5ccf4,c12b98a..c20626d --- a/AUTHORS +++ b/AUTHORS @@@ -1,20 -1,4 +1,21 @@@ +# Fairy-Stockfish authors + +# Main author +Fabian Fichter (ianfab) + +# Contributors in alphabetical order +alwey ++Belzedar94 +Fulmene +gbtami +QueensGambit +tttak +yoav-rozin +ydirson + + + - # List of authors for Stockfish, as of August 4, 2020 + # List of authors for Stockfish, as of March 31, 2021 # Founders of the Stockfish project and fishtest infrastructure Tord Romstad (romstad) diff --cc src/apiutil.h index b1d3621,0000000..d3c5aa8 mode 100644,000000..100644 --- a/src/apiutil.h +++ b/src/apiutil.h @@@ -1,933 -1,0 +1,933 @@@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + 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 APIUTIL_H_INCLUDED +#define APIUTIL_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "types.h" +#include "position.h" +#include "variant.h" + +namespace Stockfish { + +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, +}; + +inline Notation default_notation(const Variant* v) { + if (v->variantTemplate == "shogi") + return NOTATION_SHOGI_HODGES_NUMBER; + return NOTATION_SAN; +} + +namespace SAN { + +enum Disambiguation { + NO_DISAMBIGUATION, + FILE_DISAMBIGUATION, + RANK_DISAMBIGUATION, + SQUARE_DISAMBIGUATION, +}; + +inline bool is_shogi(Notation n) { + return n == NOTATION_SHOGI_HOSKING || n == NOTATION_SHOGI_HODGES || n == NOTATION_SHOGI_HODGES_NUMBER; +} + +inline 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])); +} + +inline 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))); + } +} + +inline 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); + } +} + +inline 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); + } +} + +inline 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); ++ 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; +} + +inline 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 ""; + } +} + +inline 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; +} + +} // namespace SAN + +inline bool has_insufficient_material(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); + else if (is_custom(pt) && pos.count(c, pt) > 0) + // to be conservative, assume any custom piece has mating potential + return false; + + // 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 || pos.stalemate_value() != VALUE_DRAW || pos.check_counting())) + 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 || pos.check_counting())) + return false; + + return true; +} + +namespace FEN { + +enum FenValidation : int { + FEN_INVALID_COUNTING_RULE = -14, + FEN_INVALID_CHECK_COUNT = -13, + 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 row, int file) : rowIdx(row), fileIdx(file) {} +}; + +inline bool operator==(const CharSquare& s1, const CharSquare& s2) { + return s1.rowIdx == s2.rowIdx && s1.fileIdx == s2.fileIdx; +} + +inline bool operator!=(const CharSquare& s1, const CharSquare& s2) { + return !(s1 == s2); +} + +inline 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 ranks, int files) : nbRanks(ranks), nbFiles(files) { + 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); +}; + +inline 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; +} + +inline 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 && v->pieceToCharSynonyms.find(c) == std::string::npos && validSpecialCharacters.find(c) == std::string::npos) + { + std::cerr << "Invalid piece character: '" << c << "'." << std::endl; + return NOK; + } + } + return OK; +} + +inline 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 +inline 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; +} + +inline 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; +} + +inline 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; +} + +inline 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"; + } +} + +inline 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"; + } +} + +inline Validation check_castling_rank(const std::array& castlingInfoSplitted, const CharBoard& board, const Variant* v) { + + for (Color c : {WHITE, BLACK}) + { + for (char charPiece : {v->pieceToChar[make_piece(c, v->castlingKingPiece)], + v->pieceToChar[make_piece(c, v->castlingRookPiece)]}) + { + if (castlingInfoSplitted[c].size() == 0) + continue; + const Rank castlingRank = relative_rank(c, v->castlingRank, v->maxRank); + if (!board.is_piece_on_rank(charPiece, castlingRank)) + { + std::cerr << "The " << color_to_string(c) << " king and rook must be on rank " << castlingRank << " if castling is enabled for " << color_to_string(c) << "." << std::endl; + return NOK; + } + } + } + return OK; +} + +inline Validation check_standard_castling(std::array& castlingInfoSplitted, const CharBoard& board, + const std::array& kingPositions, const std::array& kingPositionsStart, + const std::array, 2>& rookPositionsStart, const Variant* v) { + + 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 = v->pieceToChar[make_piece(c, v->castlingRookPiece)]; + 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; +} + +inline Validation check_pocket_info(const std::string& fenBoard, int nbRanks, const Variant* v, std::string& pocket) { + + char stopChar; + int offset = 0; + if (std::count(fenBoard.begin(), fenBoard.end(), '/') == nbRanks) + { + // look for last '/' + stopChar = '/'; + } + else if (std::count(fenBoard.begin(), fenBoard.end(), '[') == 1) + { + // pocket is defined as [ and ] + stopChar = '['; + offset = 1; + if (*(fenBoard.end()-1) != ']') + { + std::cerr << "Pocket specification does not end with ']'." << std::endl; + return NOK; + } + } + else + // allow to skip pocket + return OK; + + // 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 && v->pieceToCharSynonyms.find(c) == std::string::npos) + { + std::cerr << "Invalid pocket piece: '" << c << "'." << std::endl; + return NOK; + } + else + pocket += c; + } + } + std::cerr << "Pocket piece closing character '" << stopChar << "' was not found." << std::endl; + return NOK; +} + +inline int piece_count(const std::string& fenBoard, Color c, PieceType pt, const Variant* v) { + return std::count(fenBoard.begin(), fenBoard.end(), v->pieceToChar[make_piece(c, pt)]); +} + +inline Validation check_number_of_kings(const std::string& fenBoard, const std::string& startFenBoard, const Variant* v) { + int nbWhiteKings = piece_count(fenBoard, WHITE, KING, v); + int nbBlackKings = piece_count(fenBoard, BLACK, KING, v); + int nbWhiteKingsStart = piece_count(startFenBoard, WHITE, KING, v); + int nbBlackKingsStart = piece_count(startFenBoard, BLACK, KING, v); + + if (nbWhiteKings != nbWhiteKingsStart) + { + std::cerr << "Invalid number of white kings. Expected: " << nbWhiteKingsStart << ". Given: " << nbWhiteKings << std::endl; + return NOK; + } + if (nbBlackKings != nbBlackKingsStart) + { + std::cerr << "Invalid number of black kings. Expected: " << nbBlackKingsStart << ". Given: " << nbBlackKings << std::endl; + return NOK; + } + return OK; +} + + +inline Validation check_en_passant_square(const std::string& enPassantInfo) { + if (enPassantInfo.size() != 1 || enPassantInfo[0] != '-') + { + if (enPassantInfo.size() != 2) + { + std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 2 characters. Actual: " << enPassantInfo.size() << " character(s)." << std::endl; + return NOK; + } + if (!isalpha(enPassantInfo[0])) + { + std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 1st character to be a letter." << std::endl; + return NOK; + } + if (!isdigit(enPassantInfo[1])) + { + std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 2nd character to be a digit." << std::endl; + return NOK; + } + } + return OK; +} + + +inline Validation check_check_count(const std::string& checkCountInfo) { + if (checkCountInfo.size() != 3) + { + std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 3 characters. Actual: " << checkCountInfo.size() << " character(s)." << std::endl; + return NOK; + } + if (!isdigit(checkCountInfo[0])) + { + std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 1st character to be a digit." << std::endl; + return NOK; + } + if (!isdigit(checkCountInfo[2])) { + std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 3rd character to be a digit." << std::endl; + return NOK; + } + return OK; +} + + +inline Validation check_digit_field(const std::string& field) { + if (field.size() == 1 && field[0] == '-') + return OK; + for (char c : field) + if (!isdigit(c)) + return NOK; + return OK; +} + + +inline FenValidation validate_fen(const std::string& fen, const Variant* v, bool chess960 = false) { + + const std::string validSpecialCharacters = "/+~[]-"; + // 0) Layout + // check for empty fen + if (fen.size() == 0) + { + std::cerr << "Fen is empty." << std::endl; + return FEN_EMPTY; + } + + std::vector fenParts = get_fen_parts(fen, ' '); + std::vector starFenParts = get_fen_parts(v->startFen, ' '); + + // check for number of parts + const unsigned int maxNumberFenParts = 6 + v->checkCounting; + if (fenParts.size() < 1 || fenParts.size() > maxNumberFenParts) + { + std::cerr << "Invalid number of fen parts. Expected: >= 1 and <= " << maxNumberFenParts + << " 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::string pocket = ""; + if (v->pieceDrops || v->seirawanGating || v->arrowGating) + { + if (check_pocket_info(fenParts[0], nbRanks, v, pocket) == NOK) + return FEN_INVALID_POCKET_INFO; + } + + // check for number of kings + if (v->pieceTypes.find(KING) != v->pieceTypes.end()) + { + // we have a royal king in this variant, + // ensure that each side has exactly as many kings as in the starting position + // (variants like giveaway use the COMMONER piece type instead) + if (check_number_of_kings(fenParts[0], starFenParts[0], v) == NOK) + return FEN_INVALID_NUMBER_OF_KINGS; + + // check for touching kings if there are exactly two royal kings on the board (excluding pocket) + if ( v->kingType == KING + && piece_count(fenParts[0], WHITE, KING, v) - piece_count(pocket, WHITE, KING, v) == 1 + && piece_count(fenParts[0], BLACK, KING, v) - piece_count(pocket, BLACK, KING, v) == 1) + { + std::array kingPositions; + kingPositions[WHITE] = board.get_square_for_piece(v->pieceToChar[make_piece(WHITE, KING)]); + kingPositions[BLACK] = board.get_square_for_piece(v->pieceToChar[make_piece(BLACK, KING)]); + if (check_touching_kings(board, kingPositions) == NOK) + return FEN_TOUCHING_KINGS; + } + } + + // 2) Part + // check side to move char + if (fenParts.size() >= 2 && 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; + } + + // Castling and en passant can be skipped + bool skipCastlingAndEp = fenParts.size() >= 4 && fenParts.size() <= 5 && isdigit(fenParts[2][0]); + + // 3) Part + // check castling rights + if (fenParts.size() >= 3 && !skipCastlingAndEp && 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) + { + std::array kingPositions; + kingPositions[WHITE] = board.get_square_for_piece(toupper(v->pieceToChar[v->castlingKingPiece])); + kingPositions[BLACK] = board.get_square_for_piece(tolower(v->pieceToChar[v->castlingKingPiece])); + + CharBoard startBoard(board.get_nb_ranks(), board.get_nb_files()); + fill_char_board(startBoard, v->startFen, validSpecialCharacters, v); + + // skip check for gating variants to avoid confusion with gating squares + if (!v->gating && check_castling_rank(castlingInfoSplitted, board, v) == NOK) + return FEN_INVALID_CASTLING_INFO; + + // only check exact squares if starting position of castling pieces is known + if (!v->chess960 && !v->castlingDroppedPiece && !chess960) + { + std::array kingPositionsStart; + kingPositionsStart[WHITE] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(WHITE, v->castlingKingPiece)]); + kingPositionsStart[BLACK] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(BLACK, v->castlingKingPiece)]); + std::array, 2> rookPositionsStart; + rookPositionsStart[WHITE] = startBoard.get_squares_for_piece(v->pieceToChar[make_piece(WHITE, v->castlingRookPiece)]); + rookPositionsStart[BLACK] = startBoard.get_squares_for_piece(v->pieceToChar[make_piece(BLACK, v->castlingRookPiece)]); + + if (check_standard_castling(castlingInfoSplitted, board, kingPositions, kingPositionsStart, rookPositionsStart, v) == NOK) + return FEN_INVALID_CASTLING_INFO; + } + } + } + + // 4) Part + // check en-passant square + if (fenParts.size() >= 4 && !skipCastlingAndEp) + { + if (v->doubleStep && v->pieceTypes.find(PAWN) != v->pieceTypes.end()) + { + if (check_en_passant_square(fenParts[3]) == NOK) + return FEN_INVALID_EN_PASSANT_SQ; + } + else if (v->countingRule && !check_digit_field(fenParts[3])) + return FEN_INVALID_COUNTING_RULE; + } + + // 5) Part + // check check count + unsigned int optionalFields = 2 * !skipCastlingAndEp; + if (fenParts.size() >= 3 + optionalFields && v->checkCounting && fenParts.size() % 2) + { + if (check_check_count(fenParts[2 + optionalFields]) == NOK) + return FEN_INVALID_CHECK_COUNT; + optionalFields++; + } + + // 6) Part + // check half move counter + if (fenParts.size() >= 3 + optionalFields && !check_digit_field(fenParts[fenParts.size()-2])) + { + std::cerr << "Invalid half move counter: '" << fenParts[fenParts.size()-2] << "'." << std::endl; + return FEN_INVALID_HALF_MOVE_COUNTER; + } + + // 7) Part + // check move counter + if (fenParts.size() >= 4 + optionalFields && !check_digit_field(fenParts[fenParts.size()-1])) + { + std::cerr << "Invalid move counter: '" << fenParts[fenParts.size()-1] << "'." << std::endl; + return FEN_INVALID_MOVE_COUNTER; + } + + return FEN_OK; +} +} // namespace FEN + +} // namespace Stockfish + +#endif // #ifndef APIUTIL_H_INCLUDED diff --cc src/endgame.cpp index 7fc242a,a44d3a1..59a1ce5 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@@ -342,169 -313,6 +342,169 @@@ Value Endgame::operator()(const template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } +/// KFsPs vs K. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = pos.non_pawn_material(strongSide) + + pos.count(strongSide) * PawnValueEg + + push_to_edge(loserKSq, pos) + + push_close(winnerKSq, loserKSq); + + if ( pos.count(strongSide) >= 3 + && ( DarkSquares & pos.pieces(strongSide, FERS)) + && (~DarkSquares & pos.pieces(strongSide, FERS))) + result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1); + else if (pos.count(strongSide) + pos.count(strongSide) < 3) + return VALUE_DRAW; + else + { + bool dark = DarkSquares & pos.pieces(strongSide, FERS); + bool light = ~DarkSquares & pos.pieces(strongSide, FERS); + + // Determine the color of ferzes from promoting pawns + Bitboard b = pos.pieces(strongSide, PAWN); + while (b && (!dark || !light)) + { - if (file_of(pop_lsb(&b)) % 2 != relative_rank(strongSide, pos.promotion_rank(), pos.max_rank()) % 2) ++ if (file_of(pop_lsb(b)) % 2 != relative_rank(strongSide, pos.promotion_rank(), pos.max_rank()) % 2) + light = true; + else + dark = true; + } + if (!dark || !light) + return VALUE_DRAW; // we can not checkmate with same colored ferzes + } + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Mate with KNS vs K. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, KnightValueMg + SilverValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = VALUE_KNOWN_WIN + + push_close(winnerKSq, loserKSq) + + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KNF vs K. Can only be won if the weaker side's king +/// is close to a corner of the same color as the fers. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, KnightValueMg + FersValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + Square fersSq = pos.square(strongSide); + + // tries to drive toward corners A1 or H8. If we have a + // fers that cannot reach the above squares, we flip the kings in order + // to drive the enemy toward corners A8 or H1. + if (opposite_colors(fersSq, SQ_A1)) + { + winnerKSq = relative_square(BLACK, winnerKSq, pos.max_rank()); + loserKSq = relative_square(BLACK, loserKSq, pos.max_rank()); + } + + Value result = Value(push_close(winnerKSq, loserKSq)) + + 50 * push_to_corner(loserKSq, pos); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KNSFKR vs K. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, KnightValueMg + SilverValueMg + FersValueMg, 0)); + assert(verify_material(pos, weakSide, RookValueMg, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = KnightValueEg + SilverValueEg + FersValueEg - RookValueEg + + push_close(winnerKSq, loserKSq) + + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Mate with KSF vs K. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, SilverValueMg + FersValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = VALUE_KNOWN_WIN + + push_close(winnerKSq, loserKSq) + + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Mate with KSF vs KF. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, SilverValueMg + FersValueMg, 0)); + assert(verify_material(pos, weakSide, FersValueMg, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + Square fersSq = pos.square(weakSide); + + Value result = SilverValueEg + + push_close(winnerKSq, loserKSq) + + push_away(fersSq, loserKSq) + + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KR vs KS +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 0)); + assert(verify_material(pos, weakSide, SilverValueMg, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = RookValueEg + - SilverValueEg + + push_to_edge(loserKSq, pos) + + push_close(winnerKSq, loserKSq); + + return strongSide == pos.side_to_move() ? result : -result; +} + + /// KB and one or more pawns vs K. It checks for draws with rook pawns and /// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW /// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling diff --cc src/evaluate.cpp index c84dd69,789e285..16f31bd --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@@ -844,60 -639,8 +844,60 @@@ namespace Bitboard b, weak, defended, nonPawnEnemies, stronglyProtected, safe; Score score = SCORE_ZERO; + // Bonuses for variants with mandatory captures + if (pos.must_capture()) + { + // Penalties for possible captures + Bitboard captures = attackedBy[Us][ALL_PIECES] & pos.pieces(Them); + if (captures) + score -= make_score(2000, 2000) / (1 + popcount(captures & attackedBy[Them][ALL_PIECES] & ~attackedBy2[Us])); + + // Bonus if we threaten to force captures + Bitboard moves = 0, piecebb = pos.pieces(Us); + while (piecebb) + { - Square s = pop_lsb(&piecebb); ++ Square s = pop_lsb(piecebb); + if (type_of(pos.piece_on(s)) != KING) + moves |= pos.moves_from(Us, type_of(pos.piece_on(s)), s); + } + score += make_score(200, 200) * popcount(attackedBy[Them][ALL_PIECES] & moves & ~pos.pieces()); + score += make_score(200, 220) * popcount(attackedBy[Them][ALL_PIECES] & moves & ~pos.pieces() & ~attackedBy2[Us]); + } + + // Extinction threats + if (pos.extinction_value() == -VALUE_MATE) + { + Bitboard bExt = attackedBy[Us][ALL_PIECES] & pos.pieces(Them); + for (PieceType pt : pos.extinction_piece_types()) + { + if (pt == ALL_PIECES) + continue; + int denom = std::max(pos.count_with_hand(Them, pt) - pos.extinction_piece_count(), 1); + // Explosion threats + if (pos.blast_on_capture()) + { + int evasions = popcount(((attackedBy[Them][pt] & ~pos.pieces(Them)) | pos.pieces(Them, pt)) & ~attackedBy[Us][ALL_PIECES]) * denom; + int attacks = popcount((attackedBy[Them][pt] | pos.pieces(Them, pt)) & attackedBy[Us][ALL_PIECES]); + int explosions = 0; + + Bitboard bExtBlast = bExt & (attackedBy2[Us] | ~attackedBy[Us][pt]); + while (bExtBlast) + { - Square s = pop_lsb(&bExtBlast); ++ Square s = pop_lsb(bExtBlast); + if (((attacks_bb(s) | s) & pos.pieces(Them, pt)) && !(attacks_bb(s) & pos.pieces(Us, pt))) + explosions++; + } + int danger = 20 * attacks / (evasions + 1) + 40 * explosions; + score += make_score(danger * (100 + danger), 0); + } + else + // Direct extinction threats + score += make_score(1000, 1000) / (denom * denom) * popcount(bExt & pos.pieces(Them, pt)); + } + } + // Non-pawn enemies - nonPawnEnemies = pos.pieces(Them) & ~pos.pieces(PAWN); + nonPawnEnemies = pos.pieces(Them) & ~pos.pieces(PAWN, SHOGI_PAWN) & ~pos.pieces(SOLDIER); // Squares strongly protected by the enemy, either because they defend the // square with a pawn, or because they defend the square twice and we don't. @@@ -1067,35 -810,7 +1067,35 @@@ } } // 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); ++ 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 constexpr (T) @@@ -1157,171 -861,6 +1157,171 @@@ } + // Evaluation::variant() computes variant-specific evaluation bonuses for a given side. + + template template + Score Evaluation::variant() const { + + constexpr Color Them = ~Us; + constexpr Direction Down = pawn_push(Them); + + Score score = SCORE_ZERO; + + // Capture the flag + if (pos.capture_the_flag(Us)) + { + PieceType ptCtf = pos.capture_the_flag_piece(); + Bitboard ctfPieces = pos.pieces(Us, ptCtf); + Bitboard ctfTargets = pos.capture_the_flag(Us) & pos.board_bb(); + Bitboard onHold = 0; + Bitboard onHold2 = 0; + Bitboard processed = 0; + Bitboard blocked = pos.pieces(Us, PAWN) | attackedBy[Them][ALL_PIECES]; + Bitboard doubleBlocked = attackedBy2[Them] + | (pos.pieces(Us, PAWN) & (shift(pos.pieces()) | attackedBy[Them][ALL_PIECES])) + | (pos.pieces(Them) & pe->pawn_attacks(Them)) + | (pawn_attacks_bb(pos.pieces(Them, PAWN) & pe->pawn_attacks(Them))); + Bitboard inaccessible = pos.pieces(Us, PAWN) & shift(pos.pieces(Them, PAWN)); + // Traverse all paths of the CTF pieces to the CTF targets. + // Put squares that are attacked or occupied on hold for one iteration. + // This reflects that likely a move will be needed to block or capture the attack. + for (int dist = 0; (ctfPieces || onHold || onHold2) && (ctfTargets & ~processed); dist++) + { + int wins = popcount(ctfTargets & ctfPieces); + if (wins) + score += make_score(4000, 4000) * wins / (wins + dist * dist); + Bitboard current = ctfPieces & ~ctfTargets; + processed |= ctfPieces; + ctfPieces = onHold & ~processed; + onHold = onHold2 & ~processed; + onHold2 = 0; + while (current) + { - Square s = pop_lsb(¤t); ++ Square s = pop_lsb(current); + Bitboard attacks = ( (PseudoAttacks[Us][ptCtf][s] & pos.pieces()) + | (PseudoMoves[Us][ptCtf][s] & ~pos.pieces())) & ~processed & pos.board_bb(); + ctfPieces |= attacks & ~blocked; + onHold |= attacks & ~doubleBlocked; + onHold2 |= attacks & ~inaccessible; + } + } + } + + // nCheck + if (pos.check_counting()) + { + int remainingChecks = pos.checks_remaining(Us); + assert(remainingChecks > 0); + score += make_score(3600, 1000) / (remainingChecks * remainingChecks); + } + + // Extinction + if (pos.extinction_value() != VALUE_NONE) + { + for (PieceType pt : pos.extinction_piece_types()) + if (pt != ALL_PIECES) + { + // Single piece type extinction bonus + int denom = std::max(pos.count(Us, pt) - pos.extinction_piece_count(), 1); + if (pos.count(Them, pt) >= pos.extinction_opponent_piece_count() || pos.two_boards()) + score += make_score(1000000 / (500 + PieceValue[MG][pt]), + 1000000 / (500 + PieceValue[EG][pt])) / (denom * denom) + * (pos.extinction_value() / VALUE_MATE); + } + else if (pos.extinction_value() == VALUE_MATE) + { + // Losing chess variant bonus + score += make_score(pos.non_pawn_material(Us), pos.non_pawn_material(Us)) / pos.count(Us); + } + else if (pos.count(Us) == pos.count(Us)) + { + // Pawns easy to stop/capture + int l = 0, m = 0, r = popcount(pos.pieces(Us, PAWN) & file_bb(FILE_A)); + for (File f = FILE_A; f <= pos.max_file(); ++f) + { + l = m; m = r; r = popcount(pos.pieces(Us, PAWN) & shift(file_bb(f))); + score -= make_score(80 - 10 * (edge_distance(f, pos.max_file()) % 2), + 80 - 15 * (edge_distance(f, pos.max_file()) % 2)) * m / (1 + l * r); + } + } + else if (pos.count(Them) == pos.count(Them)) + { + // Add a bonus according to how close we are to breaking through the pawn wall + int dist = 8; + Bitboard breakthroughs = attackedBy[Us][ALL_PIECES] & rank_bb(relative_rank(Us, pos.max_rank(), pos.max_rank())); + if (breakthroughs) + dist = attackedBy[Us][QUEEN] & breakthroughs ? 0 : 1; + else for (File f = FILE_A; f <= pos.max_file(); ++f) + dist = std::min(dist, popcount(pos.pieces(PAWN) & file_bb(f))); + score += make_score(70, 70) * pos.count(Them) / (1 + dist * dist) / (pos.pieces(Us, QUEEN) ? 2 : 4); + } + } + + // Connect-n + if (pos.connect_n() > 0) + { + for (Direction d : {NORTH, NORTH_EAST, EAST, SOUTH_EAST}) + { + // Find sufficiently large gaps + Bitboard b = pos.board_bb() & ~pos.pieces(Them); + for (int i = 1; i < pos.connect_n(); i++) + b &= shift(d, b); + // Count number of pieces per gap + while (b) + { - Square s = pop_lsb(&b); ++ Square s = pop_lsb(b); + int c = 0; + for (int j = 0; j < pos.connect_n(); j++) + if (pos.pieces(Us) & (s - j * d)) + c++; + score += make_score(200, 200) * c / (pos.connect_n() - c) / (pos.connect_n() - c); + } + } + } + + // Potential piece flips (Reversi) + if (pos.flip_enclosed_pieces()) + { + // Stable pieces + if (pos.flip_enclosed_pieces() == REVERSI) + { + Bitboard edges = (FileABB | file_bb(pos.max_file()) | Rank1BB | rank_bb(pos.max_rank())) & pos.board_bb(); + Bitboard edgePieces = pos.pieces(Us) & edges; + while (edgePieces) + { - Bitboard connectedEdge = attacks_bb(Us, ROOK, pop_lsb(&edgePieces), ~(pos.pieces(Us) & edges)) & edges; ++ Bitboard connectedEdge = attacks_bb(Us, ROOK, pop_lsb(edgePieces), ~(pos.pieces(Us) & edges)) & edges; + if (!more_than_one(connectedEdge & ~pos.pieces(Us))) + score += make_score(300, 300); + else if (!(connectedEdge & ~pos.pieces())) + score += make_score(200, 200); + } + } + + // Unstable + Bitboard unstable = 0; + Bitboard drops = pos.drop_region(Them, IMMOBILE_PIECE); + while (drops) + { - Square s = pop_lsb(&drops); ++ Square s = pop_lsb(drops); + if (pos.flip_enclosed_pieces() == REVERSI) + { + Bitboard b = attacks_bb(Them, QUEEN, s, ~pos.pieces(Us)) & ~PseudoAttacks[Them][KING][s] & pos.pieces(Them); + while(b) - unstable |= between_bb(s, pop_lsb(&b)); ++ unstable |= between_bb(s, pop_lsb(b)); + } + else + unstable |= PseudoAttacks[Them][KING][s] & pos.pieces(Us); + } + score -= make_score(200, 200) * popcount(unstable); + } + + if (T) + Trace::add(VARIANT, Us, score); + + return score; + } + + // Evaluation::winnable() adjusts the midgame and endgame score components, based on // the known attacking/defending status of the players. The final value is derived // by interpolation from the midgame and endgame values. @@@ -1518,16 -1038,45 +1518,52 @@@ make_v return v; } - } // namespace + + /// Fisher Random Chess: correction for cornered bishops, to fix chess960 play with NNUE + + Value fix_FRC(const Position& pos) { + - constexpr Bitboard Corners = 1ULL << SQ_A1 | 1ULL << SQ_H1 | 1ULL << SQ_A8 | 1ULL << SQ_H8; ++ constexpr Bitboard Corners = Bitboard(1ULL) << SQ_A1 | Bitboard(1ULL) << SQ_H1 | Bitboard(1ULL) << SQ_A8 | Bitboard(1ULL) << SQ_H8; + + if (!(pos.pieces(BISHOP) & Corners)) + return VALUE_ZERO; + + int correction = 0; + + if ( pos.piece_on(SQ_A1) == W_BISHOP + && pos.piece_on(SQ_B2) == W_PAWN) + correction += !pos.empty(SQ_B3) ? -CorneredBishop * 4 + : -CorneredBishop * 3; + + if ( pos.piece_on(SQ_H1) == W_BISHOP + && pos.piece_on(SQ_G2) == W_PAWN) + correction += !pos.empty(SQ_G3) ? -CorneredBishop * 4 + : -CorneredBishop * 3; + + if ( pos.piece_on(SQ_A8) == B_BISHOP + && pos.piece_on(SQ_B7) == B_PAWN) + correction += !pos.empty(SQ_B6) ? CorneredBishop * 4 + : CorneredBishop * 3; + + if ( pos.piece_on(SQ_H8) == B_BISHOP + && pos.piece_on(SQ_G7) == B_PAWN) + correction += !pos.empty(SQ_G6) ? CorneredBishop * 4 + : CorneredBishop * 3; + + return pos.side_to_move() == WHITE ? Value(correction) + : -Value(correction); + } + + } // namespace Eval +/// tempo_value() returns the evaluation offset for the side to move + +Value Eval::tempo_value(const Position& pos) { + return Tempo * (1 + 4 * pos.captures_to_hand()); +} + + /// evaluate() is the evaluator for the outer world. It returns a static /// evaluation of the position from the point of view of the side to move. @@@ -1540,16 -1089,19 +1576,26 @@@ Value Eval::evaluate(const Position& po else { // Scale and shift NNUE for compatibility with search and classical evaluation - auto adjusted_NNUE = [&](){ - int mat = pos.non_pawn_material() + 2 * PawnValueMg * pos.count(); - int v2 = VALUE_ZERO; + auto adjusted_NNUE = [&]() + { + int material = pos.non_pawn_material() + 4 * PawnValueMg * pos.count(); + int scale = 580 + + material / 32 + - 4 * pos.rule50_count(); + + Value nnue = NNUE::evaluate(pos) * scale / 1024 + Tempo; + + if (pos.is_chess960()) + nnue += fix_FRC(pos); + + if (pos.check_counting()) + { + Color us = pos.side_to_move(); - v2 = mat / (30 * pos.checks_remaining( us)) - - mat / (30 * pos.checks_remaining(~us)); ++ nnue += material / (30 * pos.checks_remaining( us)) ++ - material / (30 * pos.checks_remaining(~us)); + } - return NNUE::evaluate(pos) * (641 + mat / 32 - 4 * pos.rule50_count()) / 1024 + Tempo + v2; ++ + return nnue; }; // If there is PSQ imbalance use classical eval, with small probability if it is small diff --cc src/movegen.cpp index 58bf95e,5049613..b85922f --- a/src/movegen.cpp +++ b/src/movegen.cpp @@@ -25,79 -25,27 +25,79 @@@ namespace Stockfish namespace { - template - ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) { + template + ExtMove* make_move_and_gating(const Position& pos, ExtMove* moveList, Color us, Square from, Square to) { - if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + // Arrow gating moves + if (pos.arrow_gating()) { - *moveList++ = make(to - D, to, QUEEN); - if (attacks_bb(to) & ksq) - *moveList++ = make(to - D, to, KNIGHT); + for (PieceType pt_gating : pos.piece_types()) + if (pos.count_in_hand(us, pt_gating) > 0) + { + Bitboard b = pos.drop_region(us, pt_gating) & moves_bb(us, type_of(pos.piece_on(from)), to, pos.pieces() ^ from) & ~(pos.pieces() ^ from); + while (b) - *moveList++ = make_gating(from, to, pt_gating, pop_lsb(&b)); ++ *moveList++ = make_gating(from, to, pt_gating, pop_lsb(b)); + } + return moveList; } - if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS) + *moveList++ = make(from, to); + + // Gating moves + if (pos.seirawan_gating() && (pos.gates(us) & from)) + for (PieceType pt_gating : pos.piece_types()) + if (pos.count_in_hand(us, pt_gating) > 0 && (pos.drop_region(us, pt_gating) & from)) + *moveList++ = make_gating(from, to, pt_gating, from); + if (pos.seirawan_gating() && T == CASTLING && (pos.gates(us) & to)) + for (PieceType pt_gating : pos.piece_types()) + if (pos.count_in_hand(us, pt_gating) > 0 && (pos.drop_region(us, pt_gating) & to)) + *moveList++ = make_gating(from, to, pt_gating, to); + + return moveList; + } + + template + ExtMove* make_promotions(const Position& pos, ExtMove* moveList, Square to) { + + if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { - *moveList++ = make(to - D, to, ROOK); - *moveList++ = make(to - D, to, BISHOP); - if (!(attacks_bb(to) & ksq)) - *moveList++ = make(to - D, to, KNIGHT); + for (PieceType pt : pos.promotion_piece_types()) + if (!pos.promotion_limit(pt) || pos.promotion_limit(pt) > pos.count(c, pt)) + *moveList++ = make(to - D, to, pt); + PieceType pt = pos.promoted_piece_type(PAWN); + if (pt && !(pos.piece_promotion_on_capture() && pos.empty(to))) + *moveList++ = make(to - D, to); } return moveList; } + template + ExtMove* generate_drops(const Position& pos, ExtMove* moveList, PieceType pt, Bitboard b) { + assert(Type != CAPTURES); + // Do not generate virtual drops for perft and at root + if (pos.count_in_hand(Us, pt) > 0 || (Type != NON_EVASIONS && pos.two_boards() && pos.allow_virtual_drop(Us, pt))) + { + // Restrict to valid target + b &= pos.drop_region(Us, pt); + + // Add to move list + if (pos.drop_promoted() && pos.promoted_piece_type(pt)) + { + Bitboard b2 = b; + if (Type == QUIET_CHECKS) + b2 &= pos.check_squares(pos.promoted_piece_type(pt)); + while (b2) - *moveList++ = make_drop(pop_lsb(&b2), pt, pos.promoted_piece_type(pt)); ++ *moveList++ = make_drop(pop_lsb(b2), pt, pos.promoted_piece_type(pt)); + } + if (Type == QUIET_CHECKS || pos.count_in_hand(Us, pt) <= 0) + b &= pos.check_squares(pt); + while (b) - *moveList++ = make_drop(pop_lsb(&b), pt, pt); ++ *moveList++ = make_drop(pop_lsb(b), pt, pt); + } + + return moveList; + } template ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { @@@ -186,43 -127,13 +186,43 @@@ Bitboard b3 = shift(pawnsOn7) & emptySquares; while (b1) - moveList = make_promotions(pos, moveList, pop_lsb(&b1)); - moveList = make_promotions(moveList, pop_lsb(b1), ksq); ++ moveList = make_promotions(pos, moveList, pop_lsb(b1)); while (b2) - moveList = make_promotions(pos, moveList, pop_lsb(&b2)); - moveList = make_promotions(moveList, pop_lsb(b2), ksq); ++ moveList = make_promotions(pos, moveList, pop_lsb(b2)); while (b3) - moveList = make_promotions(pos, moveList, pop_lsb(&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); ++ 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); ++ 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 @@@ -273,54 -182,16 +273,55 @@@ Bitboard bb = piecesToMove & pos.pieces(Pt); - while (bb) { - Square from = pop_lsb(&bb); + while (bb) + { + Square from = pop_lsb(bb); - Bitboard b = attacks_bb(from, pos.pieces()) & target; - if constexpr (Checks) - b &= pos.check_squares(Pt); + 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); + if (b2) + 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()); + 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)); ++ *moveList++ = make(from, pop_lsb(b2)); + + // Piece demotions + while (b3) - *moveList++ = make(from, pop_lsb(&b3)); ++ *moveList++ = make(from, pop_lsb(b3)); } return moveList; @@@ -365,67 -225,24 +366,67 @@@ target = ~pos.pieces(Us); break; } + 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, piecesToMove, target); + // generate drops + if (pos.piece_drops() && Type != CAPTURES && (pos.count_in_hand(Us, ALL_PIECES) > 0 || pos.two_boards())) + 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_and_gating(pos, moveList, Us, ksq, pop_lsb(&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 } ) if (!pos.castling_impeded(cr) && pos.can_castle(cr)) - *moveList++ = make(ksq, pos.castling_rook_square(cr)); + moveList = make_move_and_gating(pos, moveList, Us,ksq, pos.castling_rook_square(cr)); + } + // Workaround for passing: Execute a non-move with any piece + else if (pos.pass() && !pos.count(Us) && pos.pieces(Us)) + *moveList++ = make(lsb(pos.pieces(Us)), lsb(pos.pieces(Us))); + + // Castling with non-king piece + if (!pos.count(Us) && Type != CAPTURES && pos.can_castle(Us & ANY_CASTLING)) + { + Square from = pos.castling_king_square(Us); + for(CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) + if (!pos.castling_impeded(cr) && pos.can_castle(cr)) + moveList = make_move_and_gating(pos, moveList, Us, from, pos.castling_rook_square(cr)); + } + + // Special moves + if (pos.cambodian_moves() && pos.gates(Us)) + { + if (Type != CAPTURES && Type != EVASIONS && (pos.pieces(Us, KING) & pos.gates(Us))) + { + Square from = pos.square(Us); + Bitboard b = PseudoAttacks[WHITE][KNIGHT][from] & rank_bb(rank_of(from + (Us == WHITE ? NORTH : SOUTH))) + & target & ~pos.pieces(); + while (b) - moveList = make_move_and_gating(pos, moveList, Us, from, pop_lsb(&b)); ++ moveList = make_move_and_gating(pos, moveList, Us, from, pop_lsb(b)); + } + + Bitboard b = pos.pieces(Us, FERS) & pos.gates(Us); + while (b) + { - Square from = pop_lsb(&b); ++ Square from = pop_lsb(b); + Square to = from + 2 * (Us == WHITE ? NORTH : SOUTH); + if (is_ok(to) && (target & to)) + moveList = make_move_and_gating(pos, moveList, Us, from, to); + } } return moveList; @@@ -470,16 -287,16 +471,16 @@@ ExtMove* generate(const P while (dc) { - Square from = pop_lsb(&dc); + Square from = pop_lsb(dc); PieceType pt = type_of(pos.piece_on(from)); - Bitboard b = attacks_bb(pt, from, pos.pieces()) & ~pos.pieces(); + Bitboard b = pos.moves_from(us, pt, from) & ~pos.pieces(); - if (pt == KING) + if (pt == KING && pos.king_type() == KING) b &= ~attacks_bb(pos.square(~us)); while (b) - moveList = make_move_and_gating(pos, moveList, us, from, pop_lsb(&b)); - *moveList++ = make_move(from, pop_lsb(b)); ++ moveList = make_move_and_gating(pos, moveList, us, from, pop_lsb(b)); } return us == WHITE ? generate_all(pos, moveList) @@@ -496,39 -313,11 +497,39 @@@ ExtMove* generate(const Posit Color us = pos.side_to_move(); Square ksq = pos.square(us); + Bitboard sliderAttacks = 0; + Bitboard sliders = pos.checkers(); + + // Passing move by king in bikjang + if (pos.bikjang() && pos.pass()) + *moveList++ = make(ksq, ksq); - // Generate evasions for king - Bitboard b = attacks_bb(ksq) & ~pos.pieces(us); + // Consider all evasion moves for special pieces + if (sliders & pos.non_sliding_riders()) + { + Bitboard target = pos.board_bb() & ~pos.pieces(us); + Bitboard b = ( (pos.attacks_from(us, KING, ksq) & pos.pieces()) + | (pos.moves_from(us, KING, ksq) & ~pos.pieces())) & target; + while (b) - moveList = make_move_and_gating(pos, moveList, us, ksq, pop_lsb(&b)); ++ moveList = make_move_and_gating(pos, moveList, us, ksq, pop_lsb(b)); + return us == WHITE ? generate_all(pos, moveList) + : generate_all(pos, moveList); + } + + // Find all the squares attacked by slider checkers. We will remove them from + // the king evasions in order to skip known illegal moves, which avoids any + // useless legality checks later on. + while (sliders) + { - Square checksq = pop_lsb(&sliders); ++ Square checksq = pop_lsb(sliders); + sliderAttacks |= attacks_bb(~us, type_of(pos.piece_on(checksq)), checksq, pos.pieces() ^ ksq); + } + + // Generate evasions for king, capture and non capture moves + Bitboard b = ( (pos.attacks_from(us, KING, ksq) & pos.pieces()) + | (pos.moves_from(us, KING, ksq) & ~pos.pieces())) & ~pos.pieces(us) & ~sliderAttacks; while (b) - moveList = make_move_and_gating(pos, moveList, us, ksq, pop_lsb(&b)); - *moveList++ = make_move(ksq, pop_lsb(b)); ++ moveList = make_move_and_gating(pos, moveList, us, ksq, pop_lsb(b)); if (more_than_one(pos.checkers())) return moveList; // Double check, only a king move can save the day diff --cc src/nnue/features/half_kp_shogi.cpp index 3c9f551,0000000..5fc920e mode 100644,000000..100644 --- a/src/nnue/features/half_kp_shogi.cpp +++ b/src/nnue/features/half_kp_shogi.cpp @@@ -1,98 -1,0 +1,98 @@@ +/* + 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 . +*/ + +//Definition of input features HalfKP of NNUE evaluation function + +#include "half_kp_shogi.h" +#include "index_list.h" + +namespace Stockfish::Eval::NNUE::Features { + + constexpr Square rotate(Square s) { + return Square(SQUARE_NB_SHOGI - 1 - int(s)); + } + + constexpr Square to_shogi_square(Square s) { + return Square((8 - s % 12) * 9 + 8 - s / 12); + } + + // Orient a square according to perspective (rotates by 180 for black) + inline Square orient(Color perspective, Square s) { + return perspective == WHITE ? to_shogi_square(s) : rotate(to_shogi_square(s)); + } + + // Index of a feature for a given king position and another piece on some square + inline IndexType make_index(Color perspective, Square s, Piece pc, Square ksq) { + return IndexType(orient(perspective, s) + shogi_kpp_board_index[perspective][pc] + SHOGI_PS_END * ksq); + } + + // Index of a feature for a given king position and hand piece + inline IndexType make_index(Color perspective, Color c, int hand_index, PieceType pt, Square ksq) { + Color color = (c == perspective) ? WHITE : BLACK; + return IndexType(hand_index + shogi_kpp_hand_index[color][pt] + SHOGI_PS_END * ksq); + } + + // Get a list of indices for active features + template + void HalfKPShogi::AppendActiveIndices( + const Position& pos, Color perspective, IndexList* active) { + + Square ksq = orient(perspective, pos.square(perspective)); + Bitboard bb = pos.pieces() & ~pos.pieces(KING); + while (bb) { - Square s = pop_lsb(&bb); ++ Square s = pop_lsb(bb); + active->push_back(make_index(perspective, s, pos.piece_on(s), ksq)); + } + + // Indices for pieces in hand + for (Color c : {WHITE, BLACK}) + for (PieceType pt : pos.piece_types()) + for (int i = 0; i < pos.count_in_hand(c, pt); i++) + active->push_back(make_index(perspective, c, i, pt, ksq)); + } + + // Get a list of indices for recently changed features + template + void HalfKPShogi::AppendChangedIndices( + const Position& pos, const DirtyPiece& dp, Color perspective, + IndexList* removed, IndexList* added) { + + Square ksq = orient(perspective, pos.square(perspective)); + for (int i = 0; i < dp.dirty_num; ++i) { + Piece pc = dp.piece[i]; + if (type_of(pc) == KING) continue; + if (dp.from[i] != SQ_NONE) + removed->push_back(make_index(perspective, dp.from[i], pc, ksq)); + else if (dp.dirty_num == 1) + { + Piece handPc = dp.handPiece[i]; + removed->push_back(make_index(perspective, color_of(handPc), pos.count_in_hand(color_of(handPc), type_of(handPc)), type_of(handPc), ksq)); + } + if (dp.to[i] != SQ_NONE) + added->push_back(make_index(perspective, dp.to[i], pc, ksq)); + else if (i == 1) + { + Piece handPc = dp.handPiece[i]; + added->push_back(make_index(perspective, color_of(handPc), pos.count_in_hand(color_of(handPc), type_of(handPc)) - 1, type_of(handPc), ksq)); + } + } + } + + template class HalfKPShogi; + +} // namespace Stockfish::Eval::NNUE::Features diff --cc src/nnue/features/half_kp_variants.cpp index cbfe0a3,0000000..0eca178 mode 100644,000000..100644 --- a/src/nnue/features/half_kp_variants.cpp +++ b/src/nnue/features/half_kp_variants.cpp @@@ -1,96 -1,0 +1,96 @@@ +/* + 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 . +*/ + +//Definition of input features HalfKP of NNUE evaluation function + +#include "half_kp_variants.h" +#include "index_list.h" + +#ifdef LARGEBOARDS +#include "half_kp_shogi.h" +#endif + +namespace Stockfish::Eval::NNUE::Features { + + // Map square to numbering on 8x8 board + constexpr Square to_chess_square(Square s) { + return Square(s - rank_of(s) * (FILE_MAX - FILE_H)); + } + + // Orient a square according to perspective (rotates by 180 for black) + inline Square orient(const Position& pos, Color perspective, Square s) { + return to_chess_square( perspective == WHITE || (pos.capture_the_flag(BLACK) & Rank8BB) ? s + : flip_rank(flip_file(s, pos.max_file()), pos.max_rank())); + } + + // Index of a feature for a given king position and another piece on some square + inline IndexType make_index(const Position& pos, Color perspective, Square s, Piece pc, Square ksq) { + return IndexType(orient(pos, perspective, s) + kpp_board_index[perspective][pc] + PS_END * ksq); + } + + // Get a list of indices for active features + template + void HalfKPVariants::AppendActiveIndices( + const Position& pos, Color perspective, IndexList* active) { + + // Re-route to shogi features +#ifdef LARGEBOARDS + if (currentNnueFeatures == NNUE_SHOGI) + { + assert(HalfKPShogi::kDimensions <= kDimensions); + return HalfKPShogi::AppendActiveIndices(pos, perspective, active); + } +#endif + + Square ksq = orient(pos, perspective, pos.square(perspective, pos.nnue_king())); + Bitboard bb = pos.pieces() & ~pos.pieces(pos.nnue_king()); + while (bb) { - Square s = pop_lsb(&bb); ++ Square s = pop_lsb(bb); + active->push_back(make_index(pos, perspective, s, pos.piece_on(s), ksq)); + } + } + + // Get a list of indices for recently changed features + template + void HalfKPVariants::AppendChangedIndices( + const Position& pos, const DirtyPiece& dp, Color perspective, + IndexList* removed, IndexList* added) { + + // Re-route to shogi features +#ifdef LARGEBOARDS + if (currentNnueFeatures == NNUE_SHOGI) + { + assert(HalfKPShogi::kDimensions <= kDimensions); + return HalfKPShogi::AppendChangedIndices(pos, dp, perspective, removed, added); + } +#endif + + Square ksq = orient(pos, perspective, pos.square(perspective, pos.nnue_king())); + for (int i = 0; i < dp.dirty_num; ++i) { + Piece pc = dp.piece[i]; + if (type_of(pc) == pos.nnue_king()) continue; + if (dp.from[i] != SQ_NONE) + removed->push_back(make_index(pos, perspective, dp.from[i], pc, ksq)); + if (dp.to[i] != SQ_NONE) + added->push_back(make_index(pos, perspective, dp.to[i], pc, ksq)); + } + } + + template class HalfKPVariants; + +} // namespace Stockfish::Eval::NNUE::Features diff --cc src/position.cpp index 5bfa89b,ec356ac..f275834 --- a/src/position.cpp +++ b/src/position.cpp @@@ -94,10 -73,9 +94,10 @@@ std::ostream& operator<<(std::ostream& << std::setfill(' ') << std::dec << "\nCheckers: "; for (Bitboard b = pos.checkers(); b; ) - os << UCI::square(pos, pop_lsb(&b)) << " "; - os << UCI::square(pop_lsb(b)) << " "; ++ os << UCI::square(pos, pop_lsb(b)) << " "; if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) + && Options["UCI_Variant"] == "chess" && !pos.can_castle(ANY_CASTLING)) { StateInfo st; @@@ -309,185 -220,81 +309,185 @@@ Position& Position::set(const Variant* // 2. Active color ss >> token; - sideToMove = (token == 'w' ? WHITE : BLACK); + sideToMove = (token != (sfen ? 'w' : 'b') ? WHITE : BLACK); // Invert colors for SFEN ss >> token; - // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, - // Shredder-FEN that uses the letters of the columns on which the rooks began - // the game instead of KQkq and also X-FEN standard that, in case of Chess960, - // if an inner rook is associated with the castling right, the castling tag is - // replaced by the file letter of the involved rook, as for the Shredder-FEN. - while ((ss >> token) && !isspace(token)) + // 3-4. Skip parsing castling and en passant flags if not present + st->epSquare = SQ_NONE; + st->castlingKingSquare[WHITE] = st->castlingKingSquare[BLACK] = SQ_NONE; + if (!isdigit(ss.peek()) && !sfen) { - Square rsq; - Color c = islower(token) ? BLACK : WHITE; - Piece rook = make_piece(c, ROOK); - - token = char(toupper(token)); + // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, + // Shredder-FEN that uses the letters of the columns on which the rooks began + // the game instead of KQkq and also X-FEN standard that, in case of Chess960, + // if an inner rook is associated with the castling right, the castling tag is + // replaced by the file letter of the involved rook, as for the Shredder-FEN. + while ((ss >> token) && !isspace(token)) + { + Square rsq; + Color c = islower(token) ? BLACK : WHITE; + Piece rook = make_piece(c, castling_rook_piece()); - if (token == 'K') - for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) {} + token = char(toupper(token)); - else if (token == 'Q') - for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {} + if (token == 'K') + for (rsq = make_square(FILE_MAX, castling_rank(c)); piece_on(rsq) != rook; --rsq) {} - else if (token >= 'A' && token <= 'H') - rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); + else if (token == 'Q') + for (rsq = make_square(FILE_A, castling_rank(c)); piece_on(rsq) != rook; ++rsq) {} - else - continue; + else if (token >= 'A' && token <= 'A' + max_file()) + rsq = make_square(File(token - 'A'), castling_rank(c)); - set_castling_right(c, rsq); - } + else + continue; - set_state(st); + // Determine castling "king" position + if (castling_enabled() && st->castlingKingSquare[c] == SQ_NONE) + { + Bitboard castlingKings = pieces(c, castling_king_piece()) & rank_bb(castling_rank(c)); + // Ambiguity resolution for 960 variants with more than one "king" + // e.g., EAH means that an e-file king can castle with a- and h-file rooks + st->castlingKingSquare[c] = isChess960 && piece_on(rsq) == make_piece(c, castling_king_piece()) ? rsq + : castlingKings && (!more_than_one(castlingKings) || isChess960) ? lsb(castlingKings) + : make_square(castling_king_file(), castling_rank(c)); + } - // 4. En passant square. - // Ignore if square is invalid or not on side to move relative rank 6. - bool enpassant = false; + // Set gates (and skip castling rights) + if (gating()) + { + st->gatesBB[c] |= rsq; + if (token == 'K' || token == 'Q') + st->gatesBB[c] |= st->castlingKingSquare[c]; + // Do not set castling rights for gates unless there are no pieces in hand, + // which means that the file is referring to a chess960 castling right. + else if (!seirawan_gating() || count_in_hand(c, ALL_PIECES) > 0 || captures_to_hand()) + continue; + } - if ( ((ss >> col) && (col >= 'a' && col <= 'h')) - && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3')))) - { - st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); + if (castling_enabled() && piece_on(rsq) == rook) + set_castling_right(c, rsq); + } - // En passant square will be considered only if - // a) side to move have a pawn threatening epSquare - // b) there is an enemy pawn in front of epSquare - // c) there is no piece on epSquare or behind epSquare - // d) enemy pawn didn't block a check of its own color by moving forward - enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) - && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) - && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))) - && ( file_of(square(sideToMove)) == file_of(st->epSquare) - || !(blockers_for_king(sideToMove) & (st->epSquare + pawn_push(~sideToMove)))); + // Set castling rights for 960 gating variants + if (gating() && castling_enabled()) + for (Color c : {WHITE, BLACK}) + if ((gates(c) & pieces(castling_king_piece())) && !castling_rights(c) && (!seirawan_gating() || count_in_hand(c, ALL_PIECES) > 0 || captures_to_hand())) + { + Bitboard castling_rooks = gates(c) & pieces(castling_rook_piece()); + while (castling_rooks) - set_castling_right(c, pop_lsb(&castling_rooks)); ++ set_castling_right(c, pop_lsb(castling_rooks)); + } + + // counting limit + if (counting_rule() && isdigit(ss.peek())) + ss >> st->countingLimit; + + // 4. En passant square. + // Ignore if square is invalid or not on side to move relative rank 6. + else if ( ((ss >> col) && (col >= 'a' && col <= 'a' + max_file())) + && ((ss >> row) && (row >= '1' && row <= '1' + max_rank()))) + { + st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); +#ifdef LARGEBOARDS + // Consider different rank numbering in CECP + if (max_rank() == RANK_10 && Options["Protocol"] == "xboard") + st->epSquare += NORTH; +#endif + + // En passant square will be considered only if + // a) side to move have a pawn threatening epSquare + // b) there is an enemy pawn in front of epSquare + // c) there is no piece on epSquare or behind epSquare + bool enpassant; + enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) + && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) + && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); + if (!enpassant) + st->epSquare = SQ_NONE; + } } - // It's necessary for st->previous to be intialized in this way because legality check relies on its existence - if (enpassant) { - st->previous = new StateInfo(); - remove_piece(st->epSquare - pawn_push(sideToMove)); - st->previous->checkersBB = attackers_to(square(~sideToMove)) & pieces(sideToMove); - st->previous->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE), st->previous->pinners[BLACK]); - st->previous->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK), st->previous->pinners[WHITE]); - put_piece(make_piece(~sideToMove, PAWN), st->epSquare - pawn_push(sideToMove)); + // Check counter for nCheck + ss >> std::skipws >> token >> std::noskipws; + + if (check_counting()) + { + if (ss.peek() == '+') + { + st->checksRemaining[WHITE] = CheckCount(std::max(token - '0', 0)); + ss >> token >> token; + st->checksRemaining[BLACK] = CheckCount(std::max(token - '0', 0)); + } + else + { + // If check count is not provided, assume that the next check wins + st->checksRemaining[WHITE] = CheckCount(1); + st->checksRemaining[BLACK] = CheckCount(1); + ss.putback(token); + } } else - st->epSquare = SQ_NONE; + ss.putback(token); // 5-6. Halfmove clock and fullmove number - ss >> std::skipws >> st->rule50 >> gamePly; + if (sfen) + { + // Pieces in hand for SFEN + int handCount = 1; + while ((ss >> token) && !isspace(token)) + { + if (token == '-') + continue; + else if (isdigit(token)) + { + handCount = token - '0'; + while (isdigit(ss.peek()) && ss >> token) + handCount = 10 * handCount + (token - '0'); + } + else if ((idx = piece_to_char().find(token)) != string::npos) + { + for (int i = 0; i < handCount; i++) + add_to_hand(Piece(idx)); + handCount = 1; + } + } + // Move count is in ply for SFEN + ss >> std::skipws >> gamePly; + gamePly = std::max(gamePly - 1, 0); + } + else + { + ss >> std::skipws >> st->rule50 >> gamePly; - // Convert from fullmove starting from 1 to gamePly starting from 0, - // handle also common incorrect FEN with fullmove = 0. - gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); + // Convert from fullmove starting from 1 to gamePly starting from 0, + // handle also common incorrect FEN with fullmove = 0. + gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); + } - chess960 = isChess960; + // counting rules + if (st->countingLimit && st->rule50) + { + st->countingPly = st->rule50; + st->rule50 = 0; + } + + // Lichess-style counter for 3check + if (check_counting()) + { + if (ss >> token && token == '+') + { + ss >> token; + st->checksRemaining[WHITE] = CheckCount(std::max(3 - (token - '0'), 0)); + ss >> token >> token; + st->checksRemaining[BLACK] = CheckCount(std::max(3 - (token - '0'), 0)); + } + } + + chess960 = isChess960 || v->chess960; + tsumeMode = Options["TsumeMode"]; thisThread = th; + set_state(st); st->accumulator.state[WHITE] = Eval::NNUE::INIT; st->accumulator.state[BLACK] = Eval::NNUE::INIT; @@@ -773,48 -469,18 +773,48 @@@ Bitboard Position::slider_blockers(Bitb Bitboard blockers = 0; pinners = 0; + if (s == SQ_NONE || !sliders) + return blockers; + // Snipers are sliders that attack 's' when a piece and other snipers are removed - Bitboard snipers = ( (attacks_bb< ROOK>(s) & pieces(QUEEN, ROOK)) - | (attacks_bb(s) & pieces(QUEEN, BISHOP))) & sliders; + Bitboard snipers = 0; + + if (var->fastAttacks) + snipers = ( (attacks_bb< ROOK>(s) & pieces(c, QUEEN, ROOK, CHANCELLOR)) + | (attacks_bb(s) & pieces(c, QUEEN, BISHOP, ARCHBISHOP))) & sliders; + else + for (PieceType pt : piece_types()) + { + Bitboard b = sliders & (PseudoAttacks[~c][pt][s] ^ LeaperAttacks[~c][pt][s]) & pieces(c, pt); + if (b) + { + // Consider asymmetrical moves (e.g., horse) + if (AttackRiderTypes[pt] & ASYMMETRICAL_RIDERS) + { + Bitboard asymmetricals = PseudoAttacks[~c][pt][s] & pieces(c, pt); + while (asymmetricals) + { - Square s2 = pop_lsb(&asymmetricals); ++ Square s2 = pop_lsb(asymmetricals); + if (!(attacks_from(c, pt, s2) & s)) + snipers |= s2; + } + } + else + snipers |= b & ~attacks_bb(~c, pt, s, pieces()); + } + } Bitboard occupancy = pieces() ^ snipers; while (snipers) { - Square sniperSq = pop_lsb(&snipers); + Square sniperSq = pop_lsb(snipers); - Bitboard b = between_bb(s, sniperSq) & occupancy; + Bitboard b = between_bb(s, sniperSq, type_of(piece_on(sniperSq))) & occupancy; - if (b && !more_than_one(b)) + if (b && (!more_than_one(b) || ((AttackRiderTypes[type_of(piece_on(sniperSq))] & HOPPING_RIDERS) && popcount(b) == 2))) { + // Janggi cannons block each other + if ((pieces(JANGGI_CANNON) & sniperSq) && (pieces(JANGGI_CANNON) & b)) + b &= pieces(JANGGI_CANNON); blockers |= b; if (b & pieces(color_of(piece_on(s)))) pinners |= sniperSq; @@@ -827,88 -493,14 +827,88 @@@ /// Position::attackers_to() computes a bitboard of all pieces which attack a /// given square. Slider attacks use the occupied bitboard to indicate occupancy. -Bitboard Position::attackers_to(Square s, Bitboard occupied) const { +Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c, Bitboard janggiCannons) const { + + // Use a faster version for variants with moderate rule variations + if (var->fastAttacks) + { + return (pawn_attacks_bb(~c, s) & pieces(c, PAWN)) + | (attacks_bb(s) & pieces(c, KNIGHT, ARCHBISHOP, CHANCELLOR)) + | (attacks_bb< ROOK>(s, occupied) & pieces(c, ROOK, QUEEN, CHANCELLOR)) + | (attacks_bb(s, occupied) & pieces(c, BISHOP, QUEEN, ARCHBISHOP)) + | (attacks_bb(s) & pieces(c, KING, COMMONER)); + } - return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) - | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) - | (attacks_bb(s) & pieces(KNIGHT)) - | (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN)) - | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) - | (attacks_bb(s) & pieces(KING)); + // Use a faster version for selected fairy pieces + if (var->fastAttacks2) + { + return (pawn_attacks_bb(~c, s) & pieces(c, PAWN, BREAKTHROUGH_PIECE, GOLD)) + | (attacks_bb(s) & pieces(c, KNIGHT)) + | (attacks_bb< ROOK>(s, occupied) & ( pieces(c, ROOK, QUEEN, DRAGON) + | (pieces(c, LANCE) & PseudoAttacks[~c][LANCE][s]))) + | (attacks_bb(s, occupied) & pieces(c, BISHOP, QUEEN, DRAGON_HORSE)) + | (attacks_bb(s) & pieces(c, KING, COMMONER)) + | (attacks_bb(s) & pieces(c, FERS, DRAGON, SILVER)) + | (attacks_bb(s) & pieces(c, WAZIR, DRAGON_HORSE, GOLD)) + | (LeaperAttacks[~c][SHOGI_KNIGHT][s] & pieces(c, SHOGI_KNIGHT)) + | (LeaperAttacks[~c][SHOGI_PAWN][s] & pieces(c, SHOGI_PAWN, SILVER)); + } + + Bitboard b = 0; + for (PieceType pt : piece_types()) + if (board_bb(c, pt) & s) + { + PieceType move_pt = pt == KING ? king_type() : pt; + // Consider asymmetrical moves (e.g., horse) + if (AttackRiderTypes[move_pt] & ASYMMETRICAL_RIDERS) + { + Bitboard asymmetricals = PseudoAttacks[~c][move_pt][s] & pieces(c, pt); + while (asymmetricals) + { - Square s2 = pop_lsb(&asymmetricals); ++ Square s2 = pop_lsb(asymmetricals); + if (attacks_bb(c, move_pt, s2, occupied) & s) + b |= s2; + } + } + else if (pt == JANGGI_CANNON) + b |= attacks_bb(~c, move_pt, s, occupied) & attacks_bb(~c, move_pt, s, occupied & ~janggiCannons) & pieces(c, JANGGI_CANNON); + else + b |= attacks_bb(~c, move_pt, s, occupied) & pieces(c, pt); + } + + // Consider special move of neang in cambodian chess + if (cambodian_moves()) + { + Square fers_sq = s + 2 * (c == WHITE ? SOUTH : NORTH); + if (is_ok(fers_sq)) + b |= pieces(c, FERS) & gates(c) & fers_sq; + } + + // Janggi palace moves + if (diagonal_lines() & s) + { + Bitboard diags = 0; + if (king_type() == WAZIR) + diags |= attacks_bb(~c, FERS, s, occupied) & pieces(c, KING); + diags |= attacks_bb(~c, FERS, s, occupied) & pieces(c, WAZIR); + diags |= attacks_bb(~c, PAWN, s, occupied) & pieces(c, SOLDIER); + diags |= rider_attacks_bb(s, occupied) & pieces(c, ROOK); + diags |= rider_attacks_bb(s, occupied) + & rider_attacks_bb(s, occupied & ~janggiCannons) + & pieces(c, JANGGI_CANNON); + b |= diags & diagonal_lines(); + } + + // Unpromoted soldiers + if (b & pieces(SOLDIER) && relative_rank(c, s, max_rank()) < var->soldierPromotionRank) + b ^= b & pieces(SOLDIER) & ~PseudoAttacks[~c][SHOGI_PAWN][s]; + + return b; +} + + +Bitboard Position::attackers_to(Square s, Bitboard occupied) const { + return attackers_to(s, occupied, WHITE) | attackers_to(s, occupied, BLACK); } @@@ -924,107 -515,7 +924,107 @@@ bool Position::legal(Move m) const Square to = to_sq(m); assert(color_of(moved_piece(m)) == us); - assert(piece_on(square(us)) == make_piece(us, KING)); + assert(!count(us) || piece_on(square(us)) == make_piece(us, KING)); + assert(board_bb() & to); + + // Illegal checks + if ((!checking_permitted() || (sittuyin_promotion() && type_of(m) == PROMOTION) || (!drop_checks() && type_of(m) == DROP)) && gives_check(m)) + return false; + + // Illegal quiet moves + if (must_capture() && !capture(m) && has_capture()) + return false; + + // Illegal non-drop moves + if (must_drop() && type_of(m) != DROP && count_in_hand(us, var->mustDropType) > 0) + { + if (checkers()) + { + for (const auto& mevasion : MoveList(*this)) + if (type_of(mevasion) == DROP && legal(mevasion)) + return false; + } + else + { + for (const auto& mquiet : MoveList(*this)) + if (type_of(mquiet) == DROP && legal(mquiet)) + return false; + } + } + + // Illegal drop move + if (drop_opposite_colored_bishop() && type_of(m) == DROP) + { + if (type_of(moved_piece(m)) != BISHOP) + { + Bitboard remaining = drop_region(us, BISHOP) & ~pieces() & ~square_bb(to); + // Are enough squares available to drop bishops on opposite colors? + if ( (!( DarkSquares & pieces(us, BISHOP)) && ( DarkSquares & remaining)) + + (!(~DarkSquares & pieces(us, BISHOP)) && (~DarkSquares & remaining)) < count_in_hand(us, BISHOP)) + return false; + } + else + // Drop resulting in same-colored bishops + if ((DarkSquares & to ? DarkSquares : ~DarkSquares) & pieces(us, BISHOP)) + return false; + } + + // No legal moves from target square + if (immobility_illegal() && (type_of(m) == DROP || type_of(m) == NORMAL) && !(moves_bb(us, type_of(moved_piece(m)), to, 0) & board_bb())) + return false; + + // Illegal king passing move + if (pass_on_stalemate() && is_pass(m) && !checkers()) + { + for (const auto& move : MoveList(*this)) + if (!is_pass(move) && legal(move)) + return false; + } + + // Check for attacks to pseudo-royal pieces + if (var->extinctionPseudoRoyal) + { + Square kto = to; + Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) | kto; + if (type_of(m) == CASTLING) + { + // After castling, the rook and king final positions are the same in + // Chess960 as they would be in standard chess. + kto = make_square(to > from ? castling_kingside_file() : castling_queenside_file(), castling_rank(us)); + Direction step = kto > from ? EAST : WEST; + Square rto = kto - step; + // Pseudo-royal king + if (st->pseudoRoyals & from) + for (Square s = from; s != kto; s += step) + if ( !(blast_on_capture() && (attacks_bb(s) & st->pseudoRoyals & pieces(~sideToMove))) + && attackers_to(s, pieces() ^ from, ~us)) + return false; + occupied = (pieces() ^ from ^ to) | kto | rto; + } + if (type_of(m) == EN_PASSANT) + occupied &= ~square_bb(kto - pawn_push(us)); + if (capture(m) && blast_on_capture()) + occupied &= ~((attacks_bb(kto) & (pieces() ^ pieces(PAWN))) | kto); + Bitboard pseudoRoyals = st->pseudoRoyals & pieces(sideToMove); + Bitboard pseudoRoyalsTheirs = st->pseudoRoyals & pieces(~sideToMove); + if (is_ok(from) && (pseudoRoyals & from)) + pseudoRoyals ^= square_bb(from) ^ kto; + if (type_of(m) == PROMOTION && extinction_piece_types().find(promotion_type(m)) != extinction_piece_types().end()) + pseudoRoyals |= kto; + // Self-explosions are illegal + if (pseudoRoyals & ~occupied) + return false; + // Check for legality unless we capture a pseudo-royal piece + if (!(pseudoRoyalsTheirs & ~occupied)) + while (pseudoRoyals) + { - Square sr = pop_lsb(&pseudoRoyals); ++ Square sr = pop_lsb(pseudoRoyals); + // Touching pseudo-royal pieces are immune + if ( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) + && (attackers_to(sr, occupied, ~us) & (occupied & ~square_bb(kto)))) + return false; + } + } // st->previous->blockersForKing consider capsq as empty. // If pinned, it has to move along the king ray. @@@ -1063,44 -538,18 +1063,43 @@@ // In case of Chess960, verify if the Rook blocks some checks // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. - return !chess960 || !(blockers_for_king(us) & to_sq(m)); + return !chess960 || !attackers_to(to, pieces() ^ to_sq(m), ~us); } + Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) | to; + + // Flying general rule and bikjang + // In case of bikjang passing is always allowed, even when in check + if (st->bikjang && is_pass(m)) + return true; + if ((var->flyingGeneral && count(us)) || st->bikjang) + { + Square s = type_of(moved_piece(m)) == KING ? to : square(us); + if (attacks_bb(~us, ROOK, s, occupied) & pieces(~us, KING) & ~square_bb(to)) + return false; + } + + // Makpong rule + if (var->makpongRule && checkers() && type_of(moved_piece(m)) == KING && (checkers() ^ to)) + return false; + + // Return early when without king + if (!count(us)) + return true; + - // If the moving piece is a king, check whether the destination - // square is attacked by the opponent. Castling moves are checked - // for legality during move generation. + // If the moving piece is a king, check whether the destination square is + // attacked by the opponent. - if (type_of(piece_on(from)) == KING) - return !(attackers_to(to, pieces() ^ from) & pieces(~us)); + if (type_of(moved_piece(m)) == KING) + return !attackers_to(to, occupied, ~us); + + Bitboard janggiCannons = pieces(JANGGI_CANNON); + if (type_of(moved_piece(m)) == JANGGI_CANNON) + janggiCannons = (type_of(m) == DROP ? janggiCannons : janggiCannons ^ from) | to; + else if (janggiCannons & to) + janggiCannons ^= to; - // A non-king move is legal if and only if it is not pinned or it - // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) - || aligned(from, to, square(us)); + // A non-king move is legal if the king is not under attack after the move. + return !(attackers_to(square(us), occupied, ~us, janggiCannons) & ~SquareBB[to]); } @@@ -1469,85 -795,8 +1468,85 @@@ void Position::do_move(Move m, StateInf k ^= Zobrist::castling[st->castlingRights]; } + // Flip enclosed pieces + st->flippedPieces = 0; + if (flip_enclosed_pieces() && !is_pass(m)) + { + // Find end of rows to be flipped + if (flip_enclosed_pieces() == REVERSI) + { + Bitboard b = attacks_bb(us, QUEEN, to, board_bb() & ~pieces(~us)) & ~PseudoAttacks[us][KING][to] & pieces(us); + while(b) - st->flippedPieces |= between_bb(to, pop_lsb(&b)); ++ st->flippedPieces |= between_bb(to, pop_lsb(b)); + } + else + { + assert(flip_enclosed_pieces() == ATAXX); + st->flippedPieces = PseudoAttacks[us][KING][to] & pieces(~us); + } + + // Flip pieces + Bitboard to_flip = st->flippedPieces; + while(to_flip) + { - Square s = pop_lsb(&to_flip); ++ Square s = pop_lsb(to_flip); + Piece flipped = piece_on(s); + Piece resulting = ~flipped; + + // remove opponent's piece + remove_piece(s); + k ^= Zobrist::psq[flipped][s]; + st->materialKey ^= Zobrist::psq[flipped][pieceCount[flipped]]; + st->nonPawnMaterial[them] -= PieceValue[MG][flipped]; + + // add our piece + put_piece(resulting, s); + k ^= Zobrist::psq[resulting][s]; + st->materialKey ^= Zobrist::psq[resulting][pieceCount[resulting]-1]; + st->nonPawnMaterial[us] += PieceValue[MG][resulting]; + } + } + // Move the piece. The tricky Chess960 castling is handled earlier - if (type_of(m) != CASTLING) + if (type_of(m) == DROP) + { + if (Eval::useNNUE) + { + // Add drop piece + dp.piece[0] = pc; + dp.handPiece[0] = make_piece(us, in_hand_piece_type(m)); + dp.from[0] = SQ_NONE; + dp.to[0] = to; + } + + drop_piece(make_piece(us, in_hand_piece_type(m)), pc, to); + st->materialKey ^= Zobrist::psq[pc][pieceCount[pc]-1]; + if (type_of(pc) != PAWN) + st->nonPawnMaterial[us] += PieceValue[MG][pc]; + // Set castling rights for dropped king or rook + if (castling_dropped_piece() && rank_of(to) == castling_rank(us)) + { + if (type_of(pc) == castling_king_piece() && file_of(to) == castling_king_file()) + { + st->castlingKingSquare[us] = to; + Bitboard castling_rooks = pieces(us, castling_rook_piece()) + & rank_bb(castling_rank(us)) + & (file_bb(FILE_A) | file_bb(max_file())); + while (castling_rooks) - set_castling_right(us, pop_lsb(&castling_rooks)); ++ set_castling_right(us, pop_lsb(castling_rooks)); + } + else if (type_of(pc) == castling_rook_piece()) + { + if ( (file_of(to) == FILE_A || file_of(to) == max_file()) + && piece_on(make_square(castling_king_file(), castling_rank(us))) == make_piece(us, castling_king_piece())) + { + st->castlingKingSquare[us] = make_square(castling_king_file(), castling_rank(us)); + set_castling_right(us, to); + } + } + } + } + else if (type_of(m) != CASTLING) { if (Eval::useNNUE) { @@@ -1665,113 -859,11 +1664,113 @@@ // Set capture piece st->capturedPiece = captured; + // Add gating piece + if (is_gating(m)) + { + Square gate = gating_square(m); + Piece gating_piece = make_piece(us, gating_type(m)); + + put_piece(gating_piece, gate); + remove_from_hand(gating_piece); + + if (Eval::useNNUE) + { + // Add gating piece + dp.piece[dp.dirty_num] = gating_piece; + dp.handPiece[dp.dirty_num] = gating_piece; + dp.from[dp.dirty_num] = SQ_NONE; + dp.to[dp.dirty_num] = gate; + dp.dirty_num++; + } + + st->gatesBB[us] ^= gate; + k ^= Zobrist::psq[gating_piece][gate]; + st->materialKey ^= Zobrist::psq[gating_piece][pieceCount[gating_piece]]; + st->nonPawnMaterial[us] += PieceValue[MG][gating_piece]; + } + + // Remove gates + if (gating()) + { + if (is_ok(from) && (gates(us) & from)) + st->gatesBB[us] ^= from; + if (type_of(m) == CASTLING && (gates(us) & to_sq(m))) + st->gatesBB[us] ^= to_sq(m); + if (gates(them) & to) + st->gatesBB[them] ^= to; + if (seirawan_gating() && count_in_hand(us, ALL_PIECES) == 0 && !captures_to_hand()) + st->gatesBB[us] = 0; + } + + // Remove the blast pieces + if (captured && blast_on_capture()) + { + std::memset(st->unpromotedBycatch, 0, sizeof(st->unpromotedBycatch)); + st->demotedBycatch = st->promotedBycatch = 0; + Bitboard blast = (attacks_bb(to) & (pieces() ^ pieces(PAWN))) | to; + while (blast) + { - Square bsq = pop_lsb(&blast); ++ Square bsq = pop_lsb(blast); + Piece bpc = piece_on(bsq); + Color bc = color_of(bpc); + if (type_of(bpc) != PAWN) + st->nonPawnMaterial[bc] -= PieceValue[MG][bpc]; + + if (Eval::useNNUE) + { + dp.piece[dp.dirty_num] = bpc; + dp.handPiece[dp.dirty_num] = NO_PIECE; + dp.from[dp.dirty_num] = bsq; + dp.to[dp.dirty_num] = SQ_NONE; + dp.dirty_num++; + } + + // Update board and piece lists + // In order to not have to store the values of both board and unpromotedBoard, + // demote promoted pieces, but keep promoted pawns as promoted, + // and store demotion/promotion bitboards to disambiguate the piece state + bool capturedPromoted = is_promoted(bsq); + Piece unpromotedCaptured = unpromoted_piece_on(bsq); + st->unpromotedBycatch[bsq] = unpromotedCaptured ? unpromotedCaptured : bpc; + if (unpromotedCaptured) + st->demotedBycatch |= bsq; + else if (capturedPromoted) + st->promotedBycatch |= bsq; + remove_piece(bsq); + board[bsq] = NO_PIECE; + if (captures_to_hand()) + { + Piece pieceToHand = !capturedPromoted || drop_loop() ? ~bpc + : unpromotedCaptured ? ~unpromotedCaptured + : make_piece(~color_of(bpc), 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)]]; + + if (Eval::useNNUE) + dp.handPiece[dp.dirty_num - 1] = pieceToHand; + } + + // Update material hash key + k ^= Zobrist::psq[bpc][bsq]; + st->materialKey ^= Zobrist::psq[bpc][pieceCount[bpc]]; + if (type_of(bpc) == PAWN) + st->pawnKey ^= Zobrist::psq[bpc][bsq]; + + // Update castling rights if needed + if (st->castlingRights && castlingRightsMask[bsq]) + { + int cr = castlingRightsMask[bsq]; + k ^= Zobrist::castling[st->castlingRights & cr]; + st->castlingRights &= ~cr; + } + } + } + // Update the key with the final value st->key = k; - // Calculate checkers bitboard (if move gives check) - st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; + st->checkersBB = givesCheck ? attackers_to(square(them), us) & pieces(us) : Bitboard(0); sideToMove = ~sideToMove; @@@ -1823,51 -907,14 +1822,51 @@@ void Position::undo_move(Move m) Square to = to_sq(m); Piece pc = piece_on(to); - assert(empty(from) || type_of(m) == CASTLING); + assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || is_gating(m) + || (type_of(m) == PROMOTION && sittuyin_promotion()) + || (is_pass(m) && pass())); assert(type_of(st->capturedPiece) != KING); + // Add the blast pieces + if (st->capturedPiece && blast_on_capture()) + { + Bitboard blast = attacks_bb(to) | to; + while (blast) + { - Square bsq = pop_lsb(&blast); ++ Square bsq = pop_lsb(blast); + Piece unpromotedBpc = st->unpromotedBycatch[bsq]; + Piece bpc = st->demotedBycatch & bsq ? make_piece(color_of(unpromotedBpc), promoted_piece_type(type_of(unpromotedBpc))) + : unpromotedBpc; + bool isPromoted = (st->promotedBycatch | st->demotedBycatch) & bsq; + + // Update board and piece lists + if (bpc) + { + put_piece(bpc, bsq, isPromoted, st->demotedBycatch & bsq ? unpromotedBpc : NO_PIECE); + if (captures_to_hand()) + remove_from_hand(!drop_loop() && (st->promotedBycatch & bsq) ? make_piece(~color_of(unpromotedBpc), PAWN) + : ~unpromotedBpc); + } + } + // Reset piece since it exploded itself + pc = piece_on(to); + } + + // Remove gated piece + if (is_gating(m)) + { + Piece gating_piece = make_piece(us, gating_type(m)); + remove_piece(gating_square(m)); + board[gating_square(m)] = NO_PIECE; + add_to_hand(gating_piece); + st->gatesBB[us] |= gating_square(m); + } + if (type_of(m) == PROMOTION) { - assert(relative_rank(us, to) == RANK_8); + assert(relative_rank(us, to, max_rank()) >= promotion_rank() || sittuyin_promotion()); assert(type_of(pc) == promotion_type(m)); - assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); + assert(type_of(pc) >= KNIGHT && type_of(pc) < KING); remove_piece(to); pc = make_piece(us, PAWN); @@@ -1915,24 -945,7 +1914,24 @@@ assert(st->capturedPiece == make_piece(~us, PAWN)); } - put_piece(st->capturedPiece, capsq); // Restore the captured piece + put_piece(st->capturedPiece, capsq, st->capturedpromoted, st->unpromotedCapturedPiece); // Restore the captured piece + if (captures_to_hand()) + remove_from_hand(!drop_loop() && st->capturedpromoted ? (st->unpromotedCapturedPiece ? ~st->unpromotedCapturedPiece + : make_piece(~color_of(st->capturedPiece), PAWN)) + : ~st->capturedPiece); + } + } + + if (flip_enclosed_pieces()) + { + // Flip pieces + Bitboard to_flip = st->flippedPieces; + while(to_flip) + { - Square s = pop_lsb(&to_flip); ++ Square s = pop_lsb(to_flip); + Piece resulting = ~piece_on(s); + remove_piece(s); + put_piece(resulting, s); } } @@@ -2059,53 -1055,6 +2058,53 @@@ Key Position::key_after(Move m) const } +Value Position::blast_see(Move m) const { + assert(is_ok(m)); + + Square from = from_sq(m); + Square to = to_sq(m); + Color us = color_of(moved_piece(m)); + Bitboard fromto = type_of(m) == DROP ? square_bb(to) : from | to; + Bitboard blast = ((attacks_bb(to) & ~pieces(PAWN)) | fromto) & pieces(); + + Value result = VALUE_ZERO; + + // Add the least valuable attacker for quiet moves + if (!capture(m)) + { + Bitboard attackers = attackers_to(to, pieces() ^ fromto, ~us); + Value minAttacker = VALUE_INFINITE; + + while (attackers) + { - Square s = pop_lsb(&attackers); ++ Square s = pop_lsb(attackers); + if (extinction_piece_types().find(type_of(piece_on(s))) == extinction_piece_types().end()) + minAttacker = std::min(minAttacker, blast & s ? VALUE_ZERO : CapturePieceValue[MG][piece_on(s)]); + } + + if (minAttacker == VALUE_INFINITE) + return VALUE_ZERO; + + result += minAttacker; + if (type_of(m) == DROP) + result -= CapturePieceValue[MG][dropped_piece_type(m)]; + } + + // Sum up blast piece values + while (blast) + { - Piece bpc = piece_on(pop_lsb(&blast)); ++ Piece bpc = piece_on(pop_lsb(blast)); + if (extinction_piece_types().find(type_of(bpc)) != extinction_piece_types().end()) + return color_of(bpc) == us ? extinction_value() + : capture(m) ? -extinction_value() + : VALUE_ZERO; + result += color_of(bpc) == us ? -CapturePieceValue[MG][bpc] : CapturePieceValue[MG][bpc]; + } + + return capture(m) || must_capture() ? result - 1 : std::min(result, VALUE_ZERO); +} + + /// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the /// SEE value of move is greater or equal to the given threshold. We'll use an /// algorithm similar to alpha-beta pruning with a null window. diff --cc src/position.h index c359cc6,d470ef9..4491b36 --- a/src/position.h +++ b/src/position.h @@@ -347,554 -203,6 +347,554 @@@ private 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(ALL_PIECES) && count() == 2; +} + +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 File Position::castling_king_file() const { + assert(var != nullptr); + return var->castlingKingFile; +} + +inline PieceType Position::castling_king_piece() const { + assert(var != nullptr); + return var->castlingKingPiece; +} + +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 PieceType Position::nnue_king() const { + assert(var != nullptr); + return var->nnueKing; +} + +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 { + // Check for cached value + if (st->legalCapture != NO_VALUE) + return st->legalCapture == VALUE_TRUE; + if (checkers()) + { + for (const auto& mevasion : MoveList(*this)) + if (capture(mevasion) && legal(mevasion)) + { + st->legalCapture = VALUE_TRUE; + return true; + } + } + else + { + for (const auto& mcap : MoveList(*this)) + if (capture(mcap) && legal(mcap)) + { + st->legalCapture = VALUE_TRUE; + return true; + } + } + st->legalCapture = VALUE_FALSE; + return false; +} + +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 == drop_no_doubled()) + for (File f = FILE_A; f <= max_file(); ++f) + if (popcount(file_bb(f) & pieces(c, pt)) >= var->dropNoDoubledCount) + 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); ++ 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 PieceType Position::drop_no_doubled() const { + assert(var != nullptr); + return var->dropNoDoubled; +} + +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); + } + // Check for checkmate of pseudo-royal pieces + if (var->extinctionPseudoRoyal) + { + Bitboard pseudoRoyals = st->pseudoRoyals & pieces(sideToMove); + Bitboard pseudoRoyalsTheirs = st->pseudoRoyals & pieces(~sideToMove); + while (pseudoRoyals) + { - Square sr = pop_lsb(&pseudoRoyals); ++ Square sr = pop_lsb(pseudoRoyals); + if ( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) + && attackers_to(sr, ~sideToMove)) + return convert_mate_value(var->checkmateValue, 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; + } + // Checkmate using virtual pieces + if (two_boards() && var->checkmateValue < VALUE_ZERO) + { + Value virtualMaterial = VALUE_ZERO; + for (PieceType pt : piece_types()) + virtualMaterial += std::max(-count_in_hand(~sideToMove, pt), 0) * PieceValue[MG][pt]; + + if (virtualMaterial > 0) + return -VALUE_VIRTUAL_MATE + virtualMaterial / 20 + ply; + } + // 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; } diff --cc src/search.cpp index c4a71d2,f23db4c..e49fd1f --- a/src/search.cpp +++ b/src/search.cpp @@@ -1417,15 -1272,15 +1420,15 @@@ moves_loop: // When in check, search st // the 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)] - 3833) / 16384; + + (*contHist[0])[history_slot(movedPiece)][to_sq(move)] - 3833) / 16384; else - r -= ss->statScore / 14790; + r -= ss->statScore / (14790 - 4434 * pos.captures_to_hand()); } - // In general we want to cap the LMR depth search at newDepth. But for nodes - // close to the principal variation the cap is at (newDepth + 1), which will - // allow these nodes to be searched deeper than the pv (up to 4 plies deeper). - Depth d = std::clamp(newDepth - r, 1, newDepth + ((ss+1)->distanceFromPv <= 4)); + // In general we want to cap the LMR depth search at newDepth. But if + // reductions are really negative and movecount is low, we allow this move + // to be searched deeper than the first move. + Depth d = std::clamp(newDepth - r, 1, newDepth + (r < -1 && moveCount <= 5)); value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); @@@ -1556,8 -1411,9 +1559,9 @@@ assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); if (!moveCount) - bestValue = excludedMove ? alpha - : ss->inCheck ? pos.checkmate_value(ss->ply) : pos.stalemate_value(ss->ply); + bestValue = excludedMove ? alpha : - ss->inCheck ? mated_in(ss->ply) - : VALUE_DRAW; ++ ss->inCheck ? pos.checkmate_value(ss->ply) ++ : pos.stalemate_value(ss->ply); // If there is a move which produces search value greater than alpha we update stats of searched moves else if (bestMove) diff --cc src/types.h index 4f4aee7,efebce1..f2f14a3 --- a/src/types.h +++ b/src/types.h @@@ -409,36 -204,18 +409,38 @@@ static_assert(2 * SQUARE_BITS + MOVE_TY enum Piece { NO_PIECE, - W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, - PIECE_NB = 16 ++ W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING = KING, ++ B_PAWN = PAWN + PIECE_TYPE_NB, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING = KING + PIECE_TYPE_NB, + PIECE_NB = 2 * PIECE_TYPE_NB }; -constexpr Value PieceValue[PHASE_NB][PIECE_NB] = { - { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO, - VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO }, - { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO, - VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO } +enum RiderType : int { + NO_RIDER = 0, + RIDER_BISHOP = 1 << 0, + RIDER_ROOK_H = 1 << 1, + RIDER_ROOK_V = 1 << 2, + RIDER_CANNON_H = 1 << 3, + RIDER_CANNON_V = 1 << 4, + RIDER_HORSE = 1 << 5, + RIDER_ELEPHANT = 1 << 6, + RIDER_JANGGI_ELEPHANT = 1 << 7, + RIDER_CANNON_DIAG = 1 << 8, + RIDER_NIGHTRIDER = 1 << 9, + RIDER_GRASSHOPPER_H = 1 << 10, + RIDER_GRASSHOPPER_V = 1 << 11, + RIDER_GRASSHOPPER_D = 1 << 12, + HOPPING_RIDERS = RIDER_CANNON_H | RIDER_CANNON_V | RIDER_CANNON_DIAG + | RIDER_GRASSHOPPER_H | RIDER_GRASSHOPPER_V | RIDER_GRASSHOPPER_D, + LAME_LEAPERS = RIDER_HORSE | RIDER_ELEPHANT | RIDER_JANGGI_ELEPHANT, + ASYMMETRICAL_RIDERS = RIDER_HORSE | RIDER_JANGGI_ELEPHANT + | RIDER_GRASSHOPPER_H | RIDER_GRASSHOPPER_V | RIDER_GRASSHOPPER_D, + NON_SLIDING_RIDERS = HOPPING_RIDERS | LAME_LEAPERS | RIDER_NIGHTRIDER, }; +extern Value PieceValue[PHASE_NB][PIECE_NB]; +extern Value EvalPieceValue[PHASE_NB][PIECE_NB]; // variant piece values for evaluation +extern Value CapturePieceValue[PHASE_NB][PIECE_NB]; // variant piece values for captures/search + typedef int Depth; enum : int {