+#include "thread.h"
+#include "uci.h"
+#include "incbin/incbin.h"
+
+
+// Macro to embed the default NNUE file data in the engine binary (using incbin.h, by Dale Weiler).
+// This macro invocation will declare the following three variables
+// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data
+// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end
+// const unsigned int gEmbeddedNNUESize; // the size of the embedded file
+// Note that this does not work in Microsof Visual Studio.
+#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
+ INCBIN(EmbeddedNNUE, EvalFileDefaultName);
+#else
+ const unsigned char gEmbeddedNNUEData[1] = {0x0};
+ const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1];
+ const unsigned int gEmbeddedNNUESize = 1;
+#endif
+
+
+using namespace std;
+using namespace Eval::NNUE;
+
+namespace Eval {
+
+ bool useNNUE;
+ string eval_file_loaded = "None";
+
+ /// NNUE::init() tries to load a nnue network at startup time, or when the engine
+ /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
+ /// The name of the nnue network is always retrieved from the EvalFile option.
+ /// We search the given network in three locations: internally (the default
+ /// network may be embedded in the binary), in the active working directory and
+ /// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY
+ /// variable to have the engine search in a special directory in their distro.
+
+ void NNUE::init() {
+
+ useNNUE = Options["Use NNUE"];
+ if (!useNNUE)
+ return;
+
+ string eval_file = string(Options["EvalFile"]);
+
+ #if defined(DEFAULT_NNUE_DIRECTORY)
+ #define stringify2(x) #x
+ #define stringify(x) stringify2(x)
+ vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) };
+ #else
+ vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory };
+ #endif
+
+ for (string directory : dirs)
+ if (eval_file_loaded != eval_file)
+ {
+ if (directory != "<internal>")
+ {
+ ifstream stream(directory + eval_file, ios::binary);
+ if (load_eval(eval_file, stream))
+ eval_file_loaded = eval_file;
+ }
- const Score MobilityBonus[][32] = {
- {}, {},
- { S(-75,-76), S(-57,-54), S( -9,-28), S( -2,-10), S( 6, 5), S( 14, 12), // Knights
- S( 22, 26), S( 29, 29), S( 36, 29) },
- { S(-48,-59), S(-20,-23), S( 16, -3), S( 26, 13), S( 38, 24), S( 51, 42), // Bishops
- S( 55, 54), S( 63, 57), S( 63, 65), S( 68, 73), S( 81, 78), S( 81, 86),
- S( 91, 88), S( 98, 97) },
- { S(-60,-77), S(-26,-20), S(-11, 27), S( -6, 57), S( -3, 69), S( -1, 82), // Rooks
- S( 10,109), S( 16,121), S( 24,131), S( 25,143), S( 32,155), S( 32,163),
- S( 43,167), S( 48,171), S( 56,173) },
- { S(-39,-36), S(-21,-15), S( 3, 8), S( 3, 18), S( 14, 34), S( 22, 54), // Queens
- S( 28, 61), S( 41, 73), S( 43, 79), S( 48, 92), S( 56, 94), S( 60,104),
- S( 60,113), S( 66,120), S( 67,123), S( 70,126), S( 71,133), S( 73,136),
- S( 79,140), S( 88,143), S( 88,148), S( 99,166), S(102,170), S(102,175),
- S(106,184), S(109,191), S(113,206), S(116,212) }
+ constexpr Score MobilityBonus[][32] = {
+ { S(-62,-79), S(-53,-57), S(-12,-31), S( -3,-17), S( 3, 7), S( 12, 13), // Knight
+ S( 21, 16), S( 28, 21), S( 37, 26) },
+ { S(-47,-59), S(-20,-25), S( 14, -8), S( 29, 12), S( 39, 21), S( 53, 40), // Bishop
+ S( 53, 56), S( 60, 58), S( 62, 65), S( 69, 72), S( 78, 78), S( 83, 87),
+ S( 91, 88), S( 96, 98) },
+ { S(-60,-82), S(-24,-15), S( 0, 17) ,S( 3, 43), S( 4, 72), S( 14,100), // Rook
+ S( 20,102), S( 30,122), S( 41,133), S(41 ,139), S( 41,153), S( 45,160),
+ S( 57,165), S( 58,170), S( 67,175) },
+ { S(-29,-49), S(-16,-29), S( -8, -8), S( -8, 17), S( 18, 39), S( 25, 54), // Queen
+ S( 23, 59), S( 37, 73), S( 41, 76), S( 54, 95), S( 65, 95) ,S( 68,101),
+ S( 69,124), S( 70,128), S( 70,132), S( 70,133) ,S( 71,136), S( 72,140),
+ S( 74,147), S( 76,149), S( 90,153), S(104,169), S(105,171), S(106,171),
+ S(112,178), S(114,185), S(114,187), S(119,221) }
- // ThreatByKing[on one/on many] contains bonuses for king attacks on
- // pawns or pieces which are not pawn-defended.
- const Score ThreatByKing[2] = { S(3, 62), S(9, 138) };
+ // Assorted bonuses and penalties
+ constexpr Score BadOutpost = S( -7, 36);
+ constexpr Score BishopOnKingRing = S( 24, 0);
+ constexpr Score BishopXRayPawns = S( 4, 5);
+ constexpr Score CorneredBishop = S( 50, 50);
+ constexpr Score FlankAttacks = S( 8, 0);
+ constexpr Score Hanging = S( 69, 36);
+ constexpr Score KnightOnQueen = S( 16, 11);
+ constexpr Score LongDiagonalBishop = S( 45, 0);
+ constexpr Score MinorBehindPawn = S( 18, 3);
+ constexpr Score PassedFile = S( 11, 8);
+ constexpr Score PawnlessFlank = S( 17, 95);
+ constexpr Score ReachableOutpost = S( 31, 22);
+ constexpr Score RestrictedPiece = S( 7, 7);
+ constexpr Score RookOnKingRing = S( 16, 0);
+ constexpr Score SliderOnQueen = S( 60, 18);
+ constexpr Score ThreatByKing = S( 24, 89);
+ constexpr Score ThreatByPawnPush = S( 48, 39);
+ constexpr Score ThreatBySafePawn = S(173, 94);
+ constexpr Score TrappedRook = S( 55, 13);
+ constexpr Score WeakQueenProtection = S( 14, 0);
+ constexpr Score WeakQueen = S( 56, 15);
+
+
+#undef S
+
+ // Evaluation class computes and stores attacks tables and other working data
+ template<Tracing T>
+ class Evaluation {
+
+ public:
+ Evaluation() = delete;
+ explicit Evaluation(const Position& p) : pos(p) {}
+ Evaluation& operator=(const Evaluation&) = delete;
+ Value value();
+
+ private:
+ template<Color Us> void initialize();
+ template<Color Us, PieceType Pt> Score pieces();
+ template<Color Us> Score king() const;
+ template<Color Us> Score threats() const;
+ template<Color Us> Score passed() const;
+ template<Color Us> Score space() const;
+ Value winnable(Score score) const;
+
+ const Position& pos;
+ Material::Entry* me;
+ Pawns::Entry* pe;
+ Bitboard mobilityArea[COLOR_NB];
+ Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO };
+
+ // attackedBy[color][piece type] is a bitboard representing all squares
+ // attacked by a given color and piece type. Special "piece types" which
+ // is also calculated is ALL_PIECES.
+ Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB];
- // PassedFile[File] contains a bonus according to the file of a passed pawn
- const Score PassedFile[FILE_NB] = {
- S( 9, 10), S( 2, 10), S( 1, -8), S(-20,-12),
- S(-20,-12), S( 1, -8), S( 2, 10), S( 9, 10)
- };
-
- // Protector[PieceType][distance] contains a protecting bonus for our king,
- // indexed by piece type and distance between the piece and the king.
- const Score Protector[PIECE_TYPE_NB][8] = {
- {}, {},
- { S(0, 0), S( 7, 9), S( 7, 1), S( 1, 5), S(-10,-4), S( -1,-4), S( -7,-3), S(-16,-10) }, // Knight
- { S(0, 0), S(11, 8), S(-7,-1), S(-1,-2), S( -1,-7), S(-11,-3), S( -9,-1), S(-16, -1) }, // Bishop
- { S(0, 0), S(10, 0), S(-2, 2), S(-5, 4), S( -6, 2), S(-14,-3), S( -2,-9), S(-12, -7) }, // Rook
- { S(0, 0), S( 3,-5), S( 2,-5), S(-4, 0), S( -9,-6), S(-4, 7), S(-13,-7), S(-10, -7) } // Queen
- };
+ // kingRing[color] are the squares adjacent to the king plus some other
+ // very near squares, depending on king position.
+ Bitboard kingRing[COLOR_NB];
- // Assorted bonuses and penalties used by evaluation
- const Score MinorBehindPawn = S(16, 0);
- const Score BishopPawns = S( 8, 12);
- const Score RookOnPawn = S( 8, 24);
- const Score TrappedRook = S(92, 0);
- const Score WeakQueen = S(50, 10);
- const Score OtherCheck = S(10, 10);
- const Score CloseEnemies = S( 7, 0);
- const Score PawnlessFlank = S(20, 80);
- const Score ThreatByHangingPawn = S(71, 61);
- const Score ThreatByRank = S(16, 3);
- const Score Hanging = S(48, 27);
- const Score ThreatByPawnPush = S(38, 22);
- const Score HinderPassedPawn = S( 7, 0);
-
- // Penalty for a bishop on a1/h1 (a8/h8 for black) which is trapped by
- // a friendly pawn on b2/g2 (b7/g7 for black). This can obviously only
- // happen in Chess960 games.
- const Score TrappedBishopA1H1 = S(50, 50);
-
- #undef S
- #undef V
+ // kingAttackersCount[color] is the number of pieces of the given color
+ // which attack a square in the kingRing of the enemy king.
+ int kingAttackersCount[COLOR_NB];
+
+ // kingAttackersWeight[color] is the sum of the "weights" of the pieces of
+ // the given color which attack a square in the kingRing of the enemy king.
+ // The weights of the individual piece types are given by the elements in
+ // the KingAttackWeights array.
+ int kingAttackersWeight[COLOR_NB];
+
+ // kingAttacksCount[color] is the number of attacks by the given color to
+ // squares directly adjacent to the enemy king. Pieces which attack more
+ // than one square are counted multiple times. For instance, if there is
+ // a white knight on g5 and black's king is on g8, this white knight adds 2
+ // to kingAttacksCount[WHITE].
+ int kingAttacksCount[COLOR_NB];
+ };
- Square d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST);
- if (pos.piece_on(s + d) == make_piece(Us, PAWN))
- score -= !pos.empty(s + d + pawn_push(Us)) ? TrappedBishopA1H1 * 4
- : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? TrappedBishopA1H1 * 2
- : TrappedBishopA1H1;
+ // Penalty according to the number of our pawns on the same color square as the
+ // bishop, bigger when the center files are blocked with pawns and smaller
+ // when the bishop is outside the pawn chain.
+ Bitboard blocked = pos.pieces(Us, PAWN) & shift<Down>(pos.pieces());
+
+ score -= BishopPawns[edge_distance(file_of(s))] * pos.pawns_on_same_color_squares(Us, s)
+ * (!(attackedBy[Us][PAWN] & s) + popcount(blocked & CenterFiles));
+
+ // Penalty for all enemy pawns x-rayed
+ score -= BishopXRayPawns * popcount(attacks_bb<BISHOP>(s) & pos.pieces(Them, PAWN));
+
+ // Bonus for bishop on a long diagonal which can "see" both center squares
+ if (more_than_one(attacks_bb<BISHOP>(s, pos.pieces(PAWN)) & Center))
+ score += LongDiagonalBishop;
+
+ // An important Chess960 pattern: a cornered bishop blocked by a friendly
+ // pawn diagonally in front of it is a very serious problem, especially
+ // when that pawn is also blocked.
+ if ( pos.is_chess960()
+ && (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1)))
+ {
+ Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST);
+ if (pos.piece_on(s + d) == make_piece(Us, PAWN))
+ score -= !pos.empty(s + d + pawn_push(Us)) ? CorneredBishop * 4
+ : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? CorneredBishop * 2
+ : CorneredBishop;
+ }
- // Main king safety evaluation
- if (ei.kingAttackersCount[Them])
- {
- // Find the attacked squares which are defended only by our king...
- undefended = ei.attackedBy[Them][ALL_PIECES]
- & ei.attackedBy[Us][KING]
- & ~ei.attackedBy2[Us];
-
- // ... and those which are not defended at all in the larger king ring
- b = ei.attackedBy[Them][ALL_PIECES] & ~ei.attackedBy[Us][ALL_PIECES]
- & ei.kingRing[Us] & ~pos.pieces(Them);
-
- // Initialize the 'kingDanger' variable, which will be transformed
- // later into a king danger score. The initial value is based on the
- // number and types of the enemy's attacking pieces, the number of
- // attacked and undefended squares around our king and the quality of
- // the pawn shelter (current 'score' value).
- kingDanger = std::min(807, ei.kingAttackersCount[Them] * ei.kingAttackersWeight[Them])
- + 101 * ei.kingAdjacentZoneAttacksCount[Them]
- + 235 * popcount(undefended)
- + 134 * (popcount(b) + !!pos.pinned_pieces(Us))
- - 717 * !pos.count<QUEEN>(Them)
- - 7 * mg_value(score) / 5 - 5;
-
- // Analyse the safe enemy's checks which are possible on next move
- safe = ~pos.pieces(Them);
- safe &= ~ei.attackedBy[Us][ALL_PIECES] | (undefended & ei.attackedBy2[Them]);
-
- b1 = pos.attacks_from<ROOK >(ksq);
- b2 = pos.attacks_from<BISHOP>(ksq);
-
- // Enemy queen safe checks
- if ((b1 | b2) & ei.attackedBy[Them][QUEEN] & safe)
- kingDanger += QueenCheck;
-
- // For minors and rooks, also consider the square safe if attacked twice,
- // and only defended by our queen.
- safe |= ei.attackedBy2[Them]
- & ~(ei.attackedBy2[Us] | pos.pieces(Them))
- & ei.attackedBy[Us][QUEEN];
-
- // Some other potential checks are also analysed, even from squares
- // currently occupied by the opponent own pieces, as long as the square
- // is not attacked by our pawns, and is not occupied by a blocked pawn.
- other = ~( ei.attackedBy[Us][PAWN]
- | (pos.pieces(Them, PAWN) & shift<Up>(pos.pieces(PAWN))));
-
- // Enemy rooks safe and other checks
- if (b1 & ei.attackedBy[Them][ROOK] & safe)
- kingDanger += RookCheck;
-
- else if (b1 & ei.attackedBy[Them][ROOK] & other)
- score -= OtherCheck;
-
- // Enemy bishops safe and other checks
- if (b2 & ei.attackedBy[Them][BISHOP] & safe)
- kingDanger += BishopCheck;
-
- else if (b2 & ei.attackedBy[Them][BISHOP] & other)
- score -= OtherCheck;
-
- // Enemy knights safe and other checks
- b = pos.attacks_from<KNIGHT>(ksq) & ei.attackedBy[Them][KNIGHT];
- if (b & safe)
- kingDanger += KnightCheck;
-
- else if (b & other)
- score -= OtherCheck;
-
- // Transform the kingDanger units into a Score, and substract it from the evaluation
- if (kingDanger > 0)
- score -= make_score(std::min(kingDanger * kingDanger / 4096, 2 * int(BishopValueMg)), 0);
- }
+ // Attacked squares defended at most once by our queen or king
+ weak = attackedBy[Them][ALL_PIECES]
+ & ~attackedBy2[Us]
+ & (~attackedBy[Us][ALL_PIECES] | attackedBy[Us][KING] | attackedBy[Us][QUEEN]);
- score -= CloseEnemies * popcount(b);
+ else
+ unsafeChecks |= b2 & attackedBy[Them][BISHOP];
+
+ // Enemy knights checks
+ knightChecks = attacks_bb<KNIGHT>(ksq) & attackedBy[Them][KNIGHT];
+ if (knightChecks & safe)
+ kingDanger += SafeCheck[KNIGHT][more_than_one(knightChecks & safe)];
+ else
+ unsafeChecks |= knightChecks;
+
+ // Find the squares that opponent attacks in our king flank, the squares
+ // which they attack twice in that flank, and the squares that we defend.
+ b1 = attackedBy[Them][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp;
+ b2 = b1 & attackedBy2[Them];
+ b3 = attackedBy[Us][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp;
+
+ int kingFlankAttack = popcount(b1) + popcount(b2);
+ int kingFlankDefense = popcount(b3);
+
+ kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] // (~10 Elo)
+ + 183 * popcount(kingRing[Us] & weak) // (~15 Elo)
+ + 148 * popcount(unsafeChecks) // (~4 Elo)
+ + 98 * popcount(pos.blockers_for_king(Us)) // (~2 Elo)
+ + 69 * kingAttacksCount[Them] // (~0.5 Elo)
+ + 3 * kingFlankAttack * kingFlankAttack / 8 // (~0.5 Elo)
+ + mg_value(mobility[Them] - mobility[Us]) // (~0.5 Elo)
+ - 873 * !pos.count<QUEEN>(Them) // (~24 Elo)
+ - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) // (~5 Elo)
+ - 6 * mg_value(score) / 8 // (~8 Elo)
+ - 4 * kingFlankDefense // (~5 Elo)
+ + 37; // (~0.5 Elo)
+
+ // Transform the kingDanger units into a Score, and subtract it from the evaluation
+ if (kingDanger > 100)
+ score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16);
- unsafeSquares &= ei.attackedBy[Them][ALL_PIECES] | pos.pieces(Them);
-
- // If there aren't any enemy attacks, assign a big bonus. Otherwise
- // assign a smaller bonus if the block square isn't attacked.
- int k = !unsafeSquares ? 18 : !(unsafeSquares & blockSq) ? 8 : 0;
-
- // If the path to the queen is fully defended, assign a big bonus.
- // Otherwise assign a smaller bonus if the block square is defended.
- if (defendedSquares == squaresToQueen)
- k += 6;
-
- else if (defendedSquares & blockSq)
- k += 4;
-
- mbonus += k * rr, ebonus += k * rr;
+ unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them);
+
+ // If there are no enemy pieces or attacks on passed pawn span, assign a big bonus.
+ // Or if there is some, but they are all attacked by our pawns, assign a bit smaller bonus.
+ // Otherwise assign a smaller bonus if the path to queen is not attacked
+ // and even smaller bonus if it is attacked but block square is not.
+ int k = !unsafeSquares ? 36 :
+ !(unsafeSquares & ~attackedBy[Us][PAWN]) ? 30 :
+ !(unsafeSquares & squaresToQueen) ? 17 :
+ !(unsafeSquares & blockSq) ? 7 :
+ 0 ;
+
+ // Assign a larger bonus if the block square is defended
+ if ((pos.pieces(Us) & bb) || (attackedBy[Us][ALL_PIECES] & blockSq))
+ k += 5;
+
+ bonus += make_score(k * w, k * w);
- // Endings where weaker side can place his king in front of the opponent's
- // pawns are drawish.
- else if ( abs(eg) <= BishopValueEg
- && pos.count<PAWN>(strongSide) <= 2
- && !pos.pawn_passed(~strongSide, pos.square<KING>(~strongSide)))
- return ScaleFactor(37 + 7 * pos.count<PAWN>(strongSide));
+ // For rook endgames with strong side not having overwhelming pawn number advantage
+ // and its pawns being on one flank and weak side protecting its pieces with a king
+ // use lower scale factor.
+ else if ( pos.non_pawn_material(WHITE) == RookValueMg
+ && pos.non_pawn_material(BLACK) == RookValueMg
+ && pos.count<PAWN>(strongSide) - pos.count<PAWN>(~strongSide) <= 1
+ && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN))
+ && (attacks_bb<KING>(pos.square<KING>(~strongSide)) & pos.pieces(~strongSide, PAWN)))
+ sf = 36;
+ // For queen vs no queen endgames use scale factor
+ // based on number of minors of side that doesn't have queen.
+ else if (pos.count<QUEEN>() == 1)
+ sf = 37 + 3 * (pos.count<QUEEN>(WHITE) == 1 ? pos.count<BISHOP>(BLACK) + pos.count<KNIGHT>(BLACK)
+ : pos.count<BISHOP>(WHITE) + pos.count<KNIGHT>(WHITE));
+ // In every other case use scale factor based on
+ // the number of pawns of the strong side reduced if pawns are on a single flank.
+ else
+ sf = std::min(sf, 36 + 7 * pos.count<PAWN>(strongSide)) - 4 * !pawnsOnBothFlanks;
+
+ // Reduce scale factor in case of pawns being on a single flank
+ sf -= 4 * !pawnsOnBothFlanks;
- Trace::add(MATERIAL, pos.psq_score());
- Trace::add(IMBALANCE, ei.me->imbalance());
- Trace::add(PAWN, ei.pe->pawns_score());
- Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]);
- if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >= 12222)
- Trace::add(SPACE, evaluate_space<WHITE>(pos, ei)
- , evaluate_space<BLACK>(pos, ei));
- Trace::add(TOTAL, score);
+ // Scale and shift NNUE for compatibility with search and classical evaluation
+ auto adjusted_NNUE = [&](){
+ int mat = pos.non_pawn_material() + PawnValueMg * pos.count<PAWN>();
+ return NNUE::evaluate(pos) * (679 + mat / 32) / 1024 + Tempo;
+ };
+
+ // If there is PSQ imbalance use classical eval, with small probability if it is small
+ Value psq = Value(abs(eg_value(pos.psq_score())));
+ int r50 = 16 + pos.rule50_count();
+ bool largePsq = psq * 16 > (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50;
+ bool classical = largePsq || (psq > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB));
+
+ // Use classical evaluation for really low piece endgames.
+ // The most critical case is a bishop + A/H file pawn vs naked king draw.
+ bool strongClassical = pos.non_pawn_material() < 2 * RookValueMg && pos.count<PAWN>() < 2;
+
+ v = classical || strongClassical ? Evaluation<NO_TRACE>(pos).value() : adjusted_NNUE();
+
+ // If the classical eval is small and imbalance large, use NNUE nevertheless.
+ // For the case of opposite colored bishops, switch to NNUE eval with
+ // small probability if the classical eval is less than the threshold.
+ if ( largePsq && !strongClassical
+ && ( abs(v) * 16 < NNUEThreshold2 * r50
+ || ( pos.opposite_bishops()
+ && abs(v) * 16 < (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50
+ && !(pos.this_thread()->nodes & 0xB))))
+ v = adjusted_NNUE();
- << " Eval term | White | Black | Total \n"
- << " | MG EG | MG EG | MG EG \n"
- << "----------------+-------------+-------------+-------------\n"
- << " Material | " << Term(MATERIAL)
- << " Imbalance | " << Term(IMBALANCE)
- << " Pawns | " << Term(PAWN)
- << " Knights | " << Term(KNIGHT)
- << " Bishop | " << Term(BISHOP)
- << " Rooks | " << Term(ROOK)
- << " Queens | " << Term(QUEEN)
- << " Mobility | " << Term(MOBILITY)
- << " King safety | " << Term(KING)
- << " Threats | " << Term(THREAT)
- << " Passed pawns | " << Term(PASSED)
- << " Space | " << Term(SPACE)
- << "----------------+-------------+-------------+-------------\n"
- << " Total | " << Term(TOTAL);
-
- ss << "\nTotal Evaluation: " << to_cp(v) << " (white side)\n";
+ << " Term | White | Black | Total \n"
+ << " | MG EG | MG EG | MG EG \n"
+ << " ------------+-------------+-------------+------------\n"
+ << " Material | " << Term(MATERIAL)
+ << " Imbalance | " << Term(IMBALANCE)
+ << " Pawns | " << Term(PAWN)
+ << " Knights | " << Term(KNIGHT)
+ << " Bishops | " << Term(BISHOP)
+ << " Rooks | " << Term(ROOK)
+ << " Queens | " << Term(QUEEN)
+ << " Mobility | " << Term(MOBILITY)
+ << " King safety | " << Term(KING)
+ << " Threats | " << Term(THREAT)
+ << " Passed | " << Term(PASSED)
+ << " Space | " << Term(SPACE)
+ << " Winnable | " << Term(WINNABLE)
+ << " ------------+-------------+-------------+------------\n"
+ << " Total | " << Term(TOTAL);
+
+ v = pos.side_to_move() == WHITE ? v : -v;
+
+ ss << "\nClassical evaluation: " << to_cp(v) << " (white side)\n";
+
+ if (Eval::useNNUE)
+ {
+ v = NNUE::evaluate(pos);
+ v = pos.side_to_move() == WHITE ? v : -v;
+ ss << "\nNNUE evaluation: " << to_cp(v) << " (white side)\n";
+ }
+
+ v = evaluate(pos);
+ v = pos.side_to_move() == WHITE ? v : -v;
+ ss << "\nFinal evaluation: " << to_cp(v) << " (white side)\n";