Merge official-stockfish/master
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 18 Jul 2021 13:56:13 +0000 (15:56 +0200)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sun, 18 Jul 2021 13:56:13 +0000 (15:56 +0200)
bench: 6603046

1  2 
src/Makefile
src/evaluate.h
src/nnue/nnue_feature_transformer.h
src/position.cpp
src/position.h
src/search.cpp
src/syzygy/tbprobe.cpp

diff --cc src/Makefile
Simple merge
diff --cc src/evaluate.h
Simple merge
@@@ -403,17 -403,17 +403,17 @@@ namespace Stockfish::Eval::NNUE 
          // accumulator. Then, we update the current accumulator (pos.state()).
  
          // Gather all features to be updated.
 -        const Square ksq = pos.square<KING>(perspective);
 +        const Square ksq = pos.square(perspective, pos.nnue_king());
          IndexList removed[2], added[2];
          FeatureSet::append_changed_indices(
 -          ksq, next, perspective, removed[0], added[0]);
 +          ksq, next, perspective, removed[0], added[0], pos);
          for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous)
            FeatureSet::append_changed_indices(
 -            ksq, st2, perspective, removed[1], added[1]);
 +            ksq, st2, perspective, removed[1], added[1], pos);
  
          // Mark the accumulators as computed.
-         next->accumulator.state[perspective] = COMPUTED;
-         pos.state()->accumulator.state[perspective] = COMPUTED;
+         next->accumulator.computed[perspective] = true;
+         pos.state()->accumulator.computed[perspective] = true;
  
          // Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
          StateInfo *states_to_update[3] =
@@@ -309,187 -220,68 +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);
 +      // 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());
  
 -      token = char(toupper(token));
 +          token = char(toupper(token));
  
 -      if (token == 'K')
 -          for (rsq = relative_square(c, SQ_H1); 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 == 'Q')
 -          for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {}
 +          else if (token == 'Q')
 +              for (rsq = make_square(FILE_A, 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 >= 'A' && token <= 'A' + max_file())
 +              rsq = make_square(File(token - 'A'), castling_rank(c));
  
 -      else
 -          continue;
 +          else
 +              continue;
 +
 +          // 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));
 +          }
  
 -      set_castling_right(c, rsq);
 +          // 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 (castling_enabled() && piece_on(rsq) == rook)
 +              set_castling_right(c, rsq);
 +      }
 +
 +      // 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));
 +              }
 +
 +      // 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;
 +      }
    }
  
 -  // 4. En passant square.
 -  // Ignore if square is invalid or not on side to move relative rank 6.
 -  bool enpassant = false;
 +  // Check counter for nCheck
 +  ss >> std::skipws >> token >> std::noskipws;
  
 -  if (   ((ss >> col) && (col >= 'a' && col <= 'h'))
 -      && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3'))))
 +  if (check_counting())
    {
 -      st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
 +      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
 +      ss.putback(token);
  
 -      // 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
 -      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))));
 +  // 5-6. Halfmove clock and fullmove number
 +  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;
  
 -  if (!enpassant)
 -      st->epSquare = SQ_NONE;
 +      // 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);
 +  }
  
 -  // 5-6. Halfmove clock and fullmove number
 -  ss >> std::skipws >> st->rule50 >> gamePly;
 +  // counting rules
 +  if (st->countingLimit && st->rule50)
 +  {
 +      st->countingPly = st->rule50;
 +      st->rule50 = 0;
 +  }
  
 -  // 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);
 +  // 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;
 +  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;
  
    assert(pos_is_ok());
  
@@@ -1337,12 -700,10 +1335,12 @@@ void Position::do_move(Move m, StateInf
    ++gamePly;
    ++st->rule50;
    ++st->pliesFromNull;
 +  if (st->countingLimit)
 +      ++st->countingPly;
  
    // Used by NNUE
-   st->accumulator.state[WHITE] = Eval::NNUE::EMPTY;
-   st->accumulator.state[BLACK] = Eval::NNUE::EMPTY;
+   st->accumulator.computed[WHITE] = false;
+   st->accumulator.computed[BLACK] = false;
    auto& dp = st->dirtyPiece;
    dp.dirty_num = 1;
  
diff --cc src/position.h
@@@ -282,16 -156,9 +282,17 @@@ public
    int game_ply() const;
    bool is_chess960() const;
    Thread* this_thread() const;
 +  bool is_immediate_game_end() const;
 +  bool is_immediate_game_end(Value& result, int ply = 0) const;
 +  bool is_optional_game_end() const;
 +  bool is_optional_game_end(Value& result, int ply = 0, int countStarted = 0) const;
 +  bool is_game_end(Value& result, int ply = 0) const;
 +  Value material_counting_result() const;
+   bool is_draw(int ply) const;
    bool has_game_cycle(int ply) const;
    bool has_repeated() const;
 +  int counting_limit() const;
 +  int counting_ply(int countStarted) const;
    int rule50_count() const;
    Score psq_score() const;
    Value non_pawn_material(Color c) const;
@@@ -346,554 -202,6 +347,559 @@@ 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);
 +                  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);
 +          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_draw(int ply) const {
++  Value result;
++  return is_optional_game_end(result, ply);
++}
++
 +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
Simple merge
Simple merge