diff options
Diffstat (limited to 'src/eval.c')
| -rwxr-xr-x | src/eval.c | 611 |
1 files changed, 326 insertions, 285 deletions
@@ -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. |
