summaryrefslogtreecommitdiff
path: root/src/eval.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-10-13 08:59:29 -0700
committerScott Gasch <[email protected]>2026-10-13 08:59:29 -0700
commitf03bcac9ce3c6542310010bfbef2b126bb6ae861 (patch)
treefb30befe5cdd08d126d38009a5a62ca3ef40e9f9 /src/eval.c
parent03545c4600df81b1a47dbc63fc606c065e857d8b (diff)
Minor changes; eval code was play tested as ~even.
Diffstat (limited to 'src/eval.c')
-rw-r--r--src/eval.c201
1 files changed, 110 insertions, 91 deletions
diff --git a/src/eval.c b/src/eval.c
index a909953..c747a8e 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -123,7 +123,7 @@ static ULONG CASTLE_AND_DEVELOPMENT_SCALER[32] =
// 2R2m R4m/QRm Q3m 2R3m/ QR2m Q4m 2R4m/ QR3m
// Q2R Q2Rm
- 0, 1, 2, 3, 4, 5, 6, 7,
+ 1, 2, 3, 3, 4, 5, 6, 7,
// na Q2R2m QR4m na Q2R3m na na full
7, 8, 8, 8, 8, 8, 8, 8,
@@ -304,8 +304,8 @@ static SCORE PASSER_BY_RANK[2][9] =
static SCORE CANDIDATE_PASSER_BY_RANK[2][9] =
{// 0 1 2 3 4 5 6 7 8
- { +0, +0, +0, +48, +34, +22, +13, +9, +0 }, // black
- { +0, +0, +9, +13, +22, +34, +48, +0, +0 } // white
+ { +0, +0, +0, +38, +26, +17, +10, +6, +0 }, // black
+ { +0, +0, +6, +10, +17, +26, +38, +0, +0 } // white
};
// Note: x2
@@ -414,6 +414,21 @@ static SCORE UNDEVELOPED_MINORS_IN_OPENING[5] =
// "Evaluation of Material Imbalance"
static SCORE BISHOP_PAIR_BONUS = 30;
+// Material-imbalance bonuses for a handful of "close looking but not
+// actually equal" non-pawn shapes that _EvalBadTrades special-cases
+// outright rather than running through its generic ahead/behind
+// trade-encouragement logic (see _EvalBadTrades for why: that logic
+// picks "ahead" by raw point value, which gets the direction or
+// magnitude wrong for these specific shapes). All three are flat,
+// position-independent nudges -- deliberately not scaled by pawn
+// count/game phase the way the generic terms are, since the whole
+// point is that these shapes are close enough that a big swing isn't
+// warranted. DNA-tunable; starting values are hand judgment pending a
+// tuning/match-play pass.
+static SCORE QUEEN_VS_TWO_ROOKS_BONUS = 25; // favors the 2 rooks
+static SCORE QUEEN_VS_THREE_MINORS_BONUS = 10; // favors the 3 minors
+static SCORE QUEEN_VS_ROOK_PLUS_MINOR_BONUS = 20; // favors the queen
+
static SCORE STATIONARY_PAWN_ON_BISHOP_COLOR[128] =
{
+0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0,
@@ -749,7 +764,7 @@ static SCORE KING_TO_CENTER[128] =
// the stay-home bonus now uses this same threshold, so "safety scored"
// and "stay-home bonus applies" are exactly the same condition.
#define DO_KING_SAFETY_THRESHOLD \
- (VALUE_ROOK + VALUE_BISHOP + VALUE_BISHOP + VALUE_KING)
+ (VALUE_ROOK + VALUE_ROOK + VALUE_BISHOP + VALUE_KING)
static ULONG KING_COUNTER_BY_ATTACK_PATTERN[32] =
{
@@ -817,7 +832,7 @@ static SCORE KING_QUEEN_PROXIMITY_DANGER[7] =
+0, -10, -18, -25, -32, -39, -55
};
-static SCORE KING_MISSING_ONE_CASTLE_OPTION = -23;
+static SCORE KING_MISSING_ONE_CASTLE_OPTION = -25;
// 2026-09-14: new term, not a retune of an existing one.
// king_safety_data/HANDOFF.md's regression found `back_flight` (an
@@ -959,6 +974,9 @@ static DNA_BASE_SIZE g_EvalDNA[] = {
DNA_VAR(RACER_WINS_RACE),
DNA_ARRAY(UNDEVELOPED_MINORS_IN_OPENING),
DNA_VAR(BISHOP_PAIR_BONUS),
+ DNA_VAR(QUEEN_VS_TWO_ROOKS_BONUS),
+ DNA_VAR(QUEEN_VS_THREE_MINORS_BONUS),
+ DNA_VAR(QUEEN_VS_ROOK_PLUS_MINOR_BONUS),
DNA_ARRAY(STATIONARY_PAWN_ON_BISHOP_COLOR),
DNA_ARRAY(TRANSIENT_PAWN_ON_BISHOP_COLOR),
DNA_ARRAY(BISHOP_MOBILITY_BY_SQUARES),
@@ -1576,17 +1594,6 @@ Return value:
pHash->iScore[uColor],
PASSER_BY_RANK[uColor][RANK(c)],
"passed pawn");
-
- // However, don't give doubled passers such a big bonus.
- if (IS_PAWN(pos->rgSquare[c - 16 * g_iAhead[uColor]].pPiece))
- {
- EVAL_TERM(uColor,
- PAWN,
- c,
- pHash->iScore[uColor],
- -(PASSER_BY_RANK[uColor][RANK(c)] / 2),
- "doubled passer");
- }
return;
}
uSentries = FastCountBits(bb);
@@ -3053,16 +3060,14 @@ Return value:
// Out of action in KP-ish endgames, e.g.
// 8/8/1p6/p6k/P3K3/8/1P6/8 w - - 0 11 -- how close this king is
- // to the whole pawn mass. The *2 preserves the old formula's own
- // scale (it used to be a differential, this is now a plain sum,
- // but the per-pawn magnitude is unchanged).
+ // to the whole pawn mass.
i = 0;
bb = pos->bbPawns[WHITE] | pos->bbPawns[BLACK];
while (IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb)))
{
i -= (SCORE)DISTANCE(cSquare, cKing);
}
- iTotal += i * 2;
+ iTotal += i;
pEntry->u64ActionKey = u64Key;
pEntry->iActionBonus = iTotal;
@@ -5379,13 +5384,11 @@ Return value:
SCORE i;
SCORE iKingScore = 0;
- //
// Come out. KING_TO_CENTER applies unconditionally (no longer
// gated by uTotalNonPawnsBothSides<8 -- that was a different,
// combined-both-sides unit than this whole split now uses, and
// left a gap between "safety stops mattering" and "centralizing
// is actually encouraged"; see _EvalKing's header comment).
- //
i = KING_TO_CENTER[c];
EVAL_TERM(uColor,
KING,
@@ -5395,15 +5398,7 @@ Return value:
"centralize king");
// Sharper late-game guidance once the enemy is down to at most
- // one non-king piece (lone king, K+minor, K+rook, even K+queen
- // -- though a lone queen's own material value already exceeds
- // DO_KING_SAFETY_THRESHOLD on its own, VALUE_QUEEN=975 >
- // VALUE_ROOK+VALUE_BISHOP=800, so that specific case can never
- // actually reach this function at all; the count check below is
- // correct on its own terms regardless). Per-call/per-king
- // (xColor's own material), not the old combined-both-sides
- // uTotalNonPawnsBothSides==2 -- the two kings' own enemies can
- // legitimately disagree now.
+ // one non-king piece (lone king, K+minor, K+rook, even K+queen.
if (TotalNonPawnCount(pos, xColor) <= 2)
{
// Supporting-own-passer and out-of-action-in-KP-endgames,
@@ -5422,7 +5417,7 @@ Return value:
c,
iKingScore,
i,
- "king activity (passer support + in/out of action)");
+ "king/passer position");
}
return iKingScore;
}
@@ -5856,7 +5851,7 @@ Return value:
static void
_ReEvalPassers(IN OUT POSITION *pos,
- IN PAWN_HASH_ENTRY *pHash)
+ IN PAWN_HASH_ENTRY *pHash)
/**
Routine description:
@@ -5877,15 +5872,13 @@ Return value:
ULONG xColor;
COOR c;
COOR cSquare;
+ COOR cRank;
BITBOARD bb;
PIECE p;
SCORE i;
ULONG uNumPassers[2] = { 0, 0 };
int d1;
- ASSERT(pHash->bbPasserLocations[WHITE] |
- pHash->bbPasserLocations[BLACK]);
-
FOREACH_COLOR(uColor)
{
xColor = FLIP(uColor);
@@ -5899,46 +5892,34 @@ Return value:
while(IS_ON_BOARD(c = CoorFromBitBoardRank8ToRank1(&bb)))
{
- //
// Consider the control of the square in front of the passer
- //
+ cRank = RANK(c);
cSquare = c + d1;
ASSERT(IS_ON_BOARD(cSquare));
u = _WhoControlsSquareFast(pos, cSquare);
- if (u == xColor)
- {
- EVAL_TERM(uColor,
- PAWN,
- c,
- pos->iScore[uColor],
- -((PASSER_BY_RANK[uColor][RANK(c)] / 4) +
- (PASSER_BY_RANK[uColor][RANK(c)] / 16)),
- "enemy controls sq ahead");
- }
- else if (u == (ULONG)-1)
- {
- p = pos->rgSquare[cSquare].pPiece;
- if (!IS_EMPTY(p) && (OPPOSITE_COLORS(p, uColor)))
- {
- ASSERT(GET_COLOR(p) != uColor);
- EVAL_TERM(uColor,
- PAWN,
- c,
- pos->iScore[uColor],
- -(PASSER_BY_RANK[uColor][RANK(c)] / 4),
- "enemy occupies sq ahead");
- }
- }
-#ifdef DEBUG
- else
- {
- ASSERT(u == uColor);
- }
-#endif
+ EVAL_TERM(uColor,
+ PAWN,
+ c,
+ pos->iScore[uColor],
+ (u == xColor) *
+ -((PASSER_BY_RANK[uColor][cRank] / 4)),
+ "enemy controls sq ahead");
+
+ // Square ahead is uncontrolled but occupied by xColor?
+ p = pos->rgSquare[cSquare].pPiece;
+ EVAL_TERM(uColor,
+ PAWN,
+ c,
+ pos->iScore[uColor],
+ (
+ (u == (ULONG)-1) &&
+ !IS_EMPTY(p) &&
+ OPPOSITE_COLORS(p, uColor)
+ ) *
+ -(PASSER_BY_RANK[uColor][cRank] / 4),
+ "enemy occupies sq ahead");
- //
// Consider distance from friend king / enemy king
- //
i = (8 - DISTANCE(c, KingPosition(pos, uColor)));
if (((uColor == WHITE) &&
((KingPosition(pos, BLACK) >> 4) <= (c >> 4))) ||
@@ -5964,26 +5945,6 @@ Return value:
pos->iScore[uColor],
i,
"passer distance to kings");
-
- //
- // If a side is a piece down then the other side's passers
- // are even more dangerous.
- //
- if (TotalNonPawnCount(pos, uColor) >
- TotalNonPawnCount(pos, xColor))
- {
- i = (PASSER_BY_RANK[uColor][RANK(c)] / 2);
- EVAL_TERM(uColor,
- PAWN,
- c,
- pos->iScore[uColor],
- i,
- "passer and piece up");
- }
-
- //
- // TODO: For connected passers vs KR, consult the oracle
- //
}
}
}
@@ -6070,6 +6031,64 @@ Return value:
}
//
+ // Q vs RR, Q vs MMM, and Q vs RM are three "close looking but not
+ // actually equal" non-pawn shapes that this routine's generic
+ // ahead/behind machinery below gets wrong -- it picks "ahead" by
+ // raw point value and hands that side a position/phase-scaled
+ // "keep trading" bonus (up to ~160 here), which is both sometimes
+ // the wrong side and wildly too large an opinion for shapes this
+ // close. Special-case all three to their own small flat
+ // (unscaled, pawn/phase-independent) bonus instead, then return
+ // before the generic logic ever runs.
+ //
+ // Q vs RR: rooks are a bit better (open files/ranks, strong in the
+ // endgame) -- small flat bonus to the rook side. Queen is uBehind
+ // here (975 < 2*500 makes the rooks uAhead by this routine's own
+ // value-based metric).
+ if ((1 == TotalNonPawnCount(pos, uBehind)) &&
+ (1 == FastCountBits(pos->bbPieces[uBehind][QUEEN])) &&
+ (2 == TotalNonPawnCount(pos, uAhead)) &&
+ (2 == FastCountBits(pos->bbPieces[uAhead][ROOK])))
+ {
+ EVAL_TERM(uAhead, 0, ILLEGAL_COOR, pos->iScore[uAhead],
+ QUEEN_VS_TWO_ROOKS_BONUS, "queen vs two rooks");
+ return;
+ }
+
+ // Q vs MMM: three minors need heavy coordination to use their
+ // nominal edge and are drawish in practice -- small flat bonus to
+ // the minors side. Queen is uAhead here (975 > 900 makes the
+ // queen uAhead by this routine's own value-based metric).
+ if ((1 == TotalNonPawnCount(pos, uAhead)) &&
+ (1 == FastCountBits(pos->bbPieces[uAhead][QUEEN])) &&
+ (3 == TotalNonPawnCount(pos, uBehind)) &&
+ (0 == FastCountBits(pos->bbPieces[uBehind][ROOK])) &&
+ (0 == FastCountBits(pos->bbPieces[uBehind][QUEEN])))
+ {
+ EVAL_TERM(uBehind, 0, ILLEGAL_COOR, pos->iScore[uBehind],
+ QUEEN_VS_THREE_MINORS_BONUS, "queen vs three minors");
+ return;
+ }
+
+ // Q vs RM: queen's single-piece coordination (easier to combine
+ // with the king than a rook+minor pair trying to work together)
+ // and tactical resourcefulness outweighs the ~1 pawn point
+ // deficit (975 vs 800) slightly more often than not -- small flat
+ // bonus to the queen side. Queen is uAhead here (975 > 800 makes
+ // the queen uAhead by this routine's own value-based metric).
+ if ((1 == TotalNonPawnCount(pos, uAhead)) &&
+ (1 == FastCountBits(pos->bbPieces[uAhead][QUEEN])) &&
+ (2 == TotalNonPawnCount(pos, uBehind)) &&
+ (1 == FastCountBits(pos->bbPieces[uBehind][ROOK])) &&
+ (1 == (FastCountBits(pos->bbPieces[uBehind][KNIGHT]) +
+ FastCountBits(pos->bbPieces[uBehind][BISHOP]))))
+ {
+ EVAL_TERM(uAhead, 0, ILLEGAL_COOR, pos->iScore[uAhead],
+ QUEEN_VS_ROOK_PLUS_MINOR_BONUS, "queen vs rook+minor");
+ return;
+ }
+
+ //
// Encourage the side that is ahead in piece material to continue
// trading pieces and not to trade pawns. This also has the
// effect of making the side that is behind in material want to
@@ -6122,7 +6141,7 @@ Return value:
0,
ILLEGAL_COOR,
pos->iScore[uAhead],
- (0 != uAheadPawnCount) ? (SCORE)(uAheadPawnCount * 2) :
+ (0 != uAheadPawnCount) ? (SCORE)(uAheadPawnCount * 4) :
(TRUE == fAheadLoneMinorNoMate) ? -30 : 0,
"don't trade pawns");
}