summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xsrc/chess.h1
-rwxr-xr-xsrc/eval.c611
2 files changed, 326 insertions, 286 deletions
diff --git a/src/chess.h b/src/chess.h
index 7d01e64..702defb 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -729,7 +729,6 @@ typedef struct _POSITION
ULONG uClosedScaler;
SCORE iScore[2];
SCORE iReducedMaterialDownScaler[2];
- SCORE iTempScore;
ULONG uMinorsAtHome[2];
BITBOARD bb;
}
diff --git a/src/eval.c b/src/eval.c
index 06e6bc7..bc5f6f4 100755
--- a/src/eval.c
+++ b/src/eval.c
@@ -96,19 +96,19 @@ const int g_iBehind[2] = { -1, +1 };
// General eval terms
// ---------------------------------------------------------------------------
//
-static SCORE TRADE_PIECES[3][17] =
-{// 0 1 2 3 4 5 6 7 8 | 9..15 -- down piece count
- { -1, 90, 81, 73, 67, 62, 57, 52, 50, 50,50,50,50,50,50,50,-1},
- { -1, 125, 117, 109, 100, 91, 84, 78, 71, 66,66,66,66,66,66,66,-1},
- { -1, 150, 132, 124, 116, 109, 102, 94, 87, 77,77,77,77,77,77,77,-1},
-};
+//static SCORE TRADE_PIECES[3][17] =
+//{// 0 1 2 3 4 5 6 7 8 | 9..15 -- down piece count
+// { -1, 90, 81, 73, 67, 62, 57, 52, 50, 50,50,50,50,50,50,50,-1},
+// { -1, 125, 117, 109, 100, 91, 84, 78, 71, 66,66,66,66,66,66,66,-1},
+// { -1, 150, 132, 124, 116, 109, 102, 94, 87, 77,77,77,77,77,77,77,-1},
+//};
-static SCORE DONT_TRADE_PAWNS[3][9] =
-{// 0 1 2 3 4 5 6 7 8 -- up pawn count
- { -43, -15, 0, +3, +6, +10, +15, +21, +28 }, // 0
- { -63, -25, 0, +4, +9, +14, +19, +25, +32 }, // 1
- { -10, 0, 0, +7, +15, +20, +24, +29, +36 }, // 2
-};
+//static SCORE DONT_TRADE_PAWNS[3][9] =
+//{// 0 1 2 3 4 5 6 7 8 -- up pawn count
+// { -43, -15, 0, +3, +6, +10, +15, +21, +28 }, // // 0
+// { -63, -25, 0, +4, +9, +14, +19, +25, +32 }, // // 1
+// { -10, 0, 0, +7, +15, +20, +24, +29, +36 }, // // 2
+//};
static ULONG REDUCED_MATERIAL_DOWN_SCALER[32] =
{
@@ -531,6 +531,14 @@ static SCORE KNIGHT_IN_CLOSED_POSITION[33] =
static SCORE ROOK_ON_FULL_OPEN = 16;
static SCORE ROOK_ON_HALF_OPEN_WITH_ENEMY = 9;
static SCORE ROOK_ON_HALF_OPEN_WITH_FRIEND = 10;
+// Cache of the three constants above, indexed [friend pawn on file?]
+// [enemy pawn on file?] so _EvalRook's hot path is a single array read
+// instead of rebuilding a local array every call. Not `static const`
+// -- the three source constants are DNA-tunable globals (plain mutable
+// SCORE, not compile-time constants), so this has to be a plain
+// mutable array, rebuilt by InitEval() whenever they change (startup,
+// and after every `evaldna read`), not frozen at compile time.
+static SCORE ROOK_FULL_HALF_OPEN_BONUS[2][2] = { { 16, 9 }, { 10, 0 } };
// Same treatment for the enemy/friend-passer-on-file bonuses -- was
// PASSER_BY_RANK[xColor][rank]/4 (enemy case) and a rank-scaled
@@ -729,8 +737,6 @@ typedef struct _DNA_BASE_SIZE {
#define DNA_ARRAY(x) {(SCORE *)(x), ARRAY_LENGTH(x)}
#define DNA_MATRIX(x) {(SCORE *)(x), ARRAY_LENGTH(x) * ARRAY_LENGTH((x)[0])}
static DNA_BASE_SIZE g_EvalDNA[] = {
- DNA_MATRIX(TRADE_PIECES),
- DNA_MATRIX(DONT_TRADE_PAWNS),
DNA_ARRAY(REDUCED_MATERIAL_DOWN_SCALER),
DNA_ARRAY(CASTLE_AND_DEVELOPMENT_SCALER),
DNA_ARRAY(REDUCED_MATERIAL_UP_SCALER),
@@ -1163,7 +1169,6 @@ Return value:
ASSERT((uBlack & 0xFFFFFF00) == 0);
p = pos->rgSquare[c].pPiece;
- // p -= 2;
ch = g_SwapTable[p][uWhite][uBlack];
if (ch != 0)
{
@@ -1523,8 +1528,7 @@ Return value:
(((WHITE == uColor) && (RANK(c) > 5)) ||
((BLACK == uColor) && (RANK(c) < 4)))))
{
- // Tuning can (and has) flipped this sign; not a real invariant.
- //ASSERT(CANDIDATE_PASSER_BY_RANK[uColor][RANK(c)] > 0);
+ ASSERT(CANDIDATE_PASSER_BY_RANK[uColor][RANK(c)] > 0);
EVAL_TERM(uColor,
PAWN,
c,
@@ -1760,7 +1764,6 @@ Return value:
pos->bbPawnAttacks[BLACK] =
((pos->bbPawns[BLACK] & ~BBFILE[0]) << 7) |
((pos->bbPawns[BLACK] & ~BBFILE[7]) << 9);
-
pos->bbPawnAttacks[WHITE] =
((pos->bbPawns[WHITE] & ~BBFILE[0]) >> 9) |
((pos->bbPawns[WHITE] & ~BBFILE[7]) >> 7);
@@ -2240,18 +2243,18 @@ Return value:
ULONG
CountKingSafetyDefects(IN OUT POSITION *pos,
- IN ULONG uSide)
+ IN ULONG uColor)
/**
Routine description:
- Determine how many defects uSide's king position has _quickly_.
+ Determine how many defects uColor's king position has _quickly_.
TODO: add more knowledge as cheaply as possible...
Parameters:
POSITION *pos,
- ULONG uSide
+ ULONG uColor
Return value:
@@ -2260,7 +2263,7 @@ Return value:
**/
{
ULONG uCounter = 0;
- ULONG xSide = FLIP(uSide);
+ ULONG xColor = FLIP(uColor);
COOR cKing;
BITBOARD bbKingZone;
BITBOARD bbBlockers;
@@ -2271,16 +2274,16 @@ Return value:
// Don't count king safety defects if the real eval code in
// _EvalKing would not...
//
- if (pos->uNonPawnMaterial[xSide] < DO_KING_SAFETY_THRESHOLD) {
+ if (pos->uNonPawnMaterial[xColor] < DO_KING_SAFETY_THRESHOLD) {
return 0;
}
- cKing = pos->cNonPawns[uSide][0];
- uCounter = KING_INITIAL_COUNTER_BY_LOCATION[uSide][cKing] >> 1;
+ cKing = pos->cNonPawns[uColor][0];
+ uCounter = KING_INITIAL_COUNTER_BY_LOCATION[uColor][cKing] >> 1;
ASSERT(IS_KING(pos->rgSquare[cKing].pPiece));
ASSERT(pos->rgSquare[cKing].uIndex == 0);
ASSERT(IS_ON_BOARD(cKing));
- ASSERT(GET_COLOR(pos->rgSquare[cKing].pPiece) == uSide);
+ ASSERT(GET_COLOR(pos->rgSquare[cKing].pPiece) == uColor);
//
// board_representation/EVAL.md section 9 follow-up (2026-09-05):
@@ -2307,9 +2310,9 @@ Return value:
// lazy-eval check across the whole search tree, for what's already
// a rough magnitude estimate, not an exact score) -- but the
// occupancy fed to the slider lookups below is deliberately just
- // xSide's own pieces (_BuildFriendlySideBB(pos, xSide)), not the
- // whole board: a piece belonging to uSide (a defending pawn, the
- // king itself) is transparent to these rays, only xSide's own
+ // xColor's own pieces (_BuildFriendlySideBB(pos, xColor)), not the
+ // whole board: a piece belonging to uColor (a defending pawn, the
+ // king itself) is transparent to these rays, only xColor's own
// pieces actually block. This gets latent-threat detection (a
// rook aimed at the king zone but currently shielded by the
// king's own pawn still counts) for free, at zero extra cost --
@@ -2318,21 +2321,21 @@ Return value:
// own pieces as blocking the enemy king.
//
bbKingZone = g_KingAttacksBB[cKing] | COOR_TO_BB(cKing);
- bbBlockers = _BuildFriendlySideBB(pos, xSide);
+ bbBlockers = _BuildFriendlySideBB(pos, xColor);
- bb = pos->bbPieces[xSide][KNIGHT];
+ bb = pos->bbPieces[xColor][KNIGHT];
while (bb)
{
c = CoorFromBitBoardRank8ToRank1(&bb);
uCounter += (g_KnightAttacksBB[c] & bbKingZone) != 0;
}
- bb = pos->bbPieces[xSide][BISHOP];
+ bb = pos->bbPieces[xColor][BISHOP];
while (bb)
{
c = CoorFromBitBoardRank8ToRank1(&bb);
uCounter += (_BishopAttacksBB(c, bbBlockers) & bbKingZone) != 0;
}
- bb = pos->bbPieces[xSide][ROOK];
+ bb = pos->bbPieces[xColor][ROOK];
while (bb)
{
c = CoorFromBitBoardRank8ToRank1(&bb);
@@ -2345,7 +2348,7 @@ Return value:
// (a queen hitting the zone counted exactly the same as a lone
// knight doing so). Raises the bound below accordingly (up to 8
// queens are legal via promotion, each now worth 2).
- bb = pos->bbPieces[xSide][QUEEN];
+ bb = pos->bbPieces[xColor][QUEEN];
while (bb)
{
c = CoorFromBitBoardRank8ToRank1(&bb);
@@ -3102,6 +3105,7 @@ Return value:
{ C1, F1 } // WHITE
};
ULONG uColor;
+ ULONG xColor;
BITBOARD bb;
BITBOARD bbMask;
BITBOARD bbPc;
@@ -3115,6 +3119,7 @@ Return value:
p = pos->rgSquare[c].pPiece;
ASSERT(p && IS_BISHOP(p));
uColor = GET_COLOR(p);
+ xColor = FLIP(uColor);
// Undeveloped minor piece; maybe penalized later in EvalKing.
pos->uMinorsAtHome[uColor] += (c == cBishopAtHome[uColor][0]);
@@ -3156,17 +3161,17 @@ Return value:
i += TRANSIENT_PAWN_ON_BISHOP_COLOR[cSquare];
}
- bb = pos->bbPawns[FLIP(uColor)] & bbPc;
+ bb = pos->bbPawns[xColor] & bbPc;
while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb)))
{
// N.B. Only count enemy pawns that are supported by another
// pawn.
ASSERT(pos->rgSquare[cSquare].pPiece);
ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece));
- ASSERT(GET_COLOR(pos->rgSquare[cSquare].pPiece) == FLIP(uColor));
- ASSERT(pos->bbPawns[FLIP(uColor)] & COOR_TO_BB(cSquare));
+ ASSERT(GET_COLOR(pos->rgSquare[cSquare].pPiece) == xColor);
+ ASSERT(pos->bbPawns[xColor] & COOR_TO_BB(cSquare));
i +=
- ((pos->bbPawnAttacks[FLIP(uColor)] & COOR_TO_BB(cSquare)) != 0) *
+ ((pos->bbPawnAttacks[xColor] & COOR_TO_BB(cSquare)) != 0) *
TRANSIENT_PAWN_ON_BISHOP_COLOR[cSquare] / 2;
}
EVAL_TERM(uColor,
@@ -3220,14 +3225,14 @@ Return value:
pos->bb = bbPc;
{
BITBOARD bbAttack = _BishopAttacksBB(c, pos->bbOccupied);
- BITBOARD bbEnemySame = pos->bbPieces[FLIP(uColor)][BISHOP] |
- pos->bbPieces[FLIP(uColor)][KNIGHT];
- BITBOARD bbEnemyGEContinue = pos->bbPieces[FLIP(uColor)][ROOK] |
- pos->bbPieces[FLIP(uColor)][QUEEN] |
- COOR_TO_BB(pos->cNonPawns[FLIP(uColor)][0]);
+ BITBOARD bbEnemySame = pos->bbPieces[xColor][BISHOP] |
+ pos->bbPieces[xColor][KNIGHT];
+ BITBOARD bbEnemyGEContinue = pos->bbPieces[xColor][ROOK] |
+ pos->bbPieces[xColor][QUEEN] |
+ COOR_TO_BB(pos->cNonPawns[xColor][0]);
BITBOARD bbFriendBQ = pos->bbPieces[uColor][BISHOP] |
pos->bbPieces[uColor][QUEEN];
- BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[FLIP(uColor)];
+ BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[xColor];
BITBOARD bbExclude = 0;
BITBOARD bbSeen = 0;
BITBOARD bbLayer = bbAttack;
@@ -3262,7 +3267,7 @@ Return value:
BITBOARD bbEnemyGEHere = bbBlockersHere & bbEnemyGEContinue;
BITBOARD bbFriendPawnHere = bbBlockersHere & pos->bbPawns[uColor];
BITBOARD bbEnemyPawnHere = bbBlockersHere &
- pos->bbPawns[FLIP(uColor)];
+ pos->bbPawns[xColor];
BITBOARD bbTransientHere = bbFriendPawnHere & bbPc;
BITBOARD bbContinueHere;
@@ -3310,8 +3315,8 @@ Return value:
// by a friendly one -- near the enemy king. This is a
// bishop-specific king-tropism/outpost bonus.
//
- bb = pos->bbPawns[FLIP(uColor)] &
- (~pHash->bbStationaryPawns[FLIP(uColor)]);
+ bb = pos->bbPawns[xColor] &
+ (~pHash->bbStationaryPawns[xColor]);
if (TRUE == _IsSquareSafeFromEnemyPawn(pos, c, bb))
{
// Defended (and defending) a friendly pawn.
@@ -3324,7 +3329,7 @@ Return value:
((IS_ON_BOARD(c + 15 * g_iBehind[uColor])) &&
(pos->rgSquare[c + 15 * g_iBehind[uColor]].pPiece==p))));
#endif
- u = DISTANCE(c, pos->cNonPawns[FLIP(uColor)][0]);
+ u = DISTANCE(c, pos->cNonPawns[xColor][0]);
i = BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING[u];
EVAL_TERM(uColor,
BISHOP,
@@ -3366,6 +3371,7 @@ Return value:
COOR cSquare;
BITBOARD bb;
ULONG uColor;
+ ULONG xColor;
ULONG uPawnsSupporting;
ULONG uMobilitySquares;
SCORE i;
@@ -3375,6 +3381,7 @@ Return value:
ASSERT(p && IS_KNIGHT(p));
uColor = GET_COLOR(p);
ASSERT(IS_VALID_COLOR(uColor));
+ xColor = FLIP(uColor);
// Unmoved piece; potentially penalized in EvalKing.
pos->uMinorsAtHome[uColor] += (c == cKnightAtHome[uColor][0]);
@@ -3402,10 +3409,10 @@ Return value:
// if so give bonus for outposted knight which increases the
// closer it is to the enemy king and the more pawns it has
// supporting it.
- uDist = DISTANCE(c, pos->cNonPawns[FLIP(uColor)][0]);
+ uDist = DISTANCE(c, pos->cNonPawns[xColor][0]);
ASSERT((uDist > 0) && (uDist <= 8));
- bb = pos->bbPawns[FLIP(uColor)] &
- (~pHash->bbStationaryPawns[FLIP(uColor)]);
+ bb = pos->bbPawns[xColor] &
+ (~pHash->bbStationaryPawns[xColor]);
if (TRUE == _IsSquareSafeFromEnemyPawn(pos, c, bb))
{
// Count the number of supporting pawns the knight has
@@ -3435,7 +3442,7 @@ Return value:
{
if (IS_SQUARE_WHITE(c))
{
- if (pos->uWhiteSqBishopCount[FLIP(uColor)])
+ if (pos->uWhiteSqBishopCount[xColor])
{
EVAL_TERM(uColor,
KNIGHT,
@@ -3447,8 +3454,8 @@ Return value:
}
else
{
- if (pos->uNonPawnCount[FLIP(uColor)][BISHOP] -
- pos->uWhiteSqBishopCount[FLIP(uColor)])
+ if (pos->uNonPawnCount[xColor][BISHOP] -
+ pos->uWhiteSqBishopCount[xColor])
{
EVAL_TERM(uColor,
KNIGHT,
@@ -3474,7 +3481,7 @@ Return value:
// Give a bonus for blockading an enemy backward pawn.
cSquare = c + 16 * g_iAhead[uColor];
- bb = pHash->bbStationaryPawns[FLIP(uColor)];
+ bb = pHash->bbStationaryPawns[xColor];
if (bb & COOR_TO_BB(cSquare))
{
ASSERT(pos->rgSquare[cSquare].pPiece);
@@ -3515,9 +3522,9 @@ Return value:
{
BITBOARD bbAttack = g_KnightAttacksBB[c];
BITBOARD bbFriendOcc = _BuildFriendlySideBB(pos, uColor);
- BITBOARD bbEnemyNonPawnOcc = _BuildFriendlySideBB(pos, FLIP(uColor)) &
- ~pos->bbPawns[FLIP(uColor)];
- BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[FLIP(uColor)];
+ BITBOARD bbEnemyNonPawnOcc = _BuildFriendlySideBB(pos, xColor) &
+ ~pos->bbPawns[xColor];
+ BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[xColor];
BITBOARD bbMobility;
pos->bbMinorAttacks[uColor] |= bbAttack;
@@ -3565,7 +3572,6 @@ Return value:
ULONG uColor;
ULONG uTotalMobility;
COOR cSquare;
- BITBOARD bb;
SCORE i;
ASSERT(IS_ON_BOARD(c));
@@ -3573,38 +3579,27 @@ Return value:
ASSERT(p && IS_ROOK(p));
uColor = GET_COLOR(p);
ASSERT(IS_VALID_COLOR(uColor));
- {
- ULONG xColor = FLIP(uColor);
- BITBOARD bbFile = BBFILE[FILE(c)];
- BITBOARD bbFriendPawns = pos->bbPawns[uColor] & bbFile;
- BITBOARD bbEnemyPawns = pos->bbPawns[xColor] & bbFile;
- // Not `static` -- ROOK_ON_FULL_OPEN et al are DNA-tunable
- // globals (plain mutable SCORE, not compile-time constants), so
- // a static initializer would freeze in whatever value happened
- // to be compiled in and never see a later `evaldna read`. A
- // plain local array is rebuilt from the live values every call
- // (cheap: 4 loads, no branches) and stays correct under tuning.
- SCORE ROOK_FULL_HALF_OPEN_BONUS[2][2] =
- {
- { ROOK_ON_FULL_OPEN, ROOK_ON_HALF_OPEN_WITH_ENEMY },
- { ROOK_ON_HALF_OPEN_WITH_FRIEND, 0 },
- };
- // Collapsed 2026-09-06: friend-passer bonus used to distinguish
- // rook-behind-passer (good, rank-scaled +1..+25) from rook-in-
- // front-of-passer (bad, -3..-22) -- now a single flat bonus
- // regardless of which side of the pawn the rook is on, by
- // direct instruction.
- SCORE bonus = ROOK_FULL_HALF_OPEN_BONUS[bbFriendPawns != 0]
- [bbEnemyPawns != 0];
- bonus += (SCORE)((bbEnemyPawns & pHash->bbPasserLocations[xColor]) != 0) *
- ROOK_WITH_ENEMY_PASSER;
- bonus += (SCORE)((bbFriendPawns & pHash->bbPasserLocations[uColor]) != 0) *
- ROOK_WITH_FRIEND_PASSER;
+ ULONG xColor = FLIP(uColor);
+ ASSERT(IS_VALID_COLOR(xColor));
- EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], bonus,
- "file/passer bonus");
- }
+ BITBOARD bbFile = BBFILE[FILE(c)];
+ BITBOARD bbFriendPawns = pos->bbPawns[uColor] & bbFile;
+ BITBOARD bbEnemyPawns = pos->bbPawns[xColor] & bbFile;
+ // Collapsed 2026-09-06: friend-passer bonus used to distinguish
+ // rook-behind-passer (good, rank-scaled +1..+25) from rook-in-
+ // front-of-passer (bad, -3..-22) -- now a single flat bonus
+ // regardless of which side of the pawn the rook is on, by
+ // direct instruction.
+ SCORE bonus = ROOK_FULL_HALF_OPEN_BONUS[bbFriendPawns != 0][bbEnemyPawns != 0];
+
+ bonus += (SCORE)((bbEnemyPawns & pHash->bbPasserLocations[xColor]) != 0) *
+ ROOK_WITH_ENEMY_PASSER;
+ bonus += (SCORE)((bbFriendPawns & pHash->bbPasserLocations[uColor]) != 0) *
+ ROOK_WITH_FRIEND_PASSER;
+
+ EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], bonus,
+ "file/passer bonus");
//
@@ -3958,19 +3953,6 @@ Return value:
{
_RecordTrappedCandidate(pos, uColor, c);
}
-
- //
- // Removed 2026-08-30 ("pointing near enemy K" /
- // QUEEN_ATTACKS_SQ_NEXT_TO_KING): counted how many king-adjacent
- // squares the queen's own mobility ray-cast just attacked, using
- // the same pos->rgSquare[...].bvAttacks bits _EvalKing's real
- // danger computation (not the lazy-eval estimate) reads directly a
- // few squares away in the same file. King safety already derives
- // this more comprehensively -- across every attacking piece type,
- // properly weighted -- from the identical attack-table data
- // mobility just paid to populate. This was a narrower, redundant
- // re-derivation of a subset of that same signal.
- //
}
@@ -4073,15 +4055,12 @@ Return value:
ASSERT(IS_VALID_COLOR(uColor));
pos->bbKingAttacks[uColor] |= g_KingAttacksBB[c];
+ // Don't bother with king safety when the other side has no mating
+ // material.
u = pos->uNonPawnMaterial[xColor];
ASSERT(u >= VALUE_KING);
if (u < DO_KING_SAFETY_THRESHOLD)
{
- // board_representation/EVAL.md section 9: g_KingAttacksBB[c]
- // (generate.c's precomputed table, already used by move
- // generation) is exactly the old g_iQKDeltas walk's
- // destination set, IS_ON_BOARD baked in at table-build time --
- // one OR instead of an 8-iteration loop.
goto skip_safety;
}
@@ -4093,7 +4072,6 @@ Return value:
#endif
BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, xColor);
-
uFlightSquares = 0;
u = 0;
ASSERT(KingSafetyDeltas[u] != 0);
@@ -4206,12 +4184,6 @@ Return value:
uCounter);
#endif
- // No shift needed (2026-09-06): PAWN_BIT/MINOR_BIT/ROOK_BIT/
- // QUEEN_BIT now sit directly in bits 1-4, and bvPattern never gets
- // KING_BIT (bit 0) set (bvAttack/bvXray deliberately exclude it --
- // see this function's header comment), so bvPattern already lands
- // in [0, 30] here, matching KING_COUNTER_BY_ATTACK_PATTERN's range
- // directly.
ASSERT(bvPattern >= 0);
ASSERT(bvPattern < 32);
v = KING_COUNTER_BY_ATTACK_PATTERN[bvPattern];
@@ -4227,10 +4199,8 @@ Return value:
Trace("%s KS Counter post-flight sq: %u\n", COLOR_NAME(uColor), uCounter);
#endif
- //
// Note: can't use pos->iReducedMaterialDownScaler here because we
// are scaling it based on the _other_ side's material.
- //
uCounter = MINU(uCounter, 41);
i = KING_SAFETY_BY_COUNTER[uCounter];
#ifdef CALIBRATE_POSITIONAL
@@ -4257,10 +4227,8 @@ Return value:
KING_QUEEN_PROXIMITY_DANGER[MINU(uQueenNearKing, 6)],
"queen proximity danger");
- //
// Bonus for castling / penalty for loss of castle. Also, if side has
// not yet castled, be concerned with undeveloped minor pieces.
- //
if (FALSE == pos->fCastled[uColor])
{
ULONG uDevelopmentScaler = CASTLE_AND_DEVELOPMENT_SCALER[pos->uArmyScaler[uColor]];
@@ -4381,7 +4349,7 @@ Return value:
}
}
pos->iScore[uColor] += iKingScore;
- pos->iTempScore = iKingScore;
+ ctx->sPlyInfo[ctx->uPly].iKingScore[uColor] = iKingScore;
}
@@ -4539,7 +4507,7 @@ Return value:
static void
-_EvalPassers(IN OUT POSITION *pos,
+_ReEvalPassers(IN OUT POSITION *pos,
IN PAWN_HASH_ENTRY *pHash)
/**
@@ -4708,15 +4676,9 @@ Return value:
**/
{
- ULONG uAhead, uBehind;
- ULONG uMagnitude;
-
- //
- // See who is ahead
- //
ASSERT(pos->uNonPawnMaterial[WHITE] != pos->uNonPawnMaterial[BLACK]);
- uAhead = (pos->uNonPawnMaterial[WHITE] > pos->uNonPawnMaterial[BLACK]);
- uBehind = FLIP(uAhead);
+ ULONG uAhead = (pos->uNonPawnMaterial[WHITE] > pos->uNonPawnMaterial[BLACK]);
+ ULONG uBehind = FLIP(uAhead);
#ifdef DEBUG
if (pos->uNonPawnMaterial[WHITE] > pos->uNonPawnMaterial[BLACK])
{
@@ -4730,15 +4692,6 @@ Return value:
ASSERT(uBehind == WHITE);
}
#endif
- uMagnitude = ((pos->uNonPawnMaterial[uAhead] +
- pos->uNonPawnCount[uAhead][0] * 128) -
- (pos->uNonPawnMaterial[uBehind] +
- pos->uNonPawnCount[uBehind][0] * 128));
- uMagnitude /= 128;
- uMagnitude -= (uMagnitude != 0);
- ASSERT(!(uMagnitude & 0x80000000));
- uMagnitude = MINU(2, uMagnitude);
- ASSERT(uMagnitude <= 2);
//
// Encourage the side that is ahead in piece material to continue
@@ -4750,19 +4703,26 @@ Return value:
// engine will not like positions like KNB vs KRP - the side with
// the pawn has the winning chances.
//
+ ULONG uAheadPawnCount = pos->uPawnCount[uAhead];
+ ULONG uInverseBehindPieceCount = 9 - MINU(9, pos->uNonPawnCount[uBehind][0]);
EVAL_TERM(uAhead,
0,
ILLEGAL_COOR,
pos->iScore[uAhead],
- ((pos->uPawnCount[uAhead] != 0) *
- TRADE_PIECES[uMagnitude][pos->uNonPawnCount[uBehind][0]]),
+ (uInverseBehindPieceCount * 16) * (uAheadPawnCount != 0),
"trade pieces");
- ASSERT(TRADE_PIECES[uMagnitude][pos->uNonPawnCount[uBehind][0]] > 0);
+ // Zero pawns left for the ahead side is the classic false-positive
+ // (KNB vs KRP, minor-up-no-pawns endings that are often drawn or
+ // even lost) -- the old DONT_TRADE_PAWNS table existed specifically
+ // to punish it (-43/-10 depending on lead size) rather than just
+ // trailing off toward a small positive number. Keep that guard as
+ // a cheap branch rather than a lookup table; the positive slope
+ // for 1+ pawns doesn't need the same care.
EVAL_TERM(uAhead,
0,
ILLEGAL_COOR,
pos->iScore[uAhead],
- DONT_TRADE_PAWNS[uMagnitude][pos->uPawnCount[uAhead]],
+ (0 == uAheadPawnCount) ? -30 : (SCORE)(uAheadPawnCount * 2),
"don't trade pawns");
}
@@ -4857,9 +4817,8 @@ Return value:
ASSERT(IS_ON_BOARD(c));
if (_WhoControlsSquareFast(pos, c) == FLIP(uColor))
{
- p = pos->rgSquare[c].pPiece;
- (void)p;
#ifdef DEBUG
+ p = pos->rgSquare[c].pPiece;
ASSERT(p);
ASSERT(!IS_PAWN(p));
ASSERT(GET_COLOR(p) == uColor);
@@ -4931,43 +4890,142 @@ Return value:
--*/
{
- ULONG uBishopCount = pos->uNonPawnCount[BLACK][BISHOP];
- ASSERT(uBishopCount <= 10);
- ULONG uWhiteSqBishopCount = pos->uWhiteSqBishopCount[BLACK];
- ASSERT(uWhiteSqBishopCount <= 10);
- FLAG fPair = ((uBishopCount > 1) &
- (uWhiteSqBishopCount != 0) &
- (uWhiteSqBishopCount != uBishopCount));
- EVAL_TERM(BLACK,
- 0,
- ILLEGAL_COOR,
- pos->iScore[BLACK],
- fPair * BISHOP_PAIR_BONUS,
- "bishop pair");
-
- uBishopCount = pos->uNonPawnCount[WHITE][BISHOP];
- ASSERT(uBishopCount <= 10);
- uWhiteSqBishopCount = pos->uWhiteSqBishopCount[WHITE];
- ASSERT(uWhiteSqBishopCount <= 10);
- fPair = ((uBishopCount > 1) &
- (uWhiteSqBishopCount != 0) &
- (uWhiteSqBishopCount != uBishopCount));
- EVAL_TERM(WHITE,
- 0,
- ILLEGAL_COOR,
- pos->iScore[WHITE],
- fPair * BISHOP_PAIR_BONUS,
- "bishop pair");
+ ULONG uColor;
+ FOREACH_COLOR(uColor)
+ {
+ ULONG uBishopCount = pos->uNonPawnCount[uColor][BISHOP];
+ ASSERT(uBishopCount <= 10);
+ ULONG uWhiteSqBishopCount = pos->uWhiteSqBishopCount[uColor];
+ ASSERT(uWhiteSqBishopCount <= 10);
+ FLAG fPair = ((uBishopCount > 1) &
+ (uWhiteSqBishopCount != 0) &
+ (uWhiteSqBishopCount != uBishopCount));
+ EVAL_TERM(uColor,
+ 0,
+ ILLEGAL_COOR,
+ pos->iScore[uColor],
+ fPair * BISHOP_PAIR_BONUS,
+ "bishop pair");
+ }
}
+static FLAG
+_SideHasWinningChances(IN POSITION *pos,
+ IN ULONG uSide)
+/**
+
+Routine description:
+
+ Cheap, material-count-only classifier (the same idea as Crafty's
+ EvaluateWinningChances): can uSide possibly force a win from this
+ material alone, ignoring the actual position entirely? Used only
+ to soften (scale toward drawscore) an otherwise-misleading raw
+ material lead in Eval() below -- unlike recogn.c's interior-node
+ recognizers (RECOGN_EXACT/UPPER/LOWER), this is never a hard claim
+ fed into a search bound, so a wrong answer here just biases the
+ eval a little; it can't corrupt alpha-beta the way a wrong hard
+ recognizer bound can (see recogn.c's _RecognizeKNKP/_RecognizeKBNK
+ incident).
+
+ Deliberately conservative: only covers material shapes where "no,
+ this can't be forced" is basic, textbook chess knowledge
+ (insufficient mating material, up the exchange with nothing else
+ changed, bare knights vs a bare king), not trickier exceptions
+ (fortress draws, wrong-bishop-pawn-with-king-in-time geometry)
+ that need real board information -- those are recogn.c's
+ hard-recognizer territory (already exact for tiny material there)
+ or plain search's job, not this cheap pre-check's.
+
+Parameters:
+
+ POSITION *pos,
+ ULONG uSide
+
+Return value:
+
+ static FLAG : TRUE if uSide has any winning chances at all, FALSE
+ if this exact material can provably never be forced to a win
+ regardless of position.
+
+**/
+{
+ ULONG uEnemy = FLIP(uSide);
+ INT iMajorDiff;
+ INT iMinorDiff;
+
+ //
+ // A pawn always gives some winning chances (it can always try to
+ // queen with support) -- nothing below applies.
+ //
+ if (pos->uPawnCount[uSide] > 0)
+ {
+ return(TRUE);
+ }
+
+ //
+ // No pawns and the only piece besides the king is a single minor:
+ // never enough material to force mate (K+N or K+B vs anything is
+ // never a forced win on material alone).
+ //
+ if ((2 == pos->uNonPawnCount[uSide][0]) &&
+ (1 == (pos->uNonPawnCount[uSide][KNIGHT] +
+ pos->uNonPawnCount[uSide][BISHOP])))
+ {
+ return(FALSE);
+ }
+
+ //
+ // No pawns and up exactly the exchange (one extra rook/queen,
+ // balanced by one extra enemy minor elsewhere): not enough to
+ // force a win either -- e.g. KRB vs KR can be held by the
+ // defender with correct play.
+ //
+ iMajorDiff = (INT)(pos->uNonPawnCount[uSide][ROOK] +
+ 2 * pos->uNonPawnCount[uSide][QUEEN]) -
+ (INT)(pos->uNonPawnCount[uEnemy][ROOK] +
+ 2 * pos->uNonPawnCount[uEnemy][QUEEN]);
+ if ((1 == iMajorDiff) || (-1 == iMajorDiff))
+ {
+ iMinorDiff = (INT)(pos->uNonPawnCount[uEnemy][KNIGHT] +
+ pos->uNonPawnCount[uEnemy][BISHOP]) -
+ (INT)(pos->uNonPawnCount[uSide][KNIGHT] +
+ pos->uNonPawnCount[uSide][BISHOP]);
+ if (iMajorDiff == iMinorDiff)
+ {
+ return(FALSE);
+ }
+ }
+
+ //
+ // No pawns, exactly two bare knights (no bishops/rooks/queens) for
+ // uSide, and the enemy has nothing left at all: two knights can't
+ // force mate against a bare king. Two bishops CAN (excluded here
+ // by requiring BISHOP count == 0) and knight+bishop CAN too (also
+ // excluded, since that has one bishop, not zero) -- both are
+ // genuine, if sometimes technique-heavy, forced wins.
+ //
+ if ((2 == pos->uNonPawnCount[uSide][KNIGHT]) &&
+ (0 == pos->uNonPawnCount[uSide][BISHOP]) &&
+ (0 == pos->uNonPawnCount[uSide][ROOK]) &&
+ (0 == pos->uNonPawnCount[uSide][QUEEN]) &&
+ (1 == pos->uNonPawnCount[uEnemy][0]) &&
+ (0 == pos->uPawnCount[uEnemy]))
+ {
+ return(FALSE);
+ }
+
+ return(TRUE);
+}
+
void
InitEval(void)
{
- // No-op placeholder: nothing to initialize until the
- // ROOK_FULL_HALF_OPEN_BONUS static cache (from the eval.c overhaul,
- // not yet re-applied here -- see stash) exists.
+ ROOK_FULL_HALF_OPEN_BONUS[0][0] = ROOK_ON_FULL_OPEN;
+ ROOK_FULL_HALF_OPEN_BONUS[0][1] = ROOK_ON_HALF_OPEN_WITH_ENEMY;
+ ROOK_FULL_HALF_OPEN_BONUS[1][0] = ROOK_ON_HALF_OPEN_WITH_FRIEND;
+ ROOK_FULL_HALF_OPEN_BONUS[1][1] = 0;
}
@@ -5004,6 +5062,7 @@ Return value:
COOR c;
ULONG u;
ULONG uColor;
+ ULONG xColor;
BITBOARD bb;
FLAG fDeferred;
#ifdef EVAL_TIME
@@ -5359,19 +5418,26 @@ Return value:
// just "do the rook-bitboard walk after the minor-bitboard walks"
// -- nothing to defer.
//
- // Phase order preserved exactly as before (side-to-move's minors,
- // then the other side's minors, then rooks both colors, then
- // queens both colors) -- that ordering is load-bearing for
- // bvAttacks accumulation (each piece's mobility/danger depends on
- // attack bits already written by earlier-evaluated pieces this
- // same Eval() call). Knight-vs-bishop order *within* the same
- // color/phase, and encounter order within a single type's own
- // bitboard walk, were never meaningful before (cNonPawns' order
- // is arbitrary swap-with-last-on-removal, not stable) and stay
- // that way -- EVAL_TERM's plain score accumulation doesn't care.
+ // board_representation/EVAL.md section 9: the real dependency
+ // isn't "side to move first" -- it's "both colors' minors done
+ // before any rook or queen, both rooks done before any queen,
+ // everything above done before either king" (_EvalRook/_EvalQueen
+ // read the enemy's pos->bbMinorAttacks; _EvalKing reads both
+ // colors' bbMinorAttacks/bbQueenAttacks; _ReEvalPassers further
+ // down needs the king scores). Interleaving ours-then-enemy within
+ // each type (rather than both colors' minors, then both colors'
+ // rooks, then both colors' queens as separate phases) still
+ // satisfies that same ordering -- knight-vs-bishop order *within*
+ // a color, and encounter order within a single type's own bitboard
+ // walk, were never meaningful (cNonPawns' order is arbitrary
+ // swap-with-last-on-removal, not stable) and stay that way --
+ // EVAL_TERM's plain score accumulation doesn't care.
//
pos->uMinorsAtHome[BLACK] = pos->uMinorsAtHome[WHITE] = 0;
uColor = pos->uToMove;
+ xColor = FLIP(uColor);
+
+ // Knights.
bb = pos->bbPieces[uColor][KNIGHT];
while (bb)
{
@@ -5381,37 +5447,44 @@ Return value:
TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight,
_EvalKnight(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After N:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After our N at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- bb = pos->bbPieces[uColor][BISHOP];
+
+ // Enemy knights.
+ ASSERT(xColor != pos->uToMove);
+ bb = pos->bbPieces[xColor][KNIGHT];
while (bb)
{
u = FastFirstBit(bb) - 1;
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
- TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
- _EvalBishop(pos, c, pHash));
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight,
+ _EvalKnight(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After B:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After enemy N at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- uColor = FLIP(uColor);
- ASSERT(uColor != pos->uToMove);
- bb = pos->bbPieces[uColor][KNIGHT];
+ // Bishops.
+ bb = pos->bbPieces[uColor][BISHOP];
while (bb)
{
u = FastFirstBit(bb) - 1;
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
- TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight,
- _EvalKnight(pos, c, pHash));
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
+ _EvalBishop(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After N:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After our B at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- bb = pos->bbPieces[uColor][BISHOP];
+
+ // Enemy bishops.
+ bb = pos->bbPieces[xColor][BISHOP];
while (bb)
{
u = FastFirstBit(bb) - 1;
@@ -5420,14 +5493,12 @@ Return value:
TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
_EvalBishop(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After B:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After enemy B at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- //
- // Evaluate any rook(s) for side on move then for side not on move.
- //
- uColor = FLIP(uColor);
+ // Rooks.
bb = pos->bbPieces[uColor][ROOK];
while (bb)
{
@@ -5436,12 +5507,13 @@ Return value:
c = BIT_NUMBER_TO_COOR(u);
TIMED_EVAL_CALL(ctx, u64CyclesEvalRook, _EvalRook(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After R:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After our R at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- uColor = FLIP(uColor);
- bb = pos->bbPieces[uColor][ROOK];
+ // Enemy rooks.
+ bb = pos->bbPieces[xColor][ROOK];
while (bb)
{
u = FastFirstBit(bb) - 1;
@@ -5449,14 +5521,12 @@ Return value:
c = BIT_NUMBER_TO_COOR(u);
TIMED_EVAL_CALL(ctx, u64CyclesEvalRook, _EvalRook(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After R:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After enemy R at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- //
- // Evaluate any queen(s) for side on move then for side not on move.
- //
- uColor = FLIP(uColor);
+ // Our queen(s).
bb = pos->bbPieces[uColor][QUEEN];
while (bb)
{
@@ -5465,12 +5535,13 @@ Return value:
c = BIT_NUMBER_TO_COOR(u);
TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen, _EvalQueen(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After Q:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After our Q at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- uColor = FLIP(uColor);
- bb = pos->bbPieces[uColor][QUEEN];
+ // Enemy queen(s).
+ bb = pos->bbPieces[xColor][QUEEN];
while (bb)
{
u = FastFirstBit(bb) - 1;
@@ -5478,7 +5549,8 @@ Return value:
c = BIT_NUMBER_TO_COOR(u);
TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen, _EvalQueen(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After Q:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After enemy Q at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
@@ -5496,7 +5568,6 @@ Return value:
}
#endif
TIMED_EVAL_CALL(ctx, u64CyclesEvalKing, _EvalKing(pos, c, pHash, ctx));
- ctx->sPlyInfo[ctx->uPly].iKingScore[BLACK] = pos->iTempScore;
#ifdef EVAL_DUMP
Trace("After *k:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
@@ -5512,7 +5583,6 @@ Return value:
}
#endif
TIMED_EVAL_CALL(ctx, u64CyclesEvalKing, _EvalKing(pos, c, pHash, ctx));
- ctx->sPlyInfo[ctx->uPly].iKingScore[WHITE] = pos->iTempScore;
#ifdef EVAL_DUMP
Trace("After .k:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
@@ -5528,7 +5598,7 @@ Return value:
bb = (pHash->bbPasserLocations[WHITE] | pHash->bbPasserLocations[BLACK]);
if (0 != bb)
{
- _EvalPassers(pos, pHash);
+ _ReEvalPassers(pos, pHash);
#ifdef EVAL_DUMP
Trace("After passers:\n%d\t\t%d\n", pos->iScore[WHITE],
pos->iScore[BLACK]);
@@ -5548,73 +5618,8 @@ Return value:
_EvalLookForDanger(ctx);
_EvalTrappedPieces(ctx);
- //
- // B over N in the endgame with 2 pawn wings.
- //
- if ((pos->uNonPawnCount[WHITE][0] <= 2) &&
- (pos->uNonPawnCount[BLACK][0] <= 2))
- {
- if ((pos->uNonPawnCount[WHITE][BISHOP] > 0) &&
- (pos->uNonPawnCount[BLACK][BISHOP] > 0))
- {
- if ((pos->uNonPawnCount[BLACK][0] == 2) &&
- (pos->uNonPawnCount[WHITE][0] == 2))
- {
- ASSERT(pos->uNonPawnCount[BLACK][BISHOP] == 1);
- ASSERT(pos->uNonPawnCount[WHITE][BISHOP] == 1);
- if (pos->uWhiteSqBishopCount[BLACK] +
- pos->uWhiteSqBishopCount[WHITE] == 1)
- {
- pos->iScore[WHITE] /= 2;
- pos->iScore[BLACK] /= 2;
- }
- }
- }
- else
- {
- //
- // At least one side has no bishop. Look for positions
- // with two pawn wings where having a bishop is an
- // advantage.
- //
- bb = (pos->bbPawns[WHITE] |
- pos->bbPawns[BLACK]);
- ASSERT((BBFILE[A] | BBFILE[B] | BBFILE[C]) ==
- 0x0707070707070707ULL);
- ASSERT((BBFILE[F] | BBFILE[G] | BBFILE[H]) ==
- 0xe0e0e0e0e0e0e0e0ULL);
- if ((bb & 0x0707070707070707ULL) &&
- (bb & 0xe0e0e0e0e0e0e0e0ULL))
- {
- if ((pos->uNonPawnCount[BLACK][BISHOP] == 0) &&
- (pos->uNonPawnCount[WHITE][BISHOP] > 0))
- {
- EVAL_TERM(WHITE,
- BISHOP,
- 0x88,
- pos->iScore[WHITE],
- BISHOP_OVER_KNIGHT_IN_ENDGAME *
- pos->uNonPawnCount[WHITE][BISHOP],
- "endgame w/ 2 pawn flanks");
- }
- else if ((pos->uNonPawnCount[BLACK][BISHOP] > 0) &&
- (pos->uNonPawnCount[WHITE][BISHOP] == 0))
- {
- EVAL_TERM(BLACK,
- BISHOP,
- 0x88,
- pos->iScore[BLACK],
- BISHOP_OVER_KNIGHT_IN_ENDGAME *
- pos->uNonPawnCount[BLACK][BISHOP],
- "endgame w/ 2 pawn flanks");
- }
- }
- }
-#ifdef EVAL_DUMP
- Trace("After BOOC / BvsN:\n%d\t\t%d\n",
- pos->iScore[WHITE], pos->iScore[BLACK]);
-#endif
- }
+ // TODO: endgame-specific knowledge? e.g. B over N in an endgame
+ // with 2 pawn wings?
//
// Roll in the reduced material down scaler terms.
@@ -5649,10 +5654,46 @@ Return value:
// TODO: detect and discourage blocked positions?
//
- //
- // TODO: drive the score towards zero as we approach a 50 move w/o
+ // If the side who's ahead by raw material/positional score can't
+ // actually force a win with what it has left on the board (see
+ // _SideHasWinningChances), squash the score hard towards draw --
+ // same idiom as the 50-move dampening just below (and the same
+ // signed-arithmetic care: g_iDrawScore is currently always 0, but
+ // write this relative to it rather than assuming that, matching
+ // the 50-move code's own convention). /16 (not a full collapse to
+ // drawscore) deliberately mirrors Crafty's EvaluateDraws -- e.g.
+ // KRB vs KR is still theoretically losable by the run-of-the-mill
+ // defender with the checks _SideHasWinningChances gates on, so
+ // some residual signal survives.
+ if ((iScoreForSideToMove > g_iDrawScore[pos->uToMove]) &&
+ (FALSE == _SideHasWinningChances(pos, pos->uToMove)))
+ {
+ iScoreForSideToMove = g_iDrawScore[pos->uToMove] +
+ ((iScoreForSideToMove - g_iDrawScore[pos->uToMove]) / 16);
+ }
+ else if ((iScoreForSideToMove < g_iDrawScore[pos->uToMove]) &&
+ (FALSE == _SideHasWinningChances(pos, FLIP(pos->uToMove))))
+ {
+ iScoreForSideToMove = g_iDrawScore[pos->uToMove] +
+ ((iScoreForSideToMove - g_iDrawScore[pos->uToMove]) / 16);
+ }
+
+ // Drive the score towards draw as we approach a 50 move w/o
// progress draw.
- //
+ if (pos->uFifty > 84)
+ {
+ ULONG uDrawDist = 101 - pos->uFifty;
+ ASSERT(uDrawDist > 0);
+ // uDrawDist is ULONG -- multiplying a negative SCORE by it
+ // directly promotes the SCORE to unsigned first (usual
+ // arithmetic conversions, same rank), wrapping a negative
+ // iScoreForSideToMove into a huge positive garbage value
+ // instead of scaling it down. Cast uDrawDist to SCORE so the
+ // multiply happens in signed arithmetic; its range (1-16) is
+ // always representable.
+ iScoreForSideToMove = g_iDrawScore[pos->uToMove] +
+ (iScoreForSideToMove * (SCORE)uDrawDist / 16);
+ }
//
// Adjust dynamic positional component.