Merge official-stockfish/master
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 6 Jun 2021 16:55:34 +0000 (18:55 +0200)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sun, 6 Jun 2021 16:55:34 +0000 (18:55 +0200)
bench: 5392690

22 files changed:
1  2 
AUTHORS
README.md
src/Makefile
src/apiutil.h
src/bitbase.cpp
src/bitboard.h
src/endgame.cpp
src/evaluate.cpp
src/material.cpp
src/misc.cpp
src/movegen.cpp
src/nnue/features/half_kp.cpp
src/nnue/features/half_kp_shogi.cpp
src/nnue/features/half_kp_variants.cpp
src/pawns.cpp
src/position.cpp
src/position.h
src/search.cpp
src/search.h
src/syzygy/tbprobe.cpp
src/thread.cpp
src/types.h

diff --cc AUTHORS
+++ b/AUTHORS
@@@ -1,20 -1,4 +1,21 @@@
 +# Fairy-Stockfish authors
 +
 +# Main author
 +Fabian Fichter (ianfab)
 +
 +# Contributors in alphabetical order
 +alwey
++Belzedar94
 +Fulmene
 +gbtami
 +QueensGambit
 +tttak
 +yoav-rozin
 +ydirson
 +
 +
 +
- # List of authors for Stockfish, as of August 4, 2020
+ # List of authors for Stockfish, as of March 31, 2021
  
  # Founders of the Stockfish project and fishtest infrastructure
  Tord Romstad (romstad)
diff --cc README.md
Simple merge
diff --cc src/Makefile
Simple merge
diff --cc src/apiutil.h
index b1d3621,0000000..d3c5aa8
mode 100644,000000..100644
--- /dev/null
@@@ -1,933 -1,0 +1,933 @@@
 +/*
 +  Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish
 +  Copyright (C) 2018-2021 Fabian Fichter
 +
 +  Fairy-Stockfish is free software: you can redistribute it and/or modify
 +  it under the terms of the GNU General Public License as published by
 +  the Free Software Foundation, either version 3 of the License, or
 +  (at your option) any later version.
 +
 +  Fairy-Stockfish is distributed in the hope that it will be useful,
 +  but WITHOUT ANY WARRANTY; without even the implied warranty of
 +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +  GNU General Public License for more details.
 +
 +  You should have received a copy of the GNU General Public License
 +  along with this program.  If not, see <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
diff --cc src/bitbase.cpp
Simple merge
diff --cc src/bitboard.h
Simple merge
diff --cc src/endgame.cpp
@@@ -342,169 -313,6 +342,169 @@@ Value Endgame<KNNKP>::operator()(const 
  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
@@@ -844,60 -639,8 +844,60 @@@ namespace 
      Bitboard b, weak, defended, nonPawnEnemies, stronglyProtected, safe;
      Score score = SCORE_ZERO;
  
 +    // Bonuses for variants with mandatory captures
 +    if (pos.must_capture())
 +    {
 +        // Penalties for possible captures
 +        Bitboard captures = attackedBy[Us][ALL_PIECES] & pos.pieces(Them);
 +        if (captures)
 +            score -= make_score(2000, 2000) / (1 + popcount(captures & attackedBy[Them][ALL_PIECES] & ~attackedBy2[Us]));
 +
 +        // Bonus if we threaten to force captures
 +        Bitboard moves = 0, piecebb = pos.pieces(Us);
 +        while (piecebb)
 +        {
-             Square s = pop_lsb(&piecebb);
++            Square s = pop_lsb(piecebb);
 +            if (type_of(pos.piece_on(s)) != KING)
 +                moves |= pos.moves_from(Us, type_of(pos.piece_on(s)), s);
 +        }
 +        score += make_score(200, 200) * popcount(attackedBy[Them][ALL_PIECES] & moves & ~pos.pieces());
 +        score += make_score(200, 220) * popcount(attackedBy[Them][ALL_PIECES] & moves & ~pos.pieces() & ~attackedBy2[Us]);
 +    }
 +
 +    // Extinction threats
 +    if (pos.extinction_value() == -VALUE_MATE)
 +    {
 +        Bitboard bExt = attackedBy[Us][ALL_PIECES] & pos.pieces(Them);
 +        for (PieceType pt : pos.extinction_piece_types())
 +        {
 +            if (pt == ALL_PIECES)
 +                continue;
 +            int denom = std::max(pos.count_with_hand(Them, pt) - pos.extinction_piece_count(), 1);
 +            // Explosion threats
 +            if (pos.blast_on_capture())
 +            {
 +                int evasions = popcount(((attackedBy[Them][pt] & ~pos.pieces(Them)) | pos.pieces(Them, pt)) & ~attackedBy[Us][ALL_PIECES]) * denom;
 +                int attacks = popcount((attackedBy[Them][pt] | pos.pieces(Them, pt)) & attackedBy[Us][ALL_PIECES]);
 +                int explosions = 0;
 +
 +                Bitboard bExtBlast = bExt & (attackedBy2[Us] | ~attackedBy[Us][pt]);
 +                while (bExtBlast)
 +                {
-                     Square s = pop_lsb(&bExtBlast);
++                    Square s = pop_lsb(bExtBlast);
 +                    if (((attacks_bb<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(&current);
++                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.
@@@ -1518,16 -1038,45 +1518,52 @@@ make_v
      return v;
    }
  
- } // namespace
+   /// Fisher Random Chess: correction for cornered bishops, to fix chess960 play with NNUE
+   Value fix_FRC(const Position& pos) {
 -    constexpr Bitboard Corners =  1ULL << SQ_A1 | 1ULL << SQ_H1 | 1ULL << SQ_A8 | 1ULL << SQ_H8;
++    constexpr Bitboard Corners =  Bitboard(1ULL) << SQ_A1 | Bitboard(1ULL) << SQ_H1 | Bitboard(1ULL) << SQ_A8 | Bitboard(1ULL) << SQ_H8;
+     if (!(pos.pieces(BISHOP) & Corners))
+         return VALUE_ZERO;
+     int correction = 0;
+     if (   pos.piece_on(SQ_A1) == W_BISHOP
+         && pos.piece_on(SQ_B2) == W_PAWN)
+         correction += !pos.empty(SQ_B3) ? -CorneredBishop * 4
+                                         : -CorneredBishop * 3;
+     if (   pos.piece_on(SQ_H1) == W_BISHOP
+         && pos.piece_on(SQ_G2) == W_PAWN)
+         correction += !pos.empty(SQ_G3) ? -CorneredBishop * 4
+                                         : -CorneredBishop * 3;
+     if (   pos.piece_on(SQ_A8) == B_BISHOP
+         && pos.piece_on(SQ_B7) == B_PAWN)
+         correction += !pos.empty(SQ_B6) ? CorneredBishop * 4
+                                         : CorneredBishop * 3;
+     if (   pos.piece_on(SQ_H8) == B_BISHOP
+         && pos.piece_on(SQ_G7) == B_PAWN)
+         correction += !pos.empty(SQ_G6) ? CorneredBishop * 4
+                                         : CorneredBishop * 3;
+     return pos.side_to_move() == WHITE ?  Value(correction)
+                                        : -Value(correction);
+   }
+ } // namespace Eval
  
  
 +/// tempo_value() returns the evaluation offset for the side to move
 +
 +Value Eval::tempo_value(const Position& pos) {
 +  return Tempo * (1 + 4 * pos.captures_to_hand());
 +}
 +
 +
  /// evaluate() is the evaluator for the outer world. It returns a static
  /// evaluation of the position from the point of view of the side to move.
  
@@@ -1540,16 -1089,19 +1576,26 @@@ Value Eval::evaluate(const Position& po
    else
    {
        // Scale and shift NNUE for compatibility with search and classical evaluation
-       auto  adjusted_NNUE = [&](){
-          int mat = pos.non_pawn_material() + 2 * PawnValueMg * pos.count<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
Simple merge
diff --cc src/misc.cpp
Simple merge
diff --cc src/movegen.cpp
@@@ -25,79 -25,27 +25,79 @@@ namespace Stockfish 
  
  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;
@@@ -470,16 -287,16 +471,16 @@@ ExtMove* generate<QUIET_CHECKS>(const P
  
    while (dc)
    {
-      Square from = pop_lsb(&dc);
+      Square from = pop_lsb(dc);
       PieceType pt = type_of(pos.piece_on(from));
  
 -     Bitboard b = attacks_bb(pt, from, pos.pieces()) & ~pos.pieces();
 +     Bitboard b = pos.moves_from(us, pt, from) & ~pos.pieces();
  
 -     if (pt == KING)
 +     if (pt == KING && pos.king_type() == KING)
           b &= ~attacks_bb<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)
@@@ -496,39 -313,11 +497,39 @@@ ExtMove* generate<EVASIONS>(const Posit
  
    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
Simple merge
index 3c9f551,0000000..5fc920e
mode 100644,000000..100644
--- /dev/null
@@@ -1,98 -1,0 +1,98 @@@
 +/*
 +  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
 +  Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
 +
 +  Stockfish is free software: you can redistribute it and/or modify
 +  it under the terms of the GNU General Public License as published by
 +  the Free Software Foundation, either version 3 of the License, or
 +  (at your option) any later version.
 +
 +  Stockfish is distributed in the hope that it will be useful,
 +  but WITHOUT ANY WARRANTY; without even the implied warranty of
 +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +  GNU General Public License for more details.
 +
 +  You should have received a copy of the GNU General Public License
 +  along with this program.  If not, see <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
index cbfe0a3,0000000..0eca178
mode 100644,000000..100644
--- /dev/null
@@@ -1,96 -1,0 +1,96 @@@
 +/*
 +  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
 +  Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
 +
 +  Stockfish is free software: you can redistribute it and/or modify
 +  it under the terms of the GNU General Public License as published by
 +  the Free Software Foundation, either version 3 of the License, or
 +  (at your option) any later version.
 +
 +  Stockfish is distributed in the hope that it will be useful,
 +  but WITHOUT ANY WARRANTY; without even the implied warranty of
 +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +  GNU General Public License for more details.
 +
 +  You should have received a copy of the GNU General Public License
 +  along with this program.  If not, see <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
diff --cc src/pawns.cpp
Simple merge
@@@ -94,10 -73,9 +94,10 @@@ std::ostream& operator<<(std::ostream& 
       << std::setfill(' ') << std::dec << "\nCheckers: ";
  
    for (Bitboard b = pos.checkers(); b; )
-       os << UCI::square(pos, pop_lsb(&b)) << " ";
 -      os << UCI::square(pop_lsb(b)) << " ";
++      os << UCI::square(pos, pop_lsb(b)) << " ";
  
    if (    int(Tablebases::MaxCardinality) >= popcount(pos.pieces())
 +      && Options["UCI_Variant"] == "chess"
        && !pos.can_castle(ANY_CASTLING))
    {
        StateInfo st;
@@@ -309,185 -220,81 +309,185 @@@ Position& Position::set(const Variant* 
  
    // 2. Active color
    ss >> token;
 -  sideToMove = (token == 'w' ? WHITE : BLACK);
 +  sideToMove = (token != (sfen ? 'w' : 'b') ? WHITE : BLACK);  // Invert colors for SFEN
    ss >> token;
  
 -  // 3. Castling availability. Compatible with 3 standards: Normal FEN standard,
 -  // Shredder-FEN that uses the letters of the columns on which the rooks began
 -  // the game instead of KQkq and also X-FEN standard that, in case of Chess960,
 -  // if an inner rook is associated with the castling right, the castling tag is
 -  // replaced by the file letter of the involved rook, as for the Shredder-FEN.
 -  while ((ss >> token) && !isspace(token))
 +  // 3-4. Skip parsing castling and en passant flags if not present
 +  st->epSquare = SQ_NONE;
 +  st->castlingKingSquare[WHITE] = st->castlingKingSquare[BLACK] = SQ_NONE;
 +  if (!isdigit(ss.peek()) && !sfen)
    {
 -      Square rsq;
 -      Color c = islower(token) ? BLACK : WHITE;
 -      Piece rook = make_piece(c, ROOK);
 -
 -      token = char(toupper(token));
 +      // 3. Castling availability. Compatible with 3 standards: Normal FEN standard,
 +      // Shredder-FEN that uses the letters of the columns on which the rooks began
 +      // the game instead of KQkq and also X-FEN standard that, in case of Chess960,
 +      // if an inner rook is associated with the castling right, the castling tag is
 +      // replaced by the file letter of the involved rook, as for the Shredder-FEN.
 +      while ((ss >> token) && !isspace(token))
 +      {
 +          Square rsq;
 +          Color c = islower(token) ? BLACK : WHITE;
 +          Piece rook = make_piece(c, castling_rook_piece());
  
 -      if (token == 'K')
 -          for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) {}
 +          token = char(toupper(token));
  
 -      else if (token == 'Q')
 -          for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {}
 +          if (token == 'K')
 +              for (rsq = make_square(FILE_MAX, castling_rank(c)); piece_on(rsq) != rook; --rsq) {}
  
 -      else if (token >= 'A' && token <= 'H')
 -          rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1));
 +          else if (token == 'Q')
 +              for (rsq = make_square(FILE_A, castling_rank(c)); piece_on(rsq) != rook; ++rsq) {}
  
 -      else
 -          continue;
 +          else if (token >= 'A' && token <= 'A' + max_file())
 +              rsq = make_square(File(token - 'A'), castling_rank(c));
  
 -      set_castling_right(c, rsq);
 -  }
 +          else
 +              continue;
  
 -  set_state(st);
 +          // Determine castling "king" position
 +          if (castling_enabled() && st->castlingKingSquare[c] == SQ_NONE)
 +          {
 +              Bitboard castlingKings = pieces(c, castling_king_piece()) & rank_bb(castling_rank(c));
 +              // Ambiguity resolution for 960 variants with more than one "king"
 +              // e.g., EAH means that an e-file king can castle with a- and h-file rooks
 +              st->castlingKingSquare[c] =  isChess960 && piece_on(rsq) == make_piece(c, castling_king_piece()) ? rsq
 +                                         : castlingKings && (!more_than_one(castlingKings) || isChess960) ? lsb(castlingKings)
 +                                         : make_square(castling_king_file(), castling_rank(c));
 +          }
  
 -  // 4. En passant square.
 -  // Ignore if square is invalid or not on side to move relative rank 6.
 -  bool enpassant = false;
 +          // Set gates (and skip castling rights)
 +          if (gating())
 +          {
 +              st->gatesBB[c] |= rsq;
 +              if (token == 'K' || token == 'Q')
 +                  st->gatesBB[c] |= st->castlingKingSquare[c];
 +              // Do not set castling rights for gates unless there are no pieces in hand,
 +              // which means that the file is referring to a chess960 castling right.
 +              else if (!seirawan_gating() || count_in_hand(c, ALL_PIECES) > 0 || captures_to_hand())
 +                  continue;
 +          }
  
 -  if (   ((ss >> col) && (col >= 'a' && col <= 'h'))
 -      && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3'))))
 -  {
 -      st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
 +          if (castling_enabled() && piece_on(rsq) == rook)
 +              set_castling_right(c, rsq);
 +      }
  
 -      // En passant square will be considered only if
 -      // a) side to move have a pawn threatening epSquare
 -      // b) there is an enemy pawn in front of epSquare
 -      // c) there is no piece on epSquare or behind epSquare
 -      // d) enemy pawn didn't block a check of its own color by moving forward
 -      enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN)
 -               && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))
 -               && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove))))
 -               && (   file_of(square<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;
  
@@@ -773,48 -469,18 +773,48 @@@ Bitboard Position::slider_blockers(Bitb
    Bitboard blockers = 0;
    pinners = 0;
  
 +  if (s == SQ_NONE || !sliders)
 +      return blockers;
 +
    // Snipers are sliders that attack 's' when a piece and other snipers are removed
 -  Bitboard snipers = (  (attacks_bb<  ROOK>(s) & pieces(QUEEN, ROOK))
 -                      | (attacks_bb<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);
  }
  
  
@@@ -924,107 -515,7 +924,107 @@@ bool Position::legal(Move m) const 
    Square to = to_sq(m);
  
    assert(color_of(moved_piece(m)) == us);
 -  assert(piece_on(square<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]);
  }
  
  
@@@ -1469,85 -795,8 +1468,85 @@@ void Position::do_move(Move m, StateInf
        k ^= Zobrist::castling[st->castlingRights];
    }
  
 +  // Flip enclosed pieces
 +  st->flippedPieces = 0;
 +  if (flip_enclosed_pieces() && !is_pass(m))
 +  {
 +      // Find end of rows to be flipped
 +      if (flip_enclosed_pieces() == REVERSI)
 +      {
 +          Bitboard b = attacks_bb(us, QUEEN, to, board_bb() & ~pieces(~us)) & ~PseudoAttacks[us][KING][to] & pieces(us);
 +          while(b)
-               st->flippedPieces |= between_bb(to, pop_lsb(&b));
++              st->flippedPieces |= between_bb(to, pop_lsb(b));
 +      }
 +      else
 +      {
 +          assert(flip_enclosed_pieces() == ATAXX);
 +          st->flippedPieces = PseudoAttacks[us][KING][to] & pieces(~us);
 +      }
 +
 +      // Flip pieces
 +      Bitboard to_flip = st->flippedPieces;
 +      while(to_flip)
 +      {
-           Square s = pop_lsb(&to_flip);
++          Square s = pop_lsb(to_flip);
 +          Piece flipped = piece_on(s);
 +          Piece resulting = ~flipped;
 +
 +          // remove opponent's piece
 +          remove_piece(s);
 +          k ^= Zobrist::psq[flipped][s];
 +          st->materialKey ^= Zobrist::psq[flipped][pieceCount[flipped]];
 +          st->nonPawnMaterial[them] -= PieceValue[MG][flipped];
 +
 +          // add our piece
 +          put_piece(resulting, s);
 +          k ^= Zobrist::psq[resulting][s];
 +          st->materialKey ^= Zobrist::psq[resulting][pieceCount[resulting]-1];
 +          st->nonPawnMaterial[us] += PieceValue[MG][resulting];
 +      }
 +  }
 +
    // Move the piece. The tricky Chess960 castling is handled earlier
 -  if (type_of(m) != CASTLING)
 +  if (type_of(m) == DROP)
 +  {
 +      if (Eval::useNNUE)
 +      {
 +          // Add drop piece
 +          dp.piece[0] = pc;
 +          dp.handPiece[0] = make_piece(us, in_hand_piece_type(m));
 +          dp.from[0] = SQ_NONE;
 +          dp.to[0] = to;
 +      }
 +
 +      drop_piece(make_piece(us, in_hand_piece_type(m)), pc, to);
 +      st->materialKey ^= Zobrist::psq[pc][pieceCount[pc]-1];
 +      if (type_of(pc) != PAWN)
 +          st->nonPawnMaterial[us] += PieceValue[MG][pc];
 +      // Set castling rights for dropped king or rook
 +      if (castling_dropped_piece() && rank_of(to) == castling_rank(us))
 +      {
 +          if (type_of(pc) == castling_king_piece() && file_of(to) == castling_king_file())
 +          {
 +              st->castlingKingSquare[us] = to;
 +              Bitboard castling_rooks =  pieces(us, castling_rook_piece())
 +                                       & rank_bb(castling_rank(us))
 +                                       & (file_bb(FILE_A) | file_bb(max_file()));
 +              while (castling_rooks)
-                   set_castling_right(us, pop_lsb(&castling_rooks));
++                  set_castling_right(us, pop_lsb(castling_rooks));
 +          }
 +          else if (type_of(pc) == castling_rook_piece())
 +          {
 +              if (   (file_of(to) == FILE_A || file_of(to) == max_file())
 +                  && piece_on(make_square(castling_king_file(), castling_rank(us))) == make_piece(us, castling_king_piece()))
 +              {
 +                  st->castlingKingSquare[us] = make_square(castling_king_file(), castling_rank(us));
 +                  set_castling_right(us, to);
 +              }
 +          }
 +      }
 +  }
 +  else if (type_of(m) != CASTLING)
    {
        if (Eval::useNNUE)
        {
    // 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;
  
@@@ -1823,51 -907,14 +1822,51 @@@ void Position::undo_move(Move m) 
    Square to = to_sq(m);
    Piece pc = piece_on(to);
  
 -  assert(empty(from) || type_of(m) == CASTLING);
 +  assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || is_gating(m)
 +         || (type_of(m) == PROMOTION && sittuyin_promotion())
 +         || (is_pass(m) && pass()));
    assert(type_of(st->capturedPiece) != KING);
  
 +  // Add the blast pieces
 +  if (st->capturedPiece && blast_on_capture())
 +  {
 +      Bitboard blast = attacks_bb<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);
        }
    }
  
@@@ -2059,53 -1055,6 +2058,53 @@@ Key Position::key_after(Move m) const 
  }
  
  
 +Value Position::blast_see(Move m) const {
 +  assert(is_ok(m));
 +
 +  Square from = from_sq(m);
 +  Square to = to_sq(m);
 +  Color us = color_of(moved_piece(m));
 +  Bitboard fromto = type_of(m) == DROP ? square_bb(to) : from | to;
 +  Bitboard blast = ((attacks_bb<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.
diff --cc src/position.h
@@@ -347,554 -203,6 +347,554 @@@ private
  
  extern std::ostream& operator<<(std::ostream& os, const Position& pos);
  
 +inline const Variant* Position::variant() const {
 +  assert(var != nullptr);
 +  return var;
 +}
 +
 +inline Rank Position::max_rank() const {
 +  assert(var != nullptr);
 +  return var->maxRank;
 +}
 +
 +inline File Position::max_file() const {
 +  assert(var != nullptr);
 +  return var->maxFile;
 +}
 +
 +inline bool Position::two_boards() const {
 +  assert(var != nullptr);
 +  return var->twoBoards;
 +}
 +
 +inline Bitboard Position::board_bb() const {
 +  assert(var != nullptr);
 +  return board_size_bb(var->maxFile, var->maxRank);
 +}
 +
 +inline Bitboard Position::board_bb(Color c, PieceType pt) const {
 +  assert(var != nullptr);
 +  return var->mobilityRegion[c][pt] ? var->mobilityRegion[c][pt] & board_bb() : board_bb();
 +}
 +
 +inline const std::set<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;
  }
diff --cc src/search.cpp
@@@ -1417,15 -1272,15 +1420,15 @@@ moves_loop: // When in check, search st
                // the sum of main history and first continuation history with an offset.
                if (ss->inCheck)
                    r -= (thisThread->mainHistory[us][from_to(move)]
 -                     + (*contHist[0])[movedPiece][to_sq(move)] - 3833) / 16384;
 +                     + (*contHist[0])[history_slot(movedPiece)][to_sq(move)] - 3833) / 16384;
                else
 -                  r -= ss->statScore / 14790;
 +                  r -= ss->statScore / (14790 - 4434 * pos.captures_to_hand());
            }
  
-           // In general we want to cap the LMR depth search at newDepth. But for nodes
-           // close to the principal variation the cap is at (newDepth + 1), which will
-           // allow these nodes to be searched deeper than the pv (up to 4 plies deeper).
-           Depth d = std::clamp(newDepth - r, 1, newDepth + ((ss+1)->distanceFromPv <= 4));
+           // In general we want to cap the LMR depth search at newDepth. But if
+           // reductions are really negative and movecount is low, we allow this move
+           // to be searched deeper than the first move.
+           Depth d = std::clamp(newDepth - r, 1, newDepth + (r < -1 && moveCount <= 5));
  
            value = -search<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)
diff --cc src/search.h
Simple merge
Simple merge
diff --cc src/thread.cpp
Simple merge
diff --cc src/types.h
@@@ -409,36 -204,18 +409,38 @@@ static_assert(2 * SQUARE_BITS + MOVE_TY
  
  enum Piece {
    NO_PIECE,
 -  W_PAWN = PAWN,     W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
 -  B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
 -  PIECE_NB = 16
++  W_PAWN = PAWN,                 W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING = KING,
++  B_PAWN = PAWN + PIECE_TYPE_NB, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING = KING + PIECE_TYPE_NB,
 +  PIECE_NB = 2 * PIECE_TYPE_NB
  };
  
 -constexpr Value PieceValue[PHASE_NB][PIECE_NB] = {
 -  { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO,
 -    VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO },
 -  { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO,
 -    VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO }
 +enum RiderType : int {
 +  NO_RIDER = 0,
 +  RIDER_BISHOP = 1 << 0,
 +  RIDER_ROOK_H = 1 << 1,
 +  RIDER_ROOK_V = 1 << 2,
 +  RIDER_CANNON_H = 1 << 3,
 +  RIDER_CANNON_V = 1 << 4,
 +  RIDER_HORSE = 1 << 5,
 +  RIDER_ELEPHANT = 1 << 6,
 +  RIDER_JANGGI_ELEPHANT = 1 << 7,
 +  RIDER_CANNON_DIAG = 1 << 8,
 +  RIDER_NIGHTRIDER = 1 << 9,
 +  RIDER_GRASSHOPPER_H = 1 << 10,
 +  RIDER_GRASSHOPPER_V = 1 << 11,
 +  RIDER_GRASSHOPPER_D = 1 << 12,
 +  HOPPING_RIDERS =  RIDER_CANNON_H | RIDER_CANNON_V | RIDER_CANNON_DIAG
 +                  | RIDER_GRASSHOPPER_H | RIDER_GRASSHOPPER_V | RIDER_GRASSHOPPER_D,
 +  LAME_LEAPERS = RIDER_HORSE | RIDER_ELEPHANT | RIDER_JANGGI_ELEPHANT,
 +  ASYMMETRICAL_RIDERS =  RIDER_HORSE | RIDER_JANGGI_ELEPHANT
 +                       | RIDER_GRASSHOPPER_H | RIDER_GRASSHOPPER_V | RIDER_GRASSHOPPER_D,
 +  NON_SLIDING_RIDERS = HOPPING_RIDERS | LAME_LEAPERS | RIDER_NIGHTRIDER,
  };
  
 +extern Value PieceValue[PHASE_NB][PIECE_NB];
 +extern Value EvalPieceValue[PHASE_NB][PIECE_NB]; // variant piece values for evaluation
 +extern Value CapturePieceValue[PHASE_NB][PIECE_NB]; // variant piece values for captures/search
 +
  typedef int Depth;
  
  enum : int {