_state_.zobrist_hash_);
  if (mov.IsCapture() || moving_piece->kind() == kPawn) {
    current_state_.halfmove_clock = 0;
  } else {
    current_state_.halfmove_clock++;
  }

  if (side_to_move_ == kWhite) {
    current_state_.fullmove_clock++;
  }
}

void Position::UnmakeMove() {
  // To unmake the move, we must first restore the previous move's
  // irreversible state and then undo the reversible aspects of the move.
  CHECK(!irreversible_state_.empty()) << "no moves to unmake";
  current_state_ = irreversible_state_.top();
  irreversible_state_.pop();

  // See comment at the bottom of this function.
  uint64_t hack_hash = current_state_.zobrist_hash_;

  // Reverse the side to move.
  side_to_move_ =!side_to_move_;

  CHECK(current_state_.move.has_value()) << "no move available to unmake";
  Move mov = *current_state_.move;

  // The rest of UnmakeMove proceeds in reverse of MakeMove; find the piece at
  // the destination square, remove it, replace it with the piece that was
  // captured, and move the piece back to the source.
  //
  // Since "the piece that was captured" is irreverisble state, we grab that
  // from current state (which we just restored).
  auto moved_piece = PieceAt(mov.Destination());
  CHECK(moved_piece.has_value()) << "no piece at move destination square";

  RemovePiece(mov.Destination());
  if (mov.IsCapture()) {
    CHECK(current_state_.last_capture_.has_value())
        << "unmaking capture with no last capture piece";

    Square captured_piece_square = mov.Destination();
    if (mov.IsEnPassant()) {
      // Like in MakeMove, en passant is the only move where we have to put
      // the piece back somewhere other than the move destination.
      Direction ep_dir =
          side_to_move_ == kWhite? kDirectionSouth : kDirectionNorth;
      captured_piece_square = util::Towards(mov.Destination(), ep_dir);
    }

    AddPiece(captured_piece_square,
             Piece(!side_to_move_, *current_state_.last_capture_));
  }

  Piece piece_to_add = *moved_piece;
  if (mov.IsPromotion()) {
    // Only pawns can be promoted, therefore the piece that this used to be is
    // a pawn.
    piece_to_add = Piece(side_to_move_, kPawn);
  }
  AddPiece(mov.Source(), piece_to_add);

  if (mov.IsCastle()) {
    // If this move was a castle, we need to put the rook in the right spot.
    // If this is a kingside castle, the rook needs to be one square to the
    // east of the king. If this is a queenside castle, the rook needs to be
    // two squares to the west of the king.
    //
    // In both cases, mov.Destination() refers to the location of the king,
    // so we can calculate the rook position based on that.
    Square rook_square, target_rook_square;
    if (mov.IsKingsideCastle()) {
      rook_square = util::Towards(mov.Destination(), kDirectionWest);
      target_rook_square = util::Towards(mov.Destination(), kDirectionEast);
    } else {
      rook_square = util::Towards(mov.Destination(), kDirectionEast);
      target_rook_square = util::Towards(
          util::Towards(mov.Destination(), kDirectionWest), kDirectionWest);
    }

    auto rook = PieceAt(rook_square);
    CHECK(rook.has_value() && rook->kind() == kRook)
        << "rook not present at expected location";
    RemovePiece(rook_square);
    AddPiece(target_rook_square, *rook);
  }

  // HACK HACK HACK: the zobrist hash is saved in the irreversible state buffer
  // because it's hard to reverse it in UnmakeMove. We just called a bunch of
  // functions that messed with the hash, so just restore the hash from the
  // irreversible state buffer now.
  //
  // This sucks, but it works.
  current_state_.zobrist_hash_ = hack_hash;
}

std::vector<Move> Position::PseudolegalMoves() const {
  std::vector<Move> moves;
  moves.reserve(250);
  movegen::GeneratePseudolegalMoves(*this, moves);
  moves.shrink_to_fit();
  return moves;
}

std::vector<Move> Position::LegalMoves() const {
  std::vector<Move> legal_moves;
  for (Move mov : PseudolegalMoves()) {
    if (IsLegalGivenPseudolegal(mov)) {
      legal_moves.push_back(mov);
    }
  }
  return legal_moves;
}

void Position::Dump(std::ostream& out) const {
  for (int rank = kRank8; rank >= kRank1; rank--) {
    for (int file = kFileA; file < kFileLast; file++) {
      Square sq = util::SquareOf(rank, file);
      auto piece = PieceAt(sq);
      if (piece) {
        out << " " << piece->AsChar() << " ";
      } else {
        out << ". ";
      }
    }
    out << "| " << static_cast<char>(rank + 49) << std::endl;
  }
  for (int i = kFileA; i < kFileLast; i++) {
    out << "---";
  }
  out << std::endl;
  for (int i = kFileA; i < kFileLast; i++) {
    out << " " << static_cast<char>(i + 97) << " ";
  }
  out << std::endl;
}

std::string Position::AsFen() const {
  std::stringstream ss;
  for (int rank = kRank8; rank >= kRank1; rank--) {
    int empty_squares = 0;
    for (int file = kFileA; file < kFileLast; file++) {
      Square sq = util::SquareOf(rank, file);
      std::optional<Piece> piece_at_square = PieceAt(sq);
      if (piece_at_square) {
        if (empty_squares!= 0) {
          ss << empty_squares;
        }
        ss << piece_at_square->AsChar();
        empty_squares = 0;
      } else {
        empty_squares++;
      }
    }

    if (empty_squares!= 0) {
      ss << empty_squares;
    }

    if (rank!= kRank1) {
      ss << "/";
    }
  }

  ss << " ";
  if (SideToMove() == kWhite) {
    ss << "w";
  } else {
    ss << "b";
  }
  ss << " ";
  if (CanCastleKingside(kWhite)) {
    ss << "K";
  }
  if (CanCastleQueenside(kWhite)) {
    ss << "Q";
  }
  if (CanCastleKingside(kBlack)) {
    ss << "k";
  }
  if (CanCastleQueenside(kBlack)) {
    ss << "q";
  }
  ss << " ";
  auto ep_square = EnPassantSquare();
  if (ep_square) {
    ss << util::SquareString(*ep_square);
  } else {
    ss << "-";
  }
  ss << " " << HalfmoveClock() << " " << FullmoveClock();
  return ss.str();
}

std::optional<Move> Position::MoveFromUci(std::string_view uci) const {
  // UCI encodes a move as the source square, followed by the destination
  // square, and optionally followed by the promotion piece if necessary