+# 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)
--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef APIUTIL_H_INCLUDED
+#define APIUTIL_H_INCLUDED
+
+#include <vector>
+#include <string>
+#include <sstream>
+#include <cctype>
+#include <iostream>
+#include <math.h>
+
+#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<PIECE_PROMOTION>(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<LEGAL>(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<char> 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<char>(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<CharSquare> get_squares_for_piece(char piece) const {
+ std::vector<CharSquare> 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<std::string> get_fen_parts(const std::string& fullFen, char delim) {
+ std::vector<std::string> 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<CharSquare, 2>& 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<std::string, 2>& 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<std::string, 2>& 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<std::string, 2>& castlingInfoSplitted, const CharBoard& board,
+ const std::array<CharSquare, 2>& kingPositions, const std::array<CharSquare, 2>& kingPositionsStart,
+ const std::array<std::vector<CharSquare>, 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<std::string> fenParts = get_fen_parts(fen, ' ');
+ std::vector<std::string> 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<CharSquare, 2> 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<std::string, 2> 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<CharSquare, 2> 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<CharSquare, 2> 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<std::vector<CharSquare>, 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
template<> Value Endgame<KNNK>::operator()(const Position&) const { return VALUE_DRAW; }
+/// KFsPs vs K.
+template<>
+Value Endgame<KFsPsK>::operator()(const Position& pos) const {
+
+ assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
+
+ Square winnerKSq = pos.square<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(weakSide);
+
+ Value result = pos.non_pawn_material(strongSide)
+ + pos.count<PAWN>(strongSide) * PawnValueEg
+ + push_to_edge(loserKSq, pos)
+ + push_close(winnerKSq, loserKSq);
+
+ if ( pos.count<FERS>(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<FERS>(strongSide) + pos.count<PAWN>(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<KNSK>::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<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(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<KNFK>::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<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(weakSide);
+ Square fersSq = pos.square<FERS>(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<KNSFKR>::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<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(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<KSFK>::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<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(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<KSFKF>::operator()(const Position& pos) const {
+
+ assert(verify_material(pos, strongSide, SilverValueMg + FersValueMg, 0));
+ assert(verify_material(pos, weakSide, FersValueMg, 0));
+
+ Square winnerKSq = pos.square<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(weakSide);
+ Square fersSq = pos.square<FERS>(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<KRKS>::operator()(const Position& pos) const {
+
+ assert(verify_material(pos, strongSide, RookValueMg, 0));
+ assert(verify_material(pos, weakSide, SilverValueMg, 0));
+
+ Square winnerKSq = pos.square<KING>(strongSide);
+ Square loserKSq = pos.square<KING>(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
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<KING>(s) | s) & pos.pieces(Them, pt)) && !(attacks_bb<KING>(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.
}
} // 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)
}
+ // Evaluation::variant() computes variant-specific evaluation bonuses for a given side.
+
+ template<Tracing T> template<Color Us>
+ Score Evaluation<T>::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<Down>(pos.pieces()) | attackedBy[Them][ALL_PIECES]))
+ | (pos.pieces(Them) & pe->pawn_attacks(Them))
+ | (pawn_attacks_bb<Them>(pos.pieces(Them, PAWN) & pe->pawn_attacks(Them)));
+ Bitboard inaccessible = pos.pieces(Us, PAWN) & shift<Down>(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<ALL_PIECES>(Us);
+ }
+ else if (pos.count<PAWN>(Us) == pos.count<ALL_PIECES>(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<EAST>(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<PAWN>(Them) == pos.count<ALL_PIECES>(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<PAWN>(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.
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.
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<PAWN>();
- int v2 = VALUE_ZERO;
+ auto adjusted_NNUE = [&]()
+ {
+ int material = pos.non_pawn_material() + 4 * PawnValueMg * pos.count<PAWN>();
+ 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
namespace {
- template<GenType Type, Direction D>
- ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) {
+ template<MoveType T>
+ 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<PROMOTION>(to - D, to, QUEEN);
- if (attacks_bb<KNIGHT>(to) & ksq)
- *moveList++ = make<PROMOTION>(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<T>(from, to, pt_gating, pop_lsb(&b));
++ *moveList++ = make_gating<T>(from, to, pt_gating, pop_lsb(b));
+ }
+ return moveList;
}
- if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
+ *moveList++ = make<T>(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<T>(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<T>(from, to, pt_gating, to);
+
+ return moveList;
+ }
+
+ template<Color c, GenType Type, Direction D>
+ ExtMove* make_promotions(const Position& pos, ExtMove* moveList, Square to) {
+
+ if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
{
- *moveList++ = make<PROMOTION>(to - D, to, ROOK);
- *moveList++ = make<PROMOTION>(to - D, to, BISHOP);
- if (!(attacks_bb<KNIGHT>(to) & ksq))
- *moveList++ = make<PROMOTION>(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<PROMOTION>(to - D, to, pt);
+ PieceType pt = pos.promoted_piece_type(PAWN);
+ if (pt && !(pos.piece_promotion_on_capture() && pos.empty(to)))
+ *moveList++ = make<PIECE_PROMOTION>(to - D, to);
}
return moveList;
}
+ template<Color Us, GenType Type>
+ 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<Color Us, GenType Type>
ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
Bitboard b3 = shift<Up >(pawnsOn7) & emptySquares;
while (b1)
- moveList = make_promotions<Us, Type, UpRight>(pos, moveList, pop_lsb(&b1));
- moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(b1), ksq);
++ moveList = make_promotions<Us, Type, UpRight>(pos, moveList, pop_lsb(b1));
while (b2)
- moveList = make_promotions<Us, Type, UpLeft >(pos, moveList, pop_lsb(&b2));
- moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(b2), ksq);
++ moveList = make_promotions<Us, Type, UpLeft >(pos, moveList, pop_lsb(b2));
while (b3)
- moveList = make_promotions<Us, Type, Up >(pos, moveList, pop_lsb(&b3));
- moveList = make_promotions<Type, Up >(moveList, pop_lsb(b3), ksq);
++ moveList = make_promotions<Us, Type, Up >(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<PAWN>(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<PROMOTION>(from, to, pt);
+ }
+ }
+ }
}
// Standard and en passant captures
Bitboard bb = piecesToMove & pos.pieces(Pt);
- while (bb) {
- Square from = pop_lsb(&bb);
+ while (bb)
+ {
+ Square from = pop_lsb(bb);
- Bitboard b = attacks_bb<Pt>(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<NORMAL>(pos, moveList, us, from, pop_lsb(&b1));
++ moveList = make_move_and_gating<NORMAL>(pos, moveList, us, from, pop_lsb(b1));
+
+ // Shogi-style piece promotions
+ while (b2)
- *moveList++ = make<PIECE_PROMOTION>(from, pop_lsb(&b2));
++ *moveList++ = make<PIECE_PROMOTION>(from, pop_lsb(b2));
+
+ // Piece demotions
+ while (b3)
- *moveList++ = make<PIECE_DEMOTION>(from, pop_lsb(&b3));
++ *moveList++ = make<PIECE_DEMOTION>(from, pop_lsb(b3));
}
return moveList;
target = ~pos.pieces(Us);
break;
}
+ target &= pos.board_bb();
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
- moveList = generate_moves<KNIGHT, Checks>(pos, moveList, piecesToMove, target);
- moveList = generate_moves<BISHOP, Checks>(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<Checks>(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<Us, Type>(pos, moveList, pt, target & ~pos.pieces(~Us));
+
+ if (Type != QUIET_CHECKS && Type != EVASIONS && pos.count<KING>(Us))
{
Square ksq = pos.square<KING>(Us);
- Bitboard b = attacks_bb<KING>(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<NORMAL>(pos, moveList, Us, ksq, pop_lsb(&b));
- *moveList++ = make_move(ksq, pop_lsb(b));
++ moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, ksq, pop_lsb(b));
+
+ // Passing move by king
+ if (pos.pass())
+ *moveList++ = make<SPECIAL>(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<CASTLING>(ksq, pos.castling_rook_square(cr));
+ moveList = make_move_and_gating<CASTLING>(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<KING>(Us) && pos.pieces(Us))
+ *moveList++ = make<SPECIAL>(lsb(pos.pieces(Us)), lsb(pos.pieces(Us)));
+
+ // Castling with non-king piece
+ if (!pos.count<KING>(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<CASTLING>(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<KING>(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<SPECIAL>(pos, moveList, Us, from, pop_lsb(&b));
++ moveList = make_move_and_gating<SPECIAL>(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<SPECIAL>(pos, moveList, Us, from, to);
+ }
}
return moveList;
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<QUEEN>(pos.square<KING>(~us));
while (b)
- moveList = make_move_and_gating<NORMAL>(pos, moveList, us, from, pop_lsb(&b));
- *moveList++ = make_move(from, pop_lsb(b));
++ moveList = make_move_and_gating<NORMAL>(pos, moveList, us, from, pop_lsb(b));
}
return us == WHITE ? generate_all<WHITE, QUIET_CHECKS>(pos, moveList)
Color us = pos.side_to_move();
Square ksq = pos.square<KING>(us);
+ Bitboard sliderAttacks = 0;
+ Bitboard sliders = pos.checkers();
+
+ // Passing move by king in bikjang
+ if (pos.bikjang() && pos.pass())
+ *moveList++ = make<SPECIAL>(ksq, ksq);
- // Generate evasions for king
- Bitboard b = attacks_bb<KING>(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<NORMAL>(pos, moveList, us, ksq, pop_lsb(&b));
++ moveList = make_move_and_gating<NORMAL>(pos, moveList, us, ksq, pop_lsb(b));
+ return us == WHITE ? generate_all<WHITE, EVASIONS>(pos, moveList)
+ : generate_all<BLACK, EVASIONS>(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<NORMAL>(pos, moveList, us, ksq, pop_lsb(&b));
- *moveList++ = make_move(ksq, pop_lsb(b));
++ moveList = make_move_and_gating<NORMAL>(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
--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+//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 <Side AssociatedKing>
+ void HalfKPShogi<AssociatedKing>::AppendActiveIndices(
+ const Position& pos, Color perspective, IndexList* active) {
+
+ Square ksq = orient(perspective, pos.square<KING>(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 <Side AssociatedKing>
+ void HalfKPShogi<AssociatedKing>::AppendChangedIndices(
+ const Position& pos, const DirtyPiece& dp, Color perspective,
+ IndexList* removed, IndexList* added) {
+
+ Square ksq = orient(perspective, pos.square<KING>(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<Side::kFriend>;
+
+} // namespace Stockfish::Eval::NNUE::Features
--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+//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 <Side AssociatedKing>
+ void HalfKPVariants<AssociatedKing>::AppendActiveIndices(
+ const Position& pos, Color perspective, IndexList* active) {
+
+ // Re-route to shogi features
+#ifdef LARGEBOARDS
+ if (currentNnueFeatures == NNUE_SHOGI)
+ {
+ assert(HalfKPShogi<AssociatedKing>::kDimensions <= kDimensions);
+ return HalfKPShogi<AssociatedKing>::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 <Side AssociatedKing>
+ void HalfKPVariants<AssociatedKing>::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<AssociatedKing>::kDimensions <= kDimensions);
+ return HalfKPShogi<AssociatedKing>::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<Side::kFriend>;
+
+} // namespace Stockfish::Eval::NNUE::Features
<< 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;
// 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<KING>(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<KING>(~sideToMove)) & pieces(sideToMove);
- st->previous->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square<KING>(WHITE), st->previous->pinners[BLACK]);
- st->previous->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square<KING>(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;
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<BISHOP>(s) & pieces(QUEEN, BISHOP))) & sliders;
+ Bitboard snipers = 0;
+
+ if (var->fastAttacks)
+ snipers = ( (attacks_bb< ROOK>(s) & pieces(c, QUEEN, ROOK, CHANCELLOR))
+ | (attacks_bb<BISHOP>(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;
/// 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<KNIGHT>(s) & pieces(c, KNIGHT, ARCHBISHOP, CHANCELLOR))
+ | (attacks_bb< ROOK>(s, occupied) & pieces(c, ROOK, QUEEN, CHANCELLOR))
+ | (attacks_bb<BISHOP>(s, occupied) & pieces(c, BISHOP, QUEEN, ARCHBISHOP))
+ | (attacks_bb<KING>(s) & pieces(c, KING, COMMONER));
+ }
- return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN))
- | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN))
- | (attacks_bb<KNIGHT>(s) & pieces(KNIGHT))
- | (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN))
- | (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
- | (attacks_bb<KING>(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<KNIGHT>(s) & pieces(c, KNIGHT))
+ | (attacks_bb< ROOK>(s, occupied) & ( pieces(c, ROOK, QUEEN, DRAGON)
+ | (pieces(c, LANCE) & PseudoAttacks[~c][LANCE][s])))
+ | (attacks_bb<BISHOP>(s, occupied) & pieces(c, BISHOP, QUEEN, DRAGON_HORSE))
+ | (attacks_bb<KING>(s) & pieces(c, KING, COMMONER))
+ | (attacks_bb<FERS>(s) & pieces(c, FERS, DRAGON, SILVER))
+ | (attacks_bb<WAZIR>(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<RIDER_BISHOP>(s, occupied) & pieces(c, ROOK);
+ diags |= rider_attacks_bb<RIDER_CANNON_DIAG>(s, occupied)
+ & rider_attacks_bb<RIDER_CANNON_DIAG>(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);
}
Square to = to_sq(m);
assert(color_of(moved_piece(m)) == us);
- assert(piece_on(square<KING>(us)) == make_piece(us, KING));
+ assert(!count<KING>(us) || piece_on(square<KING>(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<EVASIONS>(*this))
+ if (type_of(mevasion) == DROP && legal(mevasion))
+ return false;
+ }
+ else
+ {
+ for (const auto& mquiet : MoveList<QUIETS>(*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<NON_EVASIONS>(*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<KING>(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<KING>(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<KING>(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.
// 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<KING>(us)) || st->bikjang)
+ {
+ Square s = type_of(moved_piece(m)) == KING ? to : square<KING>(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<KING>(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<KING>(us));
+ // A non-king move is legal if the king is not under attack after the move.
+ return !(attackers_to(square<KING>(us), occupied, ~us, janggiCannons) & ~SquareBB[to]);
}
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)
{
// 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<KING>(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<KING>(them)) & pieces(us) : 0;
+ st->checkersBB = givesCheck ? attackers_to(square<KING>(them), us) & pieces(us) : Bitboard(0);
sideToMove = ~sideToMove;
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<KING>(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);
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);
}
}
}
+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<KING>(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.
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<PieceType>& 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<PieceType, std::greater<PieceType> >& 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<KING>() == 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<EVASIONS>(*this))
+ if (capture(mevasion) && legal(mevasion))
+ {
+ st->legalCapture = VALUE_TRUE;
+ return true;
+ }
+ }
+ else
+ {
+ for (const auto& mcap : MoveList<CAPTURES>(*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<NORTH>(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<NORTH >(theirs) | shift<SOUTH >(theirs)
+ | shift<NORTH_EAST>(theirs) | shift<SOUTH_WEST>(theirs)
+ | shift<EAST >(theirs) | shift<WEST >(theirs)
+ | shift<SOUTH_EAST>(theirs) | shift<NORTH_WEST>(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<NORTH >(ours) | shift<SOUTH >(ours)
+ | shift<NORTH_EAST>(ours) | shift<SOUTH_WEST>(ours)
+ | shift<EAST >(ours) | shift<WEST >(ours)
+ | shift<SOUTH_EAST>(ours) | shift<NORTH_WEST>(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<ALL_PIECES>(sideToMove) - count<ALL_PIECES>(~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<KING>(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<PieceType>& 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;
}
// 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<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
assert(moveCount || !ss->inCheck || excludedMove || !MoveList<LEGAL>(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)
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 {