/** Copyright (c) Scott Gasch Module Name: eval.c Abstract: Position evaluation routines. Author: Scott Gasch (scott.gasch@gmail.com) 14 Jun 2004 Revision History: $Id: eval.c 357 2008-07-03 16:18:11Z scott $ **/ #include "chess.h" typedef void (*PEVAL_HELPER)(POSITION *, COOR, PAWN_HASH_ENTRY *); typedef FLAG (FASTCALL *PMOBILITY_HELPER)(POSITION *, COOR, ULONG *, ULONG *); // // To simplify code / maintenance I use the same loop for both colors // in some places. These globals coorespond to "ahead of the piece" // or "behind the piece" for each color. // const int g_iAhead[2] = { +1, -1 }; 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, 89, 89, 59, 46, 25, 4, 50, 50,50,50,50,50,50,50,-1}, { -1, 125, 125, 109, 132, 123, 100, 78, 71, 66,66,66,66,66,66,66,-1}, { -1, 158, 180, 148, 92, 109, 70, 46, 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 { -59, -63, 0, +3, +14, +18, +-9, +-19, +-20 }, // 0 { -31, 7, 0, +52, +41, +14, +19, +41, +32 }, // 1 { -58, 0, 0, +47, +-9, +4, +-24, +-19, +44 }, // 2 }; static ULONG REDUCED_MATERIAL_DOWN_SCALER[32] = { // none na na m na R 2m na 0, 0, 0, 0, 0, 0, 0, 0, // Rm 3m/Q 2R R2m 4m/Qm 2Rm R3m/QR Q2m 0, 1, 1, 1, 1, 1, 10, 11, // 2R2m R4m/QRm Q3m 2R3m/ QR2m Q4m 2R4m/ QR3m // Q2R Q2Rm 4, 5, 6, 6, 7, 7, 7, 7, // na Q2R2m QR4m na Q2R3m na na full 8, 8, 8, 8, 8, 8, 8, 8, }; static ULONG REDUCED_MATERIAL_UP_SCALER[32] = { // none na na m na R 2m na 8, 8, 8, 8, 8, 8, 8, 8, // Rm 3m/Q 2R R2m 4m/Qm 2Rm R3m/QR Q2m 8, 15, 15, 15, 15, 6, 6, 5, // 2R2m R4m/QRm Q3m 2R3m/ QR2m Q4m 2R4m/ QR3m // Q2R Q2Rm 12, 11, 11, 11, 10, 10, 10, 9, // na Q2R2m QR4m na Q2R3m na na full 9, 9, 0, 0, 0, 0, 0, 0, }; static ULONG PASSER_MATERIAL_UP_SCALER[32] = { // none na na m na R 2m na 16, 16, 16, 16, 16, 16, 15, 15, // Rm 3m/Q 2R R2m 4m/Qm 2Rm R3m/QR Q2m 15, 14, 15, 13, 12, 13, 12, 11, // 2R2m R4m/QRm Q3m 2R3m/ QR2m Q4m 2R4m/ QR3m // Q2R Q2Rm 2, 2, 9, 9, -8, -8, -8, -8, // na Q2R2m QR4m na Q2R3m na na full -8, -8, -8, -8, -8, -8, -8, -8, }; COOR QUEENING_RANK[2] = { 1, 8 }; COOR JUMPING_RANK[2] = { 7, 2 }; // // Pawn eval terms // --------------------------------------------------------------------------- // // "There is one case which can be treated as positional or material, // namely the rook's pawn, which differs from other pawns in that it // can only capture one way instead of two. Since this handicap cannot // be corrected without the opponent's help, I teach my students to // regard the rook's pawn as a different piece type, a crippled // pawn. Database statistics indicate that it is on average worth // about 15% less than a normal pawn. The difference is enough so that // it is usually advantageous to make a capture with a rook's pawn, // promoting it to a knights pawn, even if that produces doubled pawns // and even if there is no longer a rook on the newly opened rook's // file." // --Larry Kaufman, IM // "Evaluation of Material Imbalance" // static SCORE PAWN_CENTRALITY_BONUS[128] = { 8, 8, 8, 8, 8, 8, 8, 8, 8,8,8,8,8,8,8,8, -8, 8, 8, 8, 8, 8, 8, -8, 8,8,8,8,8,8,8,8, -8, 8, -3, -3, -3, -3, 8, -8, 8,8,8,8,8,8,8,8, -8, 8, -3, 17, 17, -3, 8, -8, 8,8,8,8,8,8,8,8, // ------------------------------------------------------------------- -8, 8, -3, 17, 17, -3, 8, -8, 8,8,8,8,8,8,8,8, -8, 8, -3, -3, -3, -3, 8, -8, 8,8,8,8,8,8,8,8, -8, 8, 8, 8, 8, 8, 8, -8, 8,8,8,8,8,8,8,8, 8, 8, 8, 8, 8, 8, 8, 8, 8,8,8,8,8,8,8,8 }; static SCORE BACKWARD_SHIELDED_BY_LOCATION[128] = { +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0, 17, 3, 3, 17, 17, 3, 3, 17, 0,0,0,0,0,0,0,0, 3, 12, 12, 3, 3, 12, 12, 3, 0,0,0,0,0,0,0,0, 12, -3, -3, 12, 12, -3, -3, 12, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- 12, -3, -3, 12, 12, -3, -3, 12, 0,0,0,0,0,0,0,0, 3, 12, 12, 3, 3, 12, 12, 3, 0,0,0,0,0,0,0,0, 17, 3, 3, 17, 17, 3, 3, 17, 0,0,0,0,0,0,0,0, +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0 }; static SCORE BACKWARD_EXPOSED_BY_LOCATION[128] = { +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0, -12, 7, 7, -27, -27, 7, 7, -12, 0,0,0,0,0,0,0,0, 7, 1, 1, 7, 7, 1, 1, 7, 0,0,0,0,0,0,0,0, 1, -6, -6, 1, 1, -6, -6, 1, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- 1, -6, -6, 1, 1, -6, -6, 1, 0,0,0,0,0,0,0,0, 7, 1, 1, 7, 7, 1, 1, 7, 0,0,0,0,0,0,0,0, -12, 7, 7, -27, -27, 7, 7, -12, 0,0,0,0,0,0,0,0, +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0 }; // // "The first statement I can confirm is that doubled pawns are indeed // on average undesirable. However this statistic needs to be broken // down to be useful. Doubled pawns themselves are really more // serious than this generally, but when your pawns are doubled you // automatically get an extra half-open file for your rooks (or queen, // but the queen can also use diagonals). So it follows logically // that the net cost of doubled pawns is much greater in the absence // of major pieces. The database shows that with all rooks present, // doubled pawns "cost" only about 1/16th of a pawn on average. With // one rook each the cost rises to 1/4th of a pawn and with no rooks // present to 3/8ths of a pawn. With the queens present the cost is // again only 1/16th of a pawn; without them it's a quarter pawn. So // the lessons are clear; beware of doubled pawns when major pieces // have been exchanged, and beware of exchanging major pieces when you // are the one with the doubled pawns." // --Larry Kaufman, IM // "All About Doubled Pawns" // // Note: indexed by number of files with 1+ pawn on them. static SCORE DOUBLED_PAWN_PENALTY_BY_COUNT[4][9] = { // ------------ count of doubled+ pawns ------------ {// 0 1 2 3 4 5 6 7 8 : no majors alive +0, -32, -41, -91, -102, -170, -207, -222, -250 }, {// : 1 rook +0, -7, 1, -76, -76, -120, -184, -200, -216 }, {// : 2 rooks or 1 queen +0, -21, -7, -18, -30, -48, -78, -111, -138 }, {// : all majors alive +0, -7, -21, -25, -31, -47, -57, -95, -121 }, }; static SCORE ISOLATED_PAWN_PENALTY_BY_COUNT[9] = {// 0 1 2 3 4 5 6 7 8 +16, 5, -7, -33, -32, -19, -34, -85, -100 }; static SCORE ISOLATED_PAWN_BY_PAWNFILE[9] = { 0, 1, -24, -9, -18, -18, -9, -24, 1 }; static SCORE ISOLATED_EXPOSED_PAWN = -5; static SCORE ISOLATED_DOUBLED_PAWN = -1; // // Note: -25% to -33% if the enemy occupies or controls the next sq. // static SCORE PASSER_BY_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +0, +0,+194,+135, +54, +20, +-6, +-3, +0 }, // black { +0, +0, +-3, +-6, +20, +54,+135,+194, +0 } // white }; static SCORE CANDIDATE_PASSER_BY_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +0, +0, +0, +48, +42, +14, +21, +17, +0 }, // black { +0, +0, +17, +21, +14, +42, +48, +0, +0 } // white }; // Note: x2 static SCORE CONNECTED_PASSERS_BY_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +0, +0, +80, +58, +56, +29, +10, +21, +0 }, // black { +0, +0, +21, +10, +29, +56, +58, +80, +0 } // white }; static SCORE SUPPORTED_PASSER_BY_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +0, +0, +92, +80, +21, +6, +11, +1, +0 }, // black { +0, +0, +1, +11, +6, +21, +80, +92, +0 } // white }; static SCORE OUTSIDE_PASSER_BY_DISTANCE[9] = {// 0 1 2 3 4 5 6 7 8 +0, +0, +15, +30, +21, +28, +34, +41, +55 }; static SCORE PASSER_BONUS_AS_MATERIAL_COMES_OFF[32] = { // none na na m na R 2m na +92, -1, -1, +53, -1, +3, +19, -1, // Rm 3m/Q 2R R2m 4m/Qm 2Rm R3m/QR Q2m +6, +9, +12, +-4, +-5, +-5, 8, 8, // 2R2m R4m/QRm Q3m 2R3m/ QR2m Q4m 2R4m/ QR3m // Q2R Q2Rm 8, 8, 8, 8, 8, 8, 8, 8, // na Q2R2m QR4m na Q2R3m na na full 8, 8, 8, 8, 8, 8, 8, 8, }; SCORE RACER_WINS_RACE = +784; static SCORE UNDEVELOPED_MINORS_IN_OPENING[5] = {// 0 1 2 3 4 -8, -14, -10, -24, -38 }; // // Bishop eval terms // --------------------------------------------------------------------------- // static SCORE BISHOP_OVER_KNIGHT_IN_ENDGAME = +1; // // "The bishop pair has an average value of half a pawn (more when the // opponent has no minor pieces to exchange for one of the bishops), // enough to regard it as part of the material evaluation of the // position, and enough to overwhelm most positional considerations. // Moreover, this substantial bishop pair value holds up in all // situations tested, regardless of what else is on the board... // // ...One rule which I often teach to students is that if you have the // bishop pair, and your opponent's single bishop is a bad bishop // (hemmed in by his own pawns), you already have full compensation // for a pawn... Kasparov has said something similar... // // As noted before, the bishop pair is worth more with fewer pawns on // the board. Aside from this factor, the half pawn value of the // bishop pair is remarkably constant, applying even when there are no // pieces on the board except two minors each." // --Larry Kaufman, IM // "Evaluation of Material Imbalance" static SCORE BISHOP_PAIR[2][17] = { { -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16 }, { 21, 68, 51, 34, 41, 48, 55, 37, 59, 57, 40, 40, 40, 40, 40, 40, 40 } }; static SCORE STATIONARY_PAWN_ON_BISHOP_COLOR[128] = { +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0, -20, -14, -7, -7, -7, -7, -14, -20, 0,0,0,0,0,0,0,0, -14, -7, -17, -8, -8, -17, -7, -14, 0,0,0,0,0,0,0,0, -7, -8, -10, -11, -11, -10, -8, -7, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- -7, -8, -10, -11, -11, -10, -8, -7, 0,0,0,0,0,0,0,0, -14, -7, -17, -8, -8, -17, -7, -14, 0,0,0,0,0,0,0,0, -20, -14, -7, -7, -7, -7, -14, -20, 0,0,0,0,0,0,0,0, +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0 }; static SCORE TRANSIENT_PAWN_ON_BISHOP_COLOR[128] = { +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0, -1, -1, -2, -2, -2, -2, -1, -1, 0,0,0,0,0,0,0,0, -11, -4, -4, -4, -4, -4, -4, -11, 0,0,0,0,0,0,0,0, -4, -5, 1, -16, -16, 1, -5, -4, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- -4, -5, 1, -16, -16, 1, -5, -4, 0,0,0,0,0,0,0,0, -11, -4, -4, -4, -4, -4, -4, -11, 0,0,0,0,0,0,0,0, -1, -1, -2, -2, -2, -2, -1, -1, 0,0,0,0,0,0,0,0, +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0 }; // // A bishop can move to between 0..13 squares. // static SCORE BISHOP_MOBILITY_BY_SQUARES[14] = {// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 -38, -22, -26, -13, -9, 0, +1, +11, +4, +13, +22, +15, +40, +-7 };// ^ | static SCORE BISHOP_MAX_MOBILITY_IN_A_ROW_BONUS[8] = {// 0 1 2 3 4 5 6 7 -18, 4, +9, +3, +12, +-3, +-3, +-3 }; static SCORE BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING[9] = {// 0 1 2 3 4 5 6 7 8 +8, +1, +36, +26, +32, +-12, +8, -26, -16 }; static SCORE BISHOP_IN_CLOSED_POSITION[33] = {// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 2, 2, 2, 17, 17, 8, 8, 7, 7, -2, 13, 20, 3, 18, 9, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 // 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 }; // // Knight eval terms // --------------------------------------------------------------------------- // static SCORE KNIGHT_CENTRALITY_BONUS[128] = { -60, -16, -16, -16, -16, -16, -16, -60, 0,0,0,0,0,0,0,0, -16, -2, -2, 0, 0, -2, -2, -16, 0,0,0,0,0,0,0,0, -16, -2, +-4, +13, +13, +-4, -2, -16, 0,0,0,0,0,0,0,0, -16, -2, +13, +16, +16, +13, -2, -16, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- -16, -2, +13, +16, +16, +13, -2, -16, 0,0,0,0,0,0,0,0, -16, -2, +-4, +13, +13, +-4, -2, -16, 0,0,0,0,0,0,0,0, -16, -2, -2, 0, 0, -2, -2, -16, 0,0,0,0,0,0,0,0, -60, -16, -16, -16, -16, -16, -16, -60, 0,0,0,0,0,0,0,0 }; static SCORE KNIGHT_KING_TROPISM_BONUS[9] = {// 0 1 2 3 4 5 6 7 8 8, +-9, +12, +1, +12, +8, +8, +8, +8 }; static SCORE KNIGHT_UNASSAILABLE_BY_DIST_FROM_EKING[9] = {// 0 1 2 3 4 5 6 7 8 +-24, +9, +11, +38, +14, +-24, +-24, +-24, +-24 }; static SCORE KNIGHT_ON_INTERESTING_SQUARE_BY_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +-8, +4, +27, +25, +22, +3, +-8, +-8, +-8 }, // black { +-8, +-8, +-8, +-8, +3, +22, +25, +27, +4 } // white }; // // A knight can move to between 0..8 squares // ----------------------------------------- // 0 = supported enemy pawn // 1 = unsupported enemy pawn, friend pawn, enemy B/N // enemy controlled empty sq. // 2 = enemy >B, friend controlled empty sq, no one controlled empty sq // // Total: 16 mobility max // static SCORE KNIGHT_MOBILITY_BY_COUNT[9] = {// 0 1 2 3 4 5 6 7 8 -33, -18, -14, -8, +-5, +5, +15, +8, +8 };// ^ | static SCORE KNIGHT_WITH_N_PAWNS_SUPPORTING[3] = { +16, +12, +0 }; static SCORE KNIGHT_IN_CLOSED_POSITION[33] = {// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 18, 4, 6, 17, -13, 12, 5, -17, 1, 19, -3, -3, 30, 30, 23, 23, 24, 24, 25, 25, 26, 27 // 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 }; // // Rook eval terms // --------------------------------------------------------------------------- // static SCORE ROOK_ON_FULL_OPEN_BY_DIST_FROM_EKING[8] = {// 0 1 2 3 4 5 6 7 +48, +38, +41, +38, +29, +29, +20, +20 }; static SCORE ROOK_ON_HALF_OPEN_WITH_ENEMY_BY_DIST_FROM_EKING[8] = {// 0 1 2 3 4 5 6 7 +12, +11, +17, +17, +32, +32, +32, +15 }; static SCORE ROOK_ON_HALF_OPEN_WITH_FRIEND_BY_DIST_FROM_EKING[8] = {// 0 1 2 3 4 5 6 7 +-3, +12, +11, +11, +17, +17, +17, +16 }; static SCORE ROOK_BEHIND_PASSER_BY_PASSER_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +-24, +-24, +41, +17, +4, +-2, +-31, +-24, +-24 }, // black { +-24, +-24, +-24, +-31, +-2, +4, +17, +41, +-24 } // white }; static SCORE ROOK_LEADS_PASSER_BY_PASSER_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +0, +0, -54, -16, 11, -9, 19, -3, +0 }, // black { +0, +0, -3, 19, -9, 11, -16, -54, +0 } // white }; static SCORE KING_TRAPPING_ROOK = -48; static SCORE ROOK_TRAPPING_EKING = +46; static SCORE ROOK_VALUE_AS_PAWNS_COME_OFF[17] = {// 0 1 2 3 4 5 6 7 8 +7, +35, +92, +46, +33, +-5, +-10, +16, +-17, // 9 10 11 12 13 14 15 16 +-15, -27, -38, -57, -44, -42, -70, -41 }; // // Note: these are multiplied by two (one per rook). // static SCORE ROOK_CONNECTED_VERT = +7; // x2 static SCORE ROOK_CONNECTED_HORIZ = +4; // x2 // // A rook can move to between 0..14 squares // ---------------------------------------- // 0 = supported enemy P/N/B, friend piece // 1 = unsupported enemy piece, enemy controlled empty sq, enemy R // 2 = enemy >R, friend controlled empty sq, no one controlled empty sq // // Total: 28 mobility max // static SCORE ROOK_MOBILITY_BY_SQUARES[15] = {// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -36, -16, -20, -14, -7, -10, +0, +4, +8, +12, +15, +9, +19, +13, +14 };// ^ | static SCORE ROOK_MAX_MOBILITY_IN_A_ROW_BONUS[8] = {// 0 1 2 3 4 5 6 7 1, 10, +8, +12, +8, +8, +8, +8 }; // // Queen eval terms // --------------------------------------------------------------------------- // // // A queen can move to between 0..27 squares // ----------------------------------------- // 0 = supported enemy P/N/B/R, friend piece // 1 = unsupported enemy piece, enemy controlled empty sq, enemy Q // 2 = enemy K, no one controlled empty sq, friend controlled empty sq // // Total: 54 mobility max // static SCORE QUEEN_MOBILITY_BY_SQUARES[28] = {// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -54, -34, -22, -17, -19, -8, -4, -2, 7, +10, +12, +15, +18, +4, +30, // | //15 16 17 18 19 20 21 22 23 24 25 26 27 +31, +32, +41, +42, +35, +36, +36, +21, +21, +6, +6, +7, +7 }; static SCORE QUEEN_OUT_EARLY[5] = {// 0 1 2 3 4 : num unmoved minors 0, -14, -6, -2, -25 }; // // In "Evaluation of Material Imbalance", IM Larry Kaufman makes the // point that queens are worth more then the standard nine "points". // Thus, this scale is biased upwards by about 0.15 pawn. // static SCORE QUEEN_KING_TROPISM[8] = {// 0 1 2 3 4 5 6 7 0, +34, +76, +30, +27, +9, +8, +-1 }; static SCORE QUEEN_ATTACKS_SQ_NEXT_TO_KING = -8; // x #sq_attacked // // King eval terms // --------------------------------------------------------------------------- // static ULONG KING_INITIAL_COUNTER_BY_LOCATION[2][128] = { { 1, 0, 0, 0, 1, 0, 0, 1, 0,0,0,0,0,0,0,0, 1, 1, 1, 1, 1, 1, 1, 1, 0,0,0,0,0,0,0,0, 3, 3, 3, 3, 3, 3, 3, 3, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0 }, { 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 3, 3, 3, 3, 3, 3, 3, 3, 0,0,0,0,0,0,0,0, 1, 1, 1, 1, 1, 1, 1, 1, 0,0,0,0,0,0,0,0, 1, 0, 0, 0, 1, 0, 0, 1, 0,0,0,0,0,0,0,0 } }; static SCORE KING_TO_CENTER[128] = { +-32, +-32, +-32, +-32, +-32, +-32, +-32, +-32, -32,-32,-32,-32,-32,-32,-32,-32, +-23, +3, +5, +-1, +-1, +5, +3, +-23, -32,-32,-32,-32,-32,-32,-32,-32, +3, +5, +13, +15, +15, +13, +5, +3, -32,-32,-32,-32,-32,-32,-32,-32, +5, +-1, +41, +31, +31, +41, +-1, +5, -32,-32,-32,-32,-32,-32,-32,-32, // ------------------------------------------------------------------ +5, +-1, +41, +31, +31, +41, +-1, +5, -32,-32,-32,-32,-32,-32,-32,-32, +3, +5, +13, +15, +15, +13, +5, +3, -32,-32,-32,-32,-32,-32,-32,-32, +-23, +3, +5, +-1, +-1, +5, +3, +-23, -32,-32,-32,-32,-32,-32,-32,-32, +-32, +-32, +-32, +-32, +-32, +-32, +-32, +-32, -32,-32,-32,-32,-32,-32,-32,-32 }; // // If a side has less than this much material we don't bother scoring // the other side's king safety. Note: this doesn't incl pawn // material. // #define DO_KING_SAFETY_THRESHOLD \ (VALUE_ROOK + VALUE_BISHOP + VALUE_KING + 1) // // If a side has less than this much material the other side's king // can come out. Note: this doesn't incl pawn material. // #define KEEP_KING_AT_HOME_THRESHOLD \ (VALUE_ROOK + VALUE_ROOK + VALUE_BISHOP + VALUE_KING + 1) static ULONG KING_COUNTER_BY_ATTACK_PATTERN[32] = { //p 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 //m 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 //r 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 //q 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 //k 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0,0,2,2,1,1,3,3,1,1,3,3,2,2,4,4,2,2,3,3,2,2,4,4,2,2,4,4,3,4,5,5 }; static SCORE KING_SAFETY_BY_COUNTER[42] = { 0, -4, -40, -28, -24, -28, -33, -39, -30, -50, -55, -60, -90, -97,-104,-135,-150,-141,-148,-211,-210,-217,-240,-255,-286,-306, -262,-330,-350,-386,-390,-378,-448,-462,-508,-508,-508,-508,-508, -508,-508,-508 }; static SCORE KING_MISSING_ONE_CASTLE_OPTION = 9; typedef struct _DNA_BASE_SIZE { SCORE *pBase; ULONG uCount; } DNA_BASE_SIZE; #define DNA_VAR(x) {(SCORE *)&(x), 1} #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(REDUCED_MATERIAL_UP_SCALER), DNA_ARRAY(PASSER_MATERIAL_UP_SCALER), DNA_ARRAY(PAWN_CENTRALITY_BONUS), DNA_ARRAY(BACKWARD_SHIELDED_BY_LOCATION), DNA_ARRAY(BACKWARD_EXPOSED_BY_LOCATION), DNA_MATRIX(DOUBLED_PAWN_PENALTY_BY_COUNT), DNA_ARRAY(ISOLATED_PAWN_PENALTY_BY_COUNT), DNA_ARRAY(ISOLATED_PAWN_BY_PAWNFILE), DNA_VAR(ISOLATED_EXPOSED_PAWN), DNA_VAR(ISOLATED_DOUBLED_PAWN), DNA_MATRIX(PASSER_BY_RANK), DNA_MATRIX(CANDIDATE_PASSER_BY_RANK), DNA_MATRIX(CONNECTED_PASSERS_BY_RANK), DNA_MATRIX(SUPPORTED_PASSER_BY_RANK), DNA_ARRAY(OUTSIDE_PASSER_BY_DISTANCE), DNA_ARRAY(PASSER_BONUS_AS_MATERIAL_COMES_OFF), DNA_VAR(RACER_WINS_RACE), DNA_ARRAY(UNDEVELOPED_MINORS_IN_OPENING), DNA_VAR(BISHOP_OVER_KNIGHT_IN_ENDGAME), DNA_MATRIX(BISHOP_PAIR), DNA_ARRAY(STATIONARY_PAWN_ON_BISHOP_COLOR), DNA_ARRAY(TRANSIENT_PAWN_ON_BISHOP_COLOR), DNA_ARRAY(BISHOP_MOBILITY_BY_SQUARES), DNA_ARRAY(BISHOP_MAX_MOBILITY_IN_A_ROW_BONUS), DNA_ARRAY(BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING), DNA_ARRAY(BISHOP_IN_CLOSED_POSITION), DNA_ARRAY(KNIGHT_CENTRALITY_BONUS), DNA_ARRAY(KNIGHT_KING_TROPISM_BONUS), DNA_ARRAY(KNIGHT_UNASSAILABLE_BY_DIST_FROM_EKING), DNA_MATRIX(KNIGHT_ON_INTERESTING_SQUARE_BY_RANK), DNA_ARRAY(KNIGHT_MOBILITY_BY_COUNT), DNA_ARRAY(KNIGHT_WITH_N_PAWNS_SUPPORTING), DNA_ARRAY(KNIGHT_IN_CLOSED_POSITION), DNA_ARRAY(ROOK_ON_FULL_OPEN_BY_DIST_FROM_EKING), DNA_ARRAY(ROOK_ON_HALF_OPEN_WITH_ENEMY_BY_DIST_FROM_EKING), DNA_ARRAY(ROOK_ON_HALF_OPEN_WITH_FRIEND_BY_DIST_FROM_EKING), DNA_MATRIX(ROOK_BEHIND_PASSER_BY_PASSER_RANK), DNA_MATRIX(ROOK_LEADS_PASSER_BY_PASSER_RANK), DNA_VAR(KING_TRAPPING_ROOK), DNA_VAR(ROOK_TRAPPING_EKING), DNA_ARRAY(ROOK_VALUE_AS_PAWNS_COME_OFF), DNA_VAR(ROOK_CONNECTED_VERT), DNA_VAR(ROOK_CONNECTED_HORIZ), DNA_ARRAY(ROOK_MOBILITY_BY_SQUARES), DNA_ARRAY(ROOK_MAX_MOBILITY_IN_A_ROW_BONUS), DNA_ARRAY(QUEEN_MOBILITY_BY_SQUARES), DNA_ARRAY(QUEEN_OUT_EARLY), DNA_ARRAY(QUEEN_KING_TROPISM), DNA_VAR(QUEEN_ATTACKS_SQ_NEXT_TO_KING), DNA_MATRIX(KING_INITIAL_COUNTER_BY_LOCATION), DNA_ARRAY(KING_TO_CENTER), DNA_ARRAY(KING_SAFETY_BY_COUNTER), DNA_VAR(KING_MISSING_ONE_CASTLE_OPTION) }; ULONG DNABufferSizeBytes() { ULONG u; ULONG uSize = 0; for (u = 0; u < ARRAY_LENGTH(g_EvalDNA); u++) { uSize += 10 * g_EvalDNA[u].uCount; } return uSize + 1; } char * ExportEvalDNA() { ULONG uSize = DNABufferSizeBytes(); char *p = malloc(uSize); ULONG u, v; SCORE *q; FLAG fFirst = TRUE; memset(p, 0, uSize); for (u = 0; u < ARRAY_LENGTH(g_EvalDNA); u++) { q = g_EvalDNA[u].pBase; for (v = 0; v < g_EvalDNA[u].uCount; v++) { if (!fFirst) { sprintf(p, "%s,%d", p, *q); } else { sprintf(p, "%s%d", p, *q); fFirst = FALSE; } q++; } strcat(p, "\n"); fFirst = TRUE; } return p; // caller must free } FLAG WriteEvalDNA(char *szFilename) { FILE *p = NULL; char *q = NULL; FLAG fRet = FALSE; if (SystemDoesFileExist(szFilename)) goto end; p = fopen(szFilename, "a+b"); if (!p) goto end; q = ExportEvalDNA(); if (!q) goto end; fprintf(p, "%s", q); fRet = TRUE; end: if (p) fclose(p); if (q) free(q); return fRet; } FLAG ImportEvalDNA(char *p) { ULONG u, v; for (u = 0; u < ARRAY_LENGTH(g_EvalDNA); u++) { for (v = 0; v < g_EvalDNA[u].uCount; v++) { while(*p && (!isdigit(*p) && (*p != '-'))) p++; if (*p == '\0') return FALSE; *(g_EvalDNA[u].pBase + v) = atoi(p); while(*p && (isdigit(*p) || (*p == '-'))) p++; } } return TRUE; } FLAG ReadEvalDNA(char *szFilename) { FILE *p = NULL; ULONG uSize = DNABufferSizeBytes(); char *q = malloc(uSize); FLAG fRet = FALSE; static char line[1024]; char *c; if (!q) goto end; memset(q, 0, uSize); p = fopen(szFilename, "rb"); if (!p) { Trace("Failed to open file \"%s\"\n", szFilename); goto end; } while(fgets(line, 1024, p) != NULL) { c = strchr(line, '#'); if (c) *c = '\0'; c = line; strcat(q, c); } fRet = ImportEvalDNA(q); end: if (q) free(q); if (p) fclose(p); return fRet; } // // Misc stuff // --------------------------------------------------------------------------- // // // The three ranks "around" a rank (indexed by rank, used in eval.c) // BITBOARD BBADJACENT_RANKS[9] = { 0, BBRANK11 | BBRANK22, BBRANK11 | BBRANK22 | BBRANK33, BBRANK22 | BBRANK33 | BBRANK44, BBRANK33 | BBRANK44 | BBRANK55, BBRANK44 | BBRANK55 | BBRANK66, BBRANK55 | BBRANK66 | BBRANK77, BBRANK66 | BBRANK77 | BBRANK88, BBRANK77 | BBRANK88 }; // // The files "around" one, indexed by file (used in eval.c) // BITBOARD BBADJACENT_FILES[8] = { // A = 0 BBFILEB, // B = 1 BBFILEA | BBFILEC, // C = 2 BBFILEB | BBFILED, // D = 3 BBFILEC | BBFILEE, // E = 4 BBFILED | BBFILEF, // F = 5 BBFILEE | BBFILEG, // G = 6 BBFILEF | BBFILEH, // H = 7 BBFILEG }; // // The ranks preceeding one, indexed by rank/color of pawn (used in // eval.c) // BITBOARD BBPRECEEDING_RANKS[8][2] = { // BLACK WHITE // ----- ----- { // 0 == RANK8 BBRANK72, 0 }, { // 1 == RANK7 BBRANK62, 0 }, { // 2 == RANK6 BBRANK52, BBRANK77 }, { // 3 == RANK5 BBRANK42, BBRANK67 }, { // 4 == RANK4 BBRANK32, BBRANK57 }, { // 5 == RANK3 BBRANK22, BBRANK47 }, { // 6 == RANK2 0, BBRANK37 }, { // 7 == RANK1 0, BBRANK27 } }; static FLAG _IsSquareSafeFromEnemyPawn(IN POSITION *pos, IN COOR c, IN BITBOARD bb) /** Routine description: Determine if a piece in square c is "outposted". A piece is outposted if no enemy pawns can advance to attack it / drive it away. Parameters: POSITION *pos : the board COOR c : the square (must not be empty!) BITBOARD bb : the bitboard of pawns you want to consider Return value: FLAG : TRUE if the piece is safe/outposted, FALSE otherwise **/ { ULONG uColor = GET_COLOR(pos->rgSquare[c].pPiece); ULONG uFile, uRank; #ifdef DEBUG COOR cSquare; PIECE p = pos->rgSquare[c].pPiece; BITBOARD dbb = bb; ASSERT(IS_ON_BOARD(c)); ASSERT(p && (IS_BISHOP(p) || IS_KNIGHT(p) || IS_PAWN(p))); ASSERT(IS_VALID_COLOR(uColor)); #endif uFile = FILE(c); ASSERT(uFile < 8); bb &= BBADJACENT_FILES[uFile]; ASSERT(((c & 0x70) >> 4) == (c >> 4)); uRank = c >> 4; ASSERT(uRank < 8); bb &= BBPRECEEDING_RANKS[uRank][uColor]; #ifdef DEBUG if (bb != 0) { while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&dbb))) { ASSERT(pos->rgSquare[cSquare].pPiece); ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); if ((FILE(cSquare) == (FILE(c) - 1)) || (FILE(cSquare) == (FILE(c) + 1))) { switch(uColor) { case WHITE: if (RANK(cSquare) > RANK(c)) { return(FALSE); } break; case BLACK: if (RANK(cSquare) < RANK(c)) { return(FALSE); } break; } } } ASSERT(FALSE); } else { ASSERT(bb == 0); while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&dbb))) { ASSERT(pos->rgSquare[cSquare].pPiece); ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); if ((FILE(cSquare) == (FILE(c) - 1)) || (FILE(cSquare) == (FILE(c) + 1))) { switch(uColor) { case WHITE: if (RANK(cSquare) > RANK(c)) { ASSERT(FALSE); } break; case BLACK: if (RANK(cSquare) < RANK(c)) { ASSERT(FALSE); } break; } } } } #endif return(bb == 0); } static ULONG _WhoControlsSquareFast(IN POSITION *pos, IN COOR c) /** Routine description: Determine which side controls a board square. Parameters: POSITION *pos : the board COOR c : the square in question Return value: static ULONG : (ULONG)-1 if neither side controls it or the sides control it evenly, WHITE if white controls the square, or BLACK if black controls the square. TODO: fix this to use counts / xrays **/ { ULONG uWhite = pos->rgSquare[c|8].bvAttacks[WHITE].uSmall | pos->rgSquare[c|8].bvAttacks[WHITE].uXray; ULONG uBlack = pos->rgSquare[c|8].bvAttacks[BLACK].uSmall | pos->rgSquare[c|8].bvAttacks[BLACK].uXray; ULONG u; PIECE p; CHAR ch; ASSERT((c + 8) == (c | 8)); ASSERT((uWhite & 0xFFFFFF00) == 0); ASSERT((uBlack & 0xFFFFFF00) == 0); // TODO: keep these and update the table to use them uWhite >>= 3; uBlack >>= 3; p = pos->rgSquare[c].pPiece; // p -= 2; ch = g_SwapTable[p][uWhite][uBlack]; if (ch != 0) { u = ch; u >>= 7; u = u & 1; ASSERT(((u == 1) && (ch < 0)) || ((u == 0) && (ch > 0))); return(FLIP(u)); } return((ULONG)-1); } /** Routine description: Zero out the attack table before building it. Parameters: POSITION *pos Return value: void **/ #define CLEAR_A_SQ \ pos->rgSquare[c].bvAttacks[0].uWholeThing = 0; \ pos->rgSquare[c].bvAttacks[1].uWholeThing = 0; #define CLEAR_A_RANK \ CLEAR_A_SQ; c++; \ CLEAR_A_SQ; c++; \ CLEAR_A_SQ; c++; \ CLEAR_A_SQ; c++; \ CLEAR_A_SQ; c++; \ CLEAR_A_SQ; c++; \ CLEAR_A_SQ; c++; \ CLEAR_A_SQ; c += 9; #define CLEAR_SHORT_RANK \ CLEAR_A_SQ; c++; \ CLEAR_A_SQ; c++; \ CLEAR_A_SQ; c++; \ CLEAR_A_SQ; c++; \ CLEAR_A_SQ; c++; \ CLEAR_A_SQ; c++; \ CLEAR_A_SQ; c += 10; static void _ClearAttackTables(IN OUT POSITION *pos) { register COOR c = 8; #if 1 CLEAR_A_SQ; c += 16; CLEAR_A_SQ; c += 16; CLEAR_A_SQ; c += 16; CLEAR_A_SQ; c += 16; CLEAR_A_SQ; c += 16; CLEAR_A_SQ; c += 16; CLEAR_A_SQ; c += 16; CLEAR_A_SQ; c = 9; CLEAR_SHORT_RANK; CLEAR_SHORT_RANK; CLEAR_SHORT_RANK; CLEAR_SHORT_RANK; CLEAR_SHORT_RANK; CLEAR_SHORT_RANK; CLEAR_SHORT_RANK; CLEAR_SHORT_RANK; #else CLEAR_A_RANK; CLEAR_A_RANK; CLEAR_A_RANK; CLEAR_A_RANK; CLEAR_A_RANK; CLEAR_A_RANK; CLEAR_A_RANK; CLEAR_A_RANK; #endif } static INLINE void _InitializePawnHashEntry(IN OUT PAWN_HASH_ENTRY *pHash, IN POSITION *pos) /** Routine description: Parameters: PAWN_HASH_ENTRY *pHash, POSITION *pos Return value: static INLINE void **/ { memset(pHash, 0, sizeof(PAWN_HASH_ENTRY)); pHash->u64Key = pos->u64PawnSig; } static INLINE void _EvaluateCandidatePasser(IN POSITION *pos, IN OUT PAWN_HASH_ENTRY *pHash, IN COOR c) /** Routine description: Parameters: POSITION *pos, PAWN_HASH_ENTRY *pHash, COOR c Return value: static INLINE **/ { COOR c1, cSquare; PIECE pSentry, pHelper; ULONG uPawnFile = FILE(c) + 1; BITBOARD bb; ULONG uSentries, uHelpers; ULONG uColor; int d1; #ifdef DEBUG COOR cVerifySquare; ULONG uVerifySentries; #endif ASSERT(IS_ON_BOARD(c)); ASSERT(RANK(c) >= 2); ASSERT(RANK(c) <= 7); pHelper = pos->rgSquare[c].pPiece; ASSERT(IS_PAWN(pHelper)); uColor = GET_COLOR(pHelper); ASSERT(IS_VALID_COLOR(uColor)); if (pHash->uCountPerFile[FLIP(uColor)][uPawnFile] != 0) { ASSERT((pHash->bbPawnLocations[FLIP(uColor)] & BBFILE[FILE(c)]) != 0); // // The only way a pawn can be a passer/candidate if the other // side has a pawn on its same file is if that enemy pawn is // behind it. This is a corner case but it is important to // detect all passers. // switch(uColor) { case WHITE: bb = pHash->bbPawnLocations[BLACK] & BBFILE[FILE(c)]; ASSERT(bb); while(IS_ON_BOARD(c1 = CoorFromBitBoardRank8ToRank1(&bb))) { ASSERT(FILE(c1) == FILE(c)); if (c1 < c) return; } break; case BLACK: bb = pHash->bbPawnLocations[WHITE] & BBFILE[FILE(c)]; ASSERT(bb); while(IS_ON_BOARD(c1 = CoorFromBitBoardRank1ToRank8(&bb))) { ASSERT(FILE(c1) == FILE(c)); if (c1 > c) return; } break; } } // // Count FLIP(uColor)'s sentries and determine the location of the // critical square. Note if there are no sentries then this // pawn (on square c) is a passer already, not a candidate. // bb = pHash->bbPawnLocations[FLIP(uColor)] & BBADJACENT_FILES[FILE(c)]; bb &= BBPRECEEDING_RANKS[(c & 0x70) >> 4][uColor]; if (!bb) { ASSERT(CountBits(bb) == 0); // // There are no sentries so this pawn is a passer. // pHash->bbPasserLocations[uColor] |= COOR_TO_BB(c); ASSERT(CountBits(pHash->bbPasserLocations[uColor]) > 0); ASSERT(CountBits(pHash->bbPasserLocations[uColor]) <= 8); EVAL_TERM(uColor, PAWN, c, 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 = CountBits(bb); // // There's one or more sentry pawns so we'll look for helpers to // decide if this pawn is a candidate passer. // if (uColor == WHITE) { pSentry = BLACK_PAWN; cSquare = CoorFromBitBoardRank1ToRank8(&bb); cSquare += 0x10; } else { pSentry = WHITE_PAWN; cSquare = CoorFromBitBoardRank8ToRank1(&bb); cSquare -= 0x10; } cSquare &= 0xF0; cSquare |= FILE(c); ASSERT(IS_ON_BOARD(cSquare)); ASSERT(FILE(cSquare) == FILE(c)); #ifdef DEBUG uVerifySentries = 0; cVerifySquare = 0; d1 = 16 * g_iAhead[uColor]; c1 = c + d1; do { if (IS_ON_BOARD(c1 - 1)) { if (pos->rgSquare[c1 - 1].pPiece == pSentry) { uVerifySentries++; if (0 == cVerifySquare) { cVerifySquare = c1 - d1; ASSERT(cVerifySquare == cSquare); } } } if (IS_ON_BOARD(c1 + 1)) { if (pos->rgSquare[c1 + 1].pPiece == pSentry) { uVerifySentries++; if (0 == cVerifySquare) { cVerifySquare = c1 - d1; ASSERT(cVerifySquare == cSquare); } } } c1 = c1 + d1; } while(!RANK8(c1) && !RANK1(c1)); ASSERT(uVerifySentries == uSentries); #endif // // Note: we don't do this with bitboards because we want to not // consider a pawn to be a potential passer if it can't safely // advance to get into helper position. // // IDEA: scale the candidate passer bonus based on rank AND on // the distance the helper(s) have to go to get into position. // uHelpers = 0; d1 = 16 * g_iAhead[uColor]; ASSERT(-d1 == 16 * g_iBehind[uColor]); c1 = cSquare + 1 - d1; if ((IS_ON_BOARD(c1)) && (pHash->uCountPerFile[uColor][FILE(c1) + 1])) { ASSERT(pHash->bbPawnLocations[uColor] & BBFILE[FILE(c1)]); if (!(pos->rgSquare[c1 + 8].bvAttacks[FLIP(uColor)].uWholeThing) || (pos->rgSquare[c1 + 8].bvAttacks[uColor].uWholeThing)) { // // The square c1 the place a helper pawn must get to in // order to aide the candidate past a sentry. // if (pos->rgSquare[c1].pPiece == pHelper) { uHelpers = 1; goto do_left; } // // There is no helper pawn in the support position yet. // See if one can get there. // c1 = c1 - d1; while (IS_ON_BOARD(c1) && ((!(pos->rgSquare[c1+8].bvAttacks[FLIP(uColor)].uWholeThing)) || (pos->rgSquare[c1+8].bvAttacks[uColor].uWholeThing))) { if (pos->rgSquare[c1].pPiece == pHelper) { uHelpers = 1; break; } else if (pos->rgSquare[c1].pPiece == pSentry) { break; } c1 = c1 - d1; } } } do_left: c1 = cSquare - 1 - d1; if ((IS_ON_BOARD(c1)) && (pHash->uCountPerFile[uColor][FILE(c1) + 1])) { ASSERT(pHash->bbPawnLocations[uColor] & BBFILE[FILE(c1)]); if (!(pos->rgSquare[c1 + 8].bvAttacks[FLIP(uColor)].uWholeThing) || (pos->rgSquare[c1 + 8].bvAttacks[uColor].uWholeThing)) { // // The square c1 is the place a helper pawn must get to in // order to aide the candidate. // if (pos->rgSquare[c1].pPiece == pHelper) { uHelpers++; goto done_helpers; } // // There is no pawn in the left support position yet. See // if one can get there. // c1 -= d1; while (IS_ON_BOARD(c1) && ((!(pos->rgSquare[c1+8].bvAttacks[FLIP(uColor)].uWholeThing)) || (pos->rgSquare[c1 + 8].bvAttacks[uColor].uWholeThing))) { if (pos->rgSquare[c1].pPiece == pHelper) { uHelpers++; break; } else if (pos->rgSquare[c1].pPiece == pSentry) { break; } c1 -= d1; } } } done_helpers: if ((uHelpers >= uSentries) || ((pHash->uCountPerFile[uColor][uPawnFile + 1] + pHash->uCountPerFile[uColor][uPawnFile - 1]) && (((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); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], CANDIDATE_PASSER_BY_RANK[uColor][RANK(c)], "candidate passer"); // // If the other side has no pieces then give this candidate an // extra bonus. // if (pos->uNonPawnCount[FLIP(uColor)][0] == 1) { EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], CANDIDATE_PASSER_BY_RANK[uColor][RANK(c)], "candidate passer in endgame"); } } } static void _EvaluateConnectedSupportedOutsidePassers(IN POSITION *pos, IN OUT PAWN_HASH_ENTRY *pHash) /** Routine description: Parameters: POSITION *pos, PAWN_HASH_ENTRY *pHash Return value: void **/ { ULONG uColor; ULONG u, v; COOR c; COOR cLeftmostPasser, cRightmostPasser; PIECE pFriend; COOR cSupport; BITBOARD bb; static const INT iDelta[5] = { -1, +1, +17, +15, 0 }; FOREACH_COLOR(uColor) { ASSERT(IS_VALID_COLOR(uColor)); if (pHash->bbPasserLocations[uColor]) { ASSERT(pos->uPawnCount[uColor] > 0); pFriend = BLACK_PAWN | uColor; cLeftmostPasser = cRightmostPasser = ILLEGAL_COOR; for (u = A; u <= H; u++) { bb = pHash->bbPasserLocations[uColor] & BBFILE[u]; if (bb != 0) { ASSERT(pHash->uCountPerFile[uColor][u+1] != 0); while(IS_ON_BOARD(c = CoorFromBitBoardRank8ToRank1(&bb))) { // // Keep track of leftmost/rightmost passer for // outside passer code later on. // ASSERT(FILE(c) == u); ASSERT(RANK(c) > 1); ASSERT(RANK(c) < 8); if (cLeftmostPasser == ILLEGAL_COOR) { cLeftmostPasser = c; } cRightmostPasser = c; v = 0; while(iDelta[v] != 0) { cSupport = c + iDelta[v] * g_iBehind[uColor]; if (IS_ON_BOARD(cSupport)) { if (pHash->bbPasserLocations[uColor] & COOR_TO_BB(cSupport)) { ASSERT(CONNECTED_PASSERS_BY_RANK[uColor] [RANK(c)] > 0); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], CONNECTED_PASSERS_BY_RANK[uColor] [RANK(c)], "connected passers"); // // TODO: connected passers vs a R is // strong. // } else if (pos->rgSquare[cSupport].pPiece == pFriend) { ASSERT(SUPPORTED_PASSER_BY_RANK[uColor] [RANK(c)] > 0); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], SUPPORTED_PASSER_BY_RANK[uColor] [RANK(c)], "supported passer"); } } v++; } } } } #ifdef DEBUG ASSERT(IS_ON_BOARD(cLeftmostPasser)); ASSERT(IS_ON_BOARD(cRightmostPasser)); ASSERT(RANK(cLeftmostPasser) > 1); ASSERT(RANK(cRightmostPasser) > 1); ASSERT(RANK(cLeftmostPasser) < 8); ASSERT(RANK(cRightmostPasser) < 8); if (CountBits(pHash->bbPasserLocations[uColor]) == 1) { ASSERT(cLeftmostPasser == cRightmostPasser); } #endif for (u = A; u <= D; u++) { if (pHash->uCountPerFile[FLIP(uColor)][u + 1] != 0) { ASSERT(pHash->bbPawnLocations[FLIP(uColor)] & BBFILE[u]); if (!(pHash->bbPasserLocations[FLIP(uColor)] & BBFILE[u])) { break; } } } if (u > FILE(cLeftmostPasser)) { EVAL_TERM(uColor, PAWN, ILLEGAL_COOR, pHash->iScore[uColor], OUTSIDE_PASSER_BY_DISTANCE[u-FILE(cLeftmostPasser)], "left outside passer"); } for (u = H; u >= E; u--) { if (pHash->uCountPerFile[FLIP(uColor)][u + 1] != 0) { ASSERT(pHash->bbPawnLocations[FLIP(uColor)] & BBFILE[u]); if (!(pHash->bbPasserLocations[FLIP(uColor)] & BBFILE[u])) { break; } } } if (u < FILE(cRightmostPasser)) { EVAL_TERM(uColor, PAWN, ILLEGAL_COOR, pHash->iScore[uColor], OUTSIDE_PASSER_BY_DISTANCE[FILE(cRightmostPasser)-u], "right outside passer"); } } } } #define PAWN_ATTACK_BLACK_DELTA (+15) #define PAWN_ATTACK_WHITE_DELTA (-17) static void _PopulatePawnAttackBits(IN OUT POSITION *pos) /** Routine description: Populate the attack table with pawn bits. Parameters: POSITION *pos : the board Return value: void **/ { ULONG u; COOR c; COOR cAttack; // // IDEA: combine clearing and initial population somehow? // // r - - - - - - l : only check L- and R- // r - - - - - - l // R A A A A A A L // R A A A A A A L // R A A A A A A L // R A A A A A A L // r - - - - - - l // r - - - - - - l : only check L+ and R+ _ClearAttackTables(pos); ASSERT(pos->uPawnCount[BLACK] <= 8); for (u = 0; u < pos->uPawnCount[BLACK]; u++) { c = pos->cPawns[BLACK][u]; ASSERT(IS_ON_BOARD(c)); ASSERT(pos->rgSquare[c].pPiece); ASSERT(IS_PAWN(pos->rgSquare[c].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == BLACK); // // IDEA: These IS_ON_BOARD checks can be removed by a lookup table. // cAttack = c + PAWN_ATTACK_BLACK_DELTA; if (IS_ON_BOARD(cAttack)) { ASSERT((cAttack + 8) == (cAttack|8)); ASSERT(!IS_ON_BOARD(cAttack|8)); pos->rgSquare[cAttack|8].bvAttacks[BLACK].small.uPawn = 1; } cAttack += 2; if (IS_ON_BOARD(cAttack)) { ASSERT((cAttack + 8) == (cAttack|8)); cAttack |= 8; ASSERT(!IS_ON_BOARD(cAttack)); pos->rgSquare[cAttack].bvAttacks[BLACK].small.uPawn = 1; } } ASSERT(pos->uPawnCount[WHITE] <= 8); for (u = 0; u < pos->uPawnCount[WHITE]; u++) { c = pos->cPawns[WHITE][u]; ASSERT(IS_ON_BOARD(c)); ASSERT(pos->rgSquare[c].pPiece); ASSERT(IS_PAWN(pos->rgSquare[c].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == WHITE); cAttack = c + PAWN_ATTACK_WHITE_DELTA; if (IS_ON_BOARD(cAttack)) { ASSERT((cAttack + 8) == (cAttack|8)); ASSERT(!IS_ON_BOARD(cAttack|8)); pos->rgSquare[cAttack|8].bvAttacks[WHITE].small.uPawn = 1; } cAttack += 2; if (IS_ON_BOARD(cAttack)) { ASSERT((cAttack + 8) == (cAttack|8)); cAttack |= 8; ASSERT(!IS_ON_BOARD(cAttack)); pos->rgSquare[cAttack].bvAttacks[WHITE].small.uPawn = 1; } } } static PAWN_HASH_ENTRY * _EvalPawns(IN OUT SEARCHER_THREAD_CONTEXT *ctx, OUT FLAG *pfDeferred) /** Routine description: Evaluate pawn structures; return a ptr to a pawn hash entry. Parameters: SEARCHER_THREAD_CONTEXT *ctx : the searcher thread context Return value: PAWN_HASH_ENTRY * **/ { static ULONG uUnmovedRank[2] = { 0x10, 0x60 }; POSITION *pos = &ctx->sPosition; PAWN_HASH_ENTRY *pHash; COOR c, cSquare; PIECE p; ULONG uIsolated[2]; ULONG uDoubled[2]; SCORE iDuos[2]; ULONG u, uPawnFile; ULONG uColor; ULONG uUnsupportable; BITBOARD bb; int d1; #ifdef DEBUG SCORE t; #endif // // Now, look up the hash entry for this position. // INC(ctx->sCounters.pawnhash.u64Probes); pHash = PawnHashLookup(ctx); ASSERT(NULL != pHash); if (pHash->u64Key == pos->u64PawnSig) { *pfDeferred = TRUE; INC(ctx->sCounters.pawnhash.u64Hits); return(pHash); } // // We need the attack table to evaluate the pawns... and we missed // the hash table so we have to populate it now. Can't defer this // one. :( // *pfDeferred = FALSE; _InitializePawnHashEntry(pHash, pos); _PopulatePawnAttackBits(pos); uIsolated[BLACK] = uIsolated[WHITE] = 0; uDoubled[BLACK] = uDoubled[WHITE] = 0; iDuos[BLACK] = iDuos[WHITE] = 0; // // First pass // FOREACH_COLOR(uColor) { ASSERT(IS_VALID_COLOR(uColor)); ASSERT(pos->uPawnCount[uColor] <= 8); d1 = 16 * g_iAhead[uColor]; for (u = 0; u < pos->uPawnCount[uColor]; u++) { c = pos->cPawns[uColor][u]; ASSERT(IS_ON_BOARD(c)); ASSERT(pos->rgSquare[c].pPiece); ASSERT(IS_PAWN(pos->rgSquare[c].pPiece)); uPawnFile = FILE(c) + 1; ASSERT((1 <= uPawnFile) && (uPawnFile <= 8)); // // Update files counter // ASSERT(pHash->uCountPerFile[uColor][uPawnFile] < 6); pHash->uCountPerFile[uColor][uPawnFile]++; // // Update bitboard bit // ASSERT(CountBits(pHash->bbPawnLocations[uColor] & BBFILE[uPawnFile-1]) < 6); ASSERT((pHash->bbPawnLocations[uColor] & COOR_TO_BB(c)) == 0); pHash->bbPawnLocations[uColor] |= COOR_TO_BB(c); ASSERT(CountBits(pHash->bbPawnLocations[uColor] & BBFILE[uPawnFile-1]) == pHash->uCountPerFile[uColor][uPawnFile]); // // Count unmoved pawns // pHash->uNumUnmovedPawns[uColor] += ((c & 0xF0) == uUnmovedRank[uColor]); ASSERT(pHash->uNumUnmovedPawns[uColor] <= 8); // // Detect rammed and other stationary pawns. // cSquare = c + d1; ASSERT(IS_ON_BOARD(cSquare)); p = pos->rgSquare[cSquare].pPiece; if (IS_PAWN(p)) { pHash->bbStationaryPawns[uColor] |= COOR_TO_BB(c); ASSERT(CountBits(pHash->bbStationaryPawns[uColor]) <= 8); pHash->uNumRammedPawns += OPPOSITE_COLORS(p, uColor); ASSERT(pHash->uNumRammedPawns <= 16); } // // Give central pawns a small bonus; penalize rook pawns: // EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], PAWN_CENTRALITY_BONUS[c], "centrality"); } } // // We counted rammed pawns twice (once for the black pawn and once // for the white one). Fix this now. // ASSERT(!(pHash->uNumRammedPawns & 1)); pHash->uNumRammedPawns /= 2; // // Second pass // FOREACH_COLOR(uColor) { ASSERT(IS_VALID_COLOR(uColor)); ASSERT(pos->uPawnCount[uColor] <= 8); ASSERT(CountBits(pHash->bbPawnLocations[uColor]) <= 8); d1 = 16 * g_iAhead[uColor]; for (u = 0; u < pos->uPawnCount[uColor]; u++) { c = pos->cPawns[uColor][u]; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_PAWN(pos->rgSquare[c].pPiece)); ASSERT((1 < RANK(c)) && (RANK(c) < 8)); // // Find weak pawns... pawns that are isolated are weak as // are pawns that have advanced beyond their support. // Both of these can tie up one or more pieces in order to // defend them. // uUnsupportable = 0; uPawnFile = FILE(c) - 1; if (pHash->uCountPerFile[uColor][uPawnFile + 1] > 0) { bb = (pHash->bbPawnLocations[uColor] & BBFILE[uPawnFile] & BBADJACENT_RANKS[RANK(c)]); if (!bb) { bb = (pHash->bbPawnLocations[FLIP(uColor)] & BBFILE[uPawnFile]); while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); if (uColor == WHITE) { if ((cSquare & 0xF0) >= (c & 0xF0)) { ASSERT(RANK(cSquare) <= RANK(c)); uUnsupportable = 1; break; } } else { ASSERT(uColor == BLACK); if ((cSquare & 0xF0) <= (c & 0xF0)) { ASSERT(RANK(cSquare) >= RANK(c)); uUnsupportable = 1; break; } } } } } else { uUnsupportable = 1; // no friend pawn on that file } if (0 == uUnsupportable) { goto fast_skip; } ASSERT(1 == uUnsupportable); uPawnFile = FILE(c) + 1; if (pHash->uCountPerFile[uColor][uPawnFile + 1] > 0) { bb = (pHash->bbPawnLocations[uColor] & BBFILE[uPawnFile] & BBADJACENT_RANKS[RANK(c)]); if (!bb) { bb = (pHash->bbPawnLocations[FLIP(uColor)] & BBFILE[uPawnFile]); while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); if (uColor == WHITE) { if ((cSquare & 0xF0) >= (c & 0xF0)) { ASSERT(RANK(cSquare) <= RANK(c)); uUnsupportable = 2; break; } } else { ASSERT(uColor == BLACK); if ((cSquare & 0xF0) <= (c & 0xF0)) { ASSERT(RANK(cSquare) >= RANK(c)); uUnsupportable = 2; break; } } } } } else { uUnsupportable = 2; } // // If this pawn is not supportable from either side then // it is either isolated or has been pushed too far ahead // of its support. Either way it's a target -- penalize // it. // if (2 == uUnsupportable) { uPawnFile = FILE(c) + 1; EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], ISOLATED_PAWN_BY_PAWNFILE[uPawnFile], "target/isolated pawn"); // // Isolated + exposed? Extra penalty. // // Tuning can (and has) flipped this sign; not a real invariant. //ASSERT(ISOLATED_EXPOSED_PAWN < 0); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], ((pHash->uCountPerFile[FLIP(uColor)][uPawnFile]==0)* ISOLATED_EXPOSED_PAWN), "exposed target"); // // Exponential penalty term // uIsolated[uColor] += 1; ASSERT(uIsolated[uColor] > 0); ASSERT(uIsolated[uColor] <= 8); // // Isolated + doubled? Extra penalty. // // Tuning can (and has) flipped this sign; not a real invariant. //ASSERT(ISOLATED_DOUBLED_PAWN < 0); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], ((pHash->uCountPerFile[uColor][uPawnFile] > 1) * ISOLATED_DOUBLED_PAWN), "doubled + target"); } fast_skip: uPawnFile = FILE(c) + 1; ASSERT((1 <= uPawnFile) && (uPawnFile <= 8)); ASSERT(CountBits(pHash->bbPawnLocations[uColor] & BBFILE[FILE(c)]) == pHash->uCountPerFile[uColor][uPawnFile]); // // Keep count of doubled pawns // uDoubled[uColor] += (pHash->uCountPerFile[uColor][uPawnFile] > 1); ASSERT((0 <= uDoubled[uColor]) && (uDoubled[uColor] <= 8)); // // We can detect pawn duos (triads, etc...) and backward pawns // by considering the control of the pawn's stopsquare. // cSquare = c + d1; ASSERT(IS_ON_BOARD(cSquare)); ASSERT((cSquare + 8) == (cSquare | 8)); if (pos->rgSquare[cSquare|8].bvAttacks[uColor].uWholeThing) { // // Count pawn duos. See "Pawn Power in Chess" pp 10-16 // #ifdef DEBUG t = ((uColor * RANK(cSquare)) + (FLIP(uColor) * (9 - RANK(cSquare)))); if (uColor == WHITE) { ASSERT(t == RANK(cSquare)); } else { ASSERT(uColor == BLACK); ASSERT(t == (9 - RANK(cSquare))); } #endif iDuos[uColor] += ((uColor * RANK(cSquare)) + (FLIP(uColor) * (9 - RANK(cSquare)))); ASSERT(iDuos[uColor] <= (7 * 8)); } else if (pos->rgSquare[cSquare|8].bvAttacks[FLIP(uColor)].uWholeThing) { // // Detect backwards pawns. See "Pawn Power in Chess" pp 25-27 // if (!IS_PAWN(pos->rgSquare[cSquare].pPiece)) { p = (BLACK_PAWN | uColor); if (((WHITE == uColor) && (RANK(c) < 4)) || ((BLACK == uColor) && (RANK(c) > 5))) { if ((IS_ON_BOARD(cSquare - 1) && pos->rgSquare[cSquare - 1].pPiece == p) || (IS_ON_BOARD(cSquare + 1) && pos->rgSquare[cSquare + 1].pPiece == p)) { // // The pawn at c is backward. Determine // whether it is shielded or exposed. // pHash->bbStationaryPawns[uColor] |= COOR_TO_BB(c); ASSERT(CountBits(pHash->bbStationaryPawns[uColor]) <= 8); if (pHash->uCountPerFile[FLIP(uColor)][uPawnFile]) { // Tuning can (and has) flipped this sign; not a real invariant. //ASSERT(BACKWARD_SHIELDED_BY_LOCATION[c] < 0); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], BACKWARD_SHIELDED_BY_LOCATION[c], "shielded backward pawn"); } else { //ASSERT(BACKWARD_EXPOSED_BY_LOCATION[c] < 0); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], BACKWARD_EXPOSED_BY_LOCATION[c], "exposed backward pawn"); } } } } // // TODO: Think about backward doubled pawns; read Kauffman's // paper "All About Doubled Pawns". // } // // Handle passers / candidate passers // _EvaluateCandidatePasser(pos, pHash, c); } } // // Reward pawn duos, see "Pawn Power in Chess" pp. 10-16 // ASSERT(iDuos[WHITE] >= 0); ASSERT(iDuos[WHITE] <= 56); ASSERT(iDuos[BLACK] >= 0); ASSERT(iDuos[BLACK] <= 56); EVAL_TERM(WHITE, PAWN, ILLEGAL_COOR, pHash->iScore[WHITE], iDuos[WHITE] / 2, "pawn duos"); EVAL_TERM(BLACK, PAWN, ILLEGAL_COOR, pHash->iScore[BLACK], iDuos[BLACK] / 2, "pawn duos"); // // The penalty for doubled pawns is scaled based on two primary // factors: the presence of major pieces for the side with doubled // pawns and the number of doubled pawns on the board. The // presence of major pieces makes the doubled pawns less severe // while the presence of many doubled pawns makes the penalty for // each more severe. // ASSERT((uDoubled[WHITE] >= 0) && (uDoubled[WHITE] <= 8)); ASSERT((uDoubled[BLACK] >= 0) && (uDoubled[BLACK] <= 8)); u = pos->uNonPawnCount[BLACK][ROOK] + pos->uNonPawnCount[BLACK][QUEEN] * 2; u = MINU(u, 3); EVAL_TERM(BLACK, PAWN, ILLEGAL_COOR, pHash->iScore[BLACK], DOUBLED_PAWN_PENALTY_BY_COUNT[u][uDoubled[BLACK]], "exponential doubled"); u = pos->uNonPawnCount[WHITE][ROOK] + pos->uNonPawnCount[WHITE][QUEEN] * 2; u = MINU(u, 3); EVAL_TERM(WHITE, PAWN, ILLEGAL_COOR, pHash->iScore[WHITE], DOUBLED_PAWN_PENALTY_BY_COUNT[u][uDoubled[WHITE]], "exponential doubled"); ASSERT(uIsolated[WHITE] >= 0); ASSERT(uIsolated[WHITE] <= 8); ASSERT(uIsolated[BLACK] >= 0); ASSERT(uIsolated[BLACK] <= 8); EVAL_TERM(BLACK, PAWN, ILLEGAL_COOR, pHash->iScore[BLACK], ISOLATED_PAWN_PENALTY_BY_COUNT[uIsolated[BLACK]], "exponential isolated"); EVAL_TERM(WHITE, PAWN, ILLEGAL_COOR, pHash->iScore[WHITE], ISOLATED_PAWN_PENALTY_BY_COUNT[uIsolated[WHITE]], "exponential isolated"); // // Look for connected, supported and outside passed pawns to give // extra bonuses. // _EvaluateConnectedSupportedOutsidePassers(pos, pHash); // // TODO: recognize quartgrips and stonewalls // return(pHash); } ULONG CountKingSafetyDefects(IN OUT POSITION *pos, IN ULONG uSide) /** Routine description: Determine how many defects uSide's king position has _quickly_. TODO: add more knowledge as cheaply as possible... Parameters: POSITION *pos, ULONG uSide Return value: ULONG **/ { ULONG uCounter = 0; ULONG xSide = FLIP(uSide); COOR cKing; COOR c; int i; PIECE p; ULONG u; // // Don't count king safety defects if the real eval code in // _EvalKing would not... // if (pos->uNonPawnMaterial[xSide] < DO_KING_SAFETY_THRESHOLD) { return 0; } cKing = pos->cNonPawns[uSide][0]; uCounter = KING_INITIAL_COUNTER_BY_LOCATION[uSide][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); // // Make sure cKing - 1, cKing and cKing + 1 are on the board // cKing += (!IS_ON_BOARD(cKing - 1)); cKing -= (!IS_ON_BOARD(cKing + 1)); ASSERT(IS_ON_BOARD(cKing)); ASSERT(IS_ON_BOARD(cKing + 1)); ASSERT(IS_ON_BOARD(cKing - 1)); // // Consider all enemy pieces except the king (not including pawns) // for (u = 1; u < pos->uNonPawnCount[xSide][0]; u++) { c = pos->cNonPawns[xSide][u]; ASSERT(IS_ON_BOARD(c)); i = (int)c - (int)(cKing + 1); ASSERT((i >= -128) && (i <= 125)); p = pos->rgSquare[c].pPiece; ASSERT(pos->rgSquare[c].uIndex == u); ASSERT(!IS_KING(p)); ASSERT(GET_COLOR(p) == xSide); p = 1 << PIECE_TYPE(p); uCounter += ((i == 0) | (i == -2) | ((CHECK_VECTOR_WITH_INDEX(i, xSide) & p) != 0) | ((CHECK_VECTOR_WITH_INDEX(i + 1, xSide) & p) != 0) | ((CHECK_VECTOR_WITH_INDEX(i + 2, xSide) & p) != 0)); } ASSERT(uCounter < 15); // Save the number of enemy pieces pointing at this king for later use. pos->uPiecesPointingAtKing[uSide] = (uCounter - (KING_INITIAL_COUNTER_BY_LOCATION[uSide][cKing] >> 1)); return uCounter; } static void EstimatePositionalScore(IN POSITION *pos, IN UNUSED PAWN_HASH_ENTRY *pHash, IN OUT SCORE *piAlphaMargin, IN OUT SCORE *piBetaMargin) /** Routine description: Before doing an early lazy eval, look at the position and widen the alpha/beta margins to account for the positional terms that have not been computed yet at this point in Eval() -- king safety plus a flat residual covering mobility, passers, and everything else that genuinely requires attack-generation to know exactly. Both terms below are calibrated from measured data (CALIBRATE_ POSITIONAL instrumentation, ~1.6M full-eval samples over an ECM slice), not guessed: for each value, p90 of the *actual* score swing between this point in Eval() and full-eval completion, so the resulting margin is wrong (too narrow) on at most ~10% of calls, in either bucket. See conversation history for the percentile tables -- if re-tuning, regenerate them, don't hand- edit these numbers. Note: king safety and "everything else" turned out to be close to independent of piece count and of each other, so summing their two p90s (rather than deriving one joint p90) is a deliberately conservative (wider than strictly necessary) combination. O(1) -- no attack bitboard generation -- so it's safe to call whenever the cheap material-only lazy check (the caller's first-pass margin) wasn't enough to resolve the cutoff on its own. Parameters: POSITION *pos PAWN_HASH_ENTRY *pHash : unused now (kept for call-site symmetry); pHash->iScore is already folded into pos->iScore by this point, and the passer-specific estimate this used to compute turned out to be negligible next to the residual term below. SCORE *piAlphaMargin, *piBetaMargin : widened in place, identically (no measured basis for an asymmetric alpha/beta split) Return value: void **/ { // p90 of |true king-safety swing|, indexed by combined defect // count (CountKingSafetyDefects(stm) + CountKingSafetyDefects(xsm)), // clamped above index 10 (sparse data beyond that). static const SCORE iKingSwingP90[11] = { 47, 62, 87, 119, 169, 157, 181, 282, 342, 342, 385 }; // p90 of |mobility + passers + everything else combined|, measured // directly (no useful correlation found with piece count). static const SCORE iResidualP90 = 154; ULONG uDefects = (CountKingSafetyDefects(pos, WHITE) + CountKingSafetyDefects(pos, BLACK)); SCORE iKingTerm = iKingSwingP90[MINU(10, uDefects)]; *piAlphaMargin += iKingTerm + iResidualP90; *piBetaMargin += iKingTerm + iResidualP90; } static void _InvalidEvaluator(UNUSED POSITION *pos, UNUSED COOR c, UNUSED PAWN_HASH_ENTRY *pHash) /** Routine description: This code should never be called Parameters: POSITION *pos, COOR c, PAWN_HASH_ENTRY *pHash, Return value: void **/ { UtilPanic(SHOULD_NOT_GET_HERE, NULL, NULL, NULL, NULL, __FILE__, __LINE__); } // // ====================================================================== // static FLAG FASTCALL _InvalidMobilityHelper(UNUSED POSITION *pos, UNUSED COOR c, UNUSED ULONG *puMobility, UNUSED ULONG *puBit) /** Routine description: This code should never be called Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { UtilPanic(SHOULD_NOT_GET_HERE, NULL, NULL, NULL, NULL, __FILE__, __LINE__); return(TRUE); } // ---------------------------------------------------------------------- static FLAG FASTCALL _BMinorToEnemyLess(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, UNUSED ULONG *puBit) /** Routine description: Consider a black minor (knight | bishop) taking an enemy pawn Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL : always TRUE, stop scanning in this direction **/ { #ifdef DEBUG PIECE pMinor = pos->rgSquare[pos->cPiece].pPiece; PIECE p = pos->rgSquare[c].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(!IS_EMPTY(p)); ASSERT(PIECE_VALUE(p) < VALUE_BISHOP); ASSERT(!IS_EMPTY(pMinor)); ASSERT(IS_PAWN(p)); ASSERT(IS_BISHOP(pMinor) || IS_KNIGHT(pMinor)); ASSERT(OPPOSITE_COLORS(p, pMinor)); ASSERT(GET_COLOR(pMinor) == BLACK); #endif *puMobility += (!UNSAFE_FOR_MINOR(pos->rgSquare[c|8].bvAttacks[WHITE])); ASSERT(*puMobility <= 8); return(TRUE); // stop scanning this dir } static FLAG FASTCALL _WMinorToEnemyLess(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, UNUSED ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE pMinor = pos->rgSquare[pos->cPiece].pPiece; PIECE p = pos->rgSquare[c].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(!IS_EMPTY(p)); ASSERT(IS_PAWN(p)); ASSERT(PIECE_VALUE(p) < VALUE_BISHOP); ASSERT(!IS_EMPTY(pMinor)); ASSERT(IS_BISHOP(pMinor) || IS_KNIGHT(pMinor)); ASSERT(OPPOSITE_COLORS(p, pMinor)); ASSERT(GET_COLOR(pMinor) == WHITE); #endif *puMobility += (!UNSAFE_FOR_MINOR(pos->rgSquare[c|8].bvAttacks[BLACK])); ASSERT(*puMobility <= 8); return(TRUE); // stop scanning this dir } static FLAG FASTCALL _WRookToEnemyLess(POSITION *pos, COOR c, ULONG *puMobility, UNUSED ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pRook = pos->rgSquare[pos->cPiece].pPiece; ASSERT(IS_ROOK(pRook)); ASSERT(!IS_EMPTY(p)); ASSERT(OPPOSITE_COLORS(p, pRook)); ASSERT(PIECE_VALUE(p) < VALUE_ROOK); ASSERT(WHITE == GET_COLOR(pRook)); #endif *puMobility += (!UNSAFE_FOR_ROOK(pos->rgSquare[c|8].bvAttacks[BLACK])); ASSERT(*puMobility <= 7); return(TRUE); // stop scanning this dir } static FLAG FASTCALL _BRookToEnemyLess(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, UNUSED ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pRook = pos->rgSquare[pos->cPiece].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(IS_ROOK(pRook)); ASSERT(OPPOSITE_COLORS(p, pRook)); ASSERT(PIECE_VALUE(p) < VALUE_ROOK); ASSERT(BLACK == GET_COLOR(pRook)); #endif *puMobility += (!UNSAFE_FOR_ROOK(pos->rgSquare[c|8].bvAttacks[WHITE])); ASSERT(*puMobility <= 7); return(TRUE); // stop scanning this dir } static FLAG FASTCALL _BQueenToEnemyLess(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, UNUSED ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pQueen = pos->rgSquare[pos->cPiece].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(IS_QUEEN(pQueen)); ASSERT(PIECE_VALUE(p) < VALUE_QUEEN); ASSERT(GET_COLOR(pQueen) == BLACK); ASSERT(OPPOSITE_COLORS(pQueen, p)); #endif *puMobility += (!UNSAFE_FOR_QUEEN(pos->rgSquare[c|8].bvAttacks[WHITE])); ASSERT(*puMobility <= 27); return(TRUE); // stop scanning this dir } static FLAG FASTCALL _WQueenToEnemyLess(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, UNUSED ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pQueen = pos->rgSquare[pos->cPiece].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(IS_QUEEN(pQueen)); ASSERT(PIECE_VALUE(p) < VALUE_QUEEN); ASSERT(GET_COLOR(pQueen) == WHITE); ASSERT(OPPOSITE_COLORS(pQueen, p)); #endif *puMobility += (!UNSAFE_FOR_QUEEN(pos->rgSquare[c|8].bvAttacks[BLACK])); ASSERT(*puMobility <= 27); return(TRUE); // stop scanning this dir } // ---------------------------------------------------------------------- static FLAG FASTCALL _EMinorToEnemySame(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, UNUSED ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE pMinor = pos->rgSquare[pos->cPiece].pPiece; PIECE p = pos->rgSquare[c].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(!IS_EMPTY(p)); ASSERT(!IS_EMPTY(pMinor)); ASSERT(OPPOSITE_COLORS(p, pMinor)); if (IS_BISHOP(pMinor)) { ASSERT(PIECE_VALUE(p) == VALUE_BISHOP); } else { ASSERT(IS_KNIGHT(pMinor)); ASSERT(PIECE_VALUE(p) >= VALUE_KNIGHT); } #endif *puMobility += 1; ASSERT(*puMobility <= 8); return(TRUE); // stop scanning this dir } static FLAG FASTCALL _ERookToEnemySame(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, UNUSED ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pRook = pos->rgSquare[pos->cPiece].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(IS_ROOK(pRook)); ASSERT(!IS_EMPTY(p)); ASSERT(IS_ROOK(p)); ASSERT(OPPOSITE_COLORS(p, pRook)); #endif *puMobility += 1; ASSERT(*puMobility <= 7); return(TRUE); // stop scanning this dir } static FLAG FASTCALL _EQueenToEnemyGreaterEqual(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, UNUSED ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pQueen = pos->rgSquare[pos->cPiece].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(IS_QUEEN(pQueen)); ASSERT(!IS_EMPTY(p)); ASSERT(PIECE_VALUE(p) >= VALUE_QUEEN); ASSERT(OPPOSITE_COLORS(pQueen, p)); #endif *puMobility += 1; ASSERT(*puMobility <= 27); return(TRUE); // stop scanning this dir } // ---------------------------------------------------------------------- static FLAG FASTCALL _EMinorToEnemyGreater(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE pMinor = pos->rgSquare[pos->cPiece].pPiece; PIECE p = pos->rgSquare[c].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(!IS_EMPTY(p)); ASSERT(PIECE_VALUE(p) > VALUE_BISHOP); ASSERT(IS_BISHOP(pMinor) || IS_KNIGHT(pMinor)); ASSERT(OPPOSITE_COLORS(p, pMinor)); #endif *puMobility += 1; ASSERT(*puMobility <= 8); *puBit = MINOR_XRAY_BIT; return(FALSE); // keep scanning this dir } static FLAG FASTCALL _ERookToEnemyGreater(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pRook = pos->rgSquare[pos->cPiece].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(IS_ROOK(pRook)); ASSERT(!IS_EMPTY(p)); ASSERT(OPPOSITE_COLORS(p, pRook)); ASSERT(PIECE_VALUE(p) > VALUE_ROOK); #endif *puMobility += 1; ASSERT(*puMobility <= 7); *puBit = ROOK_XRAY_BIT; return(FALSE); // keep scanning this dir } // ---------------------------------------------------------------------- static FLAG FASTCALL _AnythingToFriendNoXray(IN POSITION *pos, IN COOR c, UNUSED ULONG *puMobility, UNUSED ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG COOR cMover = pos->cPiece; PIECE pMover = pos->rgSquare[cMover].pPiece; PIECE pFriend = pos->rgSquare[c].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(cMover)); ASSERT(!IS_EMPTY(pFriend)); ASSERT(!IS_EMPTY(pMover)); ASSERT(GET_COLOR(pMover) == GET_COLOR(pFriend)); if (IS_BISHOP(pMover)) { ASSERT(!IS_BISHOP(pFriend)); ASSERT(!IS_QUEEN(pFriend)); } else if (IS_ROOK(pMover)) { ASSERT(!IS_QUEEN(pFriend)); ASSERT(!IS_ROOK(pFriend)); } else if (IS_QUEEN(pMover)) { ASSERT(!IS_BISHOP(pFriend)); ASSERT(!IS_ROOK(pFriend)); ASSERT(!IS_QUEEN(pFriend)); } #endif return(TRUE); // stop scanning this dir } // ---------------------------------------------------------------------- static FLAG FASTCALL _EMinorToFriendXray(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE pMinor = pos->rgSquare[pos->cPiece].pPiece; PIECE p = pos->rgSquare[c].pPiece; ASSERT(!IS_EMPTY(p)); ASSERT(!IS_EMPTY(pMinor)); ASSERT(IS_BISHOP(pMinor)); ASSERT(GET_COLOR(p) == GET_COLOR(pMinor)); #endif *puBit = MINOR_XRAY_BIT; return(FALSE); } static FLAG FASTCALL _BRookToFriendRook(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { COOR cRook = pos->cPiece; FLAG fHoriz = ((c & 0xF0) == (cRook & 0xF0)); #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pRook = pos->rgSquare[cRook].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(cRook)); ASSERT(IS_ROOK(pRook)); ASSERT(!IS_EMPTY(p)); ASSERT(IS_ROOK(p)); ASSERT(GET_COLOR(p) == GET_COLOR(pRook)); ASSERT(GET_COLOR(p) == BLACK); ASSERT((fHoriz && (RANK(c) == RANK(pos->cPiece))) || (!fHoriz && (FILE(c) == FILE(pos->cPiece)))); #endif EVAL_TERM(BLACK, ROOK, c, pos->iScore[BLACK], (ROOK_CONNECTED_HORIZ * fHoriz + ROOK_CONNECTED_VERT * FLIP(fHoriz)), "rook connected"); *puBit = ROOK_XRAY_BIT; return(FALSE); } static FLAG FASTCALL _WRookToFriendRook(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { COOR cRook = pos->cPiece; FLAG fHoriz = ((c & 0xF0) == (cRook & 0xF0)); #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pRook = pos->rgSquare[cRook].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(cRook)); ASSERT(IS_ROOK(pRook)); ASSERT(!IS_EMPTY(p)); ASSERT(IS_ROOK(p)); ASSERT(GET_COLOR(p) == GET_COLOR(pRook)); ASSERT(GET_COLOR(p) == WHITE); ASSERT((fHoriz && (RANK(c) == RANK(pos->cPiece))) || (!fHoriz && (FILE(c) == FILE(pos->cPiece)))); #endif EVAL_TERM(WHITE, ROOK, c, pos->iScore[WHITE], (ROOK_CONNECTED_HORIZ * fHoriz + ROOK_CONNECTED_VERT * FLIP(fHoriz)), "rook connected"); *puBit = ROOK_XRAY_BIT; return(FALSE); } static FLAG FASTCALL _ERookToFriendQueen(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pRook = pos->rgSquare[pos->cPiece].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(IS_ROOK(pRook)); ASSERT(!IS_EMPTY(p)); ASSERT(IS_QUEEN(p)); ASSERT(GET_COLOR(p) == GET_COLOR(pRook)); #endif *puBit = ROOK_XRAY_BIT; return(FALSE); } static FLAG FASTCALL _EQueenToFriendBishop(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { COOR cQueen = pos->cPiece; #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pQueen = pos->rgSquare[cQueen].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(cQueen)); ASSERT(IS_QUEEN(pQueen)); ASSERT(IS_BISHOP(p)); ASSERT(GET_COLOR(p) == GET_COLOR(pQueen)); #endif *puBit = QUEEN_XRAY_BIT; return((((c & 0xF0) == (cQueen & 0xF0)) || ((c & 0x0F) == (cQueen & 0x0F)))); } static FLAG FASTCALL _EQueenToFriendRook(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { COOR cQueen = pos->cPiece; #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pQueen = pos->rgSquare[cQueen].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(cQueen)); ASSERT(IS_QUEEN(pQueen)); ASSERT(IS_ROOK(p)); ASSERT(GET_COLOR(p) == GET_COLOR(pQueen)); #endif *puBit = QUEEN_XRAY_BIT; return(FLIP((((c & 0xF0) == (cQueen & 0xF0)) || ((c & 0x0F) == (cQueen & 0x0F))))); } static FLAG FASTCALL _EQueenToFriendQueen(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG COOR cQueen = pos->cPiece; PIECE p = pos->rgSquare[c].pPiece; PIECE pQueen = pos->rgSquare[cQueen].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(cQueen)); ASSERT(IS_QUEEN(pQueen)); ASSERT(IS_QUEEN(p)); ASSERT(GET_COLOR(p) == GET_COLOR(pQueen)); #endif *puBit = QUEEN_XRAY_BIT; return(FALSE); } // ---------------------------------------------------------------------- static FLAG FASTCALL _BMinorToEmpty(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE pMinor = pos->rgSquare[pos->cPiece].pPiece; PIECE p = pos->rgSquare[c].pPiece; ASSERT(IS_EMPTY(p)); ASSERT(!IS_EMPTY(pMinor)); ASSERT(IS_BISHOP(pMinor) || IS_KNIGHT(pMinor)); ASSERT(GET_COLOR(pMinor) == BLACK); #endif *puMobility += (!UNSAFE_FOR_MINOR(pos->rgSquare[c|8].bvAttacks[WHITE])); ASSERT(*puMobility <= 8); return(FALSE); } static FLAG FASTCALL _WMinorToEmpty(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE pMinor = pos->rgSquare[pos->cPiece].pPiece; PIECE p = pos->rgSquare[c].pPiece; ASSERT(IS_EMPTY(p)); ASSERT(!IS_EMPTY(pMinor)); ASSERT(IS_BISHOP(pMinor) || IS_KNIGHT(pMinor)); ASSERT(GET_COLOR(pMinor) == WHITE); #endif *puMobility += (!UNSAFE_FOR_MINOR(pos->rgSquare[c|8].bvAttacks[BLACK])); ASSERT(*puMobility <= 8); return(FALSE); } static FLAG FASTCALL _WRookToEmpty(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pRook = pos->rgSquare[pos->cPiece].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(IS_ROOK(pRook)); ASSERT(IS_EMPTY(p)); ASSERT(GET_COLOR(pRook) == WHITE); #endif *puMobility += (!UNSAFE_FOR_ROOK(pos->rgSquare[c|8].bvAttacks[BLACK])); ASSERT(*puMobility <= 7); return(FALSE); } static FLAG FASTCALL _BRookToEmpty(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pRook = pos->rgSquare[pos->cPiece].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(IS_ROOK(pRook)); ASSERT(IS_EMPTY(p)); ASSERT(GET_COLOR(pRook) == BLACK); #endif *puMobility += (!UNSAFE_FOR_ROOK(pos->rgSquare[c|8].bvAttacks[WHITE])); ASSERT(*puMobility <= 7); return(FALSE); } static FLAG FASTCALL _BQueenToEmpty(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pQueen = pos->rgSquare[pos->cPiece].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(IS_QUEEN(pQueen)); ASSERT(IS_EMPTY(p)); ASSERT(GET_COLOR(pQueen) == BLACK); #endif *puMobility += (!UNSAFE_FOR_QUEEN(pos->rgSquare[c|8].bvAttacks[WHITE])); ASSERT(*puMobility <= 27); return(FALSE); } static FLAG FASTCALL _WQueenToEmpty(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE p = pos->rgSquare[c].pPiece; PIECE pQueen = pos->rgSquare[pos->cPiece].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(IS_QUEEN(pQueen)); ASSERT(IS_EMPTY(p)); ASSERT(GET_COLOR(pQueen) == WHITE); #endif *puMobility += (!UNSAFE_FOR_QUEEN(pos->rgSquare[c|8].bvAttacks[BLACK])); ASSERT(*puMobility <= 27); return(FALSE); } static FLAG FASTCALL _EBishopToFriendPawn(IN POSITION *pos, IN COOR c, IN OUT ULONG *puMobility, IN OUT ULONG *puBit) /** Routine description: Parameters: POSITION *pos, COOR c, ULONG *puMobility, ULONG *puBit Return value: static FLAG FASTCALL **/ { #ifdef DEBUG PIECE pPawn = pos->rgSquare[c].pPiece; PIECE pBishop = pos->rgSquare[pos->cPiece].pPiece; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ON_BOARD(pos->cPiece)); ASSERT(IS_BISHOP(pBishop)); ASSERT(IS_PAWN(pPawn)); ASSERT(GET_COLOR(pBishop) == GET_COLOR(pPawn)); #endif *puMobility += ((pos->bb & COOR_TO_BB(c)) != 0); return(TRUE); } // // ====================================================================== // static void _EvalBishop(IN OUT POSITION *pos, IN COOR c, IN PAWN_HASH_ENTRY *pHash) /** Routine description: Parameters: POSITION *pos, COOR c, PAWN_HASH_ENTRY *pHash, Return value: FLAG **/ { static const BITBOARD bbColorSq[2] = { 0x55aa55aa55aa55aaULL, 0xaa55aa55aa55aa55ULL }; static const PMOBILITY_HELPER BMobJumpTable[2][14] = { {// (black) _BMinorToEmpty, // EMPTY_SQUARE (0) _InvalidMobilityHelper, // INVALID_PIECE (1) _EBishopToFriendPawn, // BLACK_PAWN (2) _BMinorToEnemyLess, // WHITE_PAWN (3) _AnythingToFriendNoXray, // BLACK_KNIGHT (4) _EMinorToEnemySame, // WHITE_KNIGHT (5) _EMinorToFriendXray, // BLACK_BISHOP (6) _EMinorToEnemySame, // WHITE_BISHOP (7) _AnythingToFriendNoXray, // BLACK_ROOK (8) _EMinorToEnemyGreater, // WHITE_ROOK (9) _EMinorToFriendXray, // BLACK_QUEEN (10) _EMinorToEnemyGreater, // WHITE_QUEEN (11) _AnythingToFriendNoXray, // BLACK_KING (12) _EMinorToEnemyGreater, // WHITE_KING (13) }, {// (white) _WMinorToEmpty, // EMPTY_SQUARE (0) _InvalidMobilityHelper, // INVALID_PIECE (1) _WMinorToEnemyLess, // BLACK_PAWN (2) _EBishopToFriendPawn, // WHITE_PAWN (3) _EMinorToEnemySame, // BLACK_KNIGHT (4) _AnythingToFriendNoXray, // WHITE_KNIGHT (5) _EMinorToEnemySame, // BLACK_BISHOP (6) _EMinorToFriendXray, // WHITE_BISHOP (7) _EMinorToEnemyGreater, // BLACK_ROOK (8) _AnythingToFriendNoXray, // WHITE_ROOK (9) _EMinorToEnemyGreater, // BLACK_QUEEN (10) _EMinorToFriendXray, // WHITE_QUEEN (11) _EMinorToEnemyGreater, // BLACK_KING (12) _AnythingToFriendNoXray, // WHITE_KING (13) } }; static const COOR cBishopAtHome[2][2] = { { C8, F8 }, // BLACK { C1, F1 } // WHITE }; ULONG uColor; BITBOARD bb; BITBOARD bbMask; BITBOARD bbPc; COOR cSquare; SCORE i; ULONG u; ULONG uTotalMobility; ULONG uMaxMobility; ULONG uCurrentMobility; ULONG uBit; PIECE p; PMOBILITY_HELPER pFun; ASSERT(IS_ON_BOARD(c)); p = pos->rgSquare[c].pPiece; ASSERT(p && IS_BISHOP(p)); uColor = GET_COLOR(p); #ifdef DEBUG pos->cPiece = c; #endif // // Unmoved piece // pos->uMinorsAtHome[uColor] += (c == cBishopAtHome[uColor][0]); pos->uMinorsAtHome[uColor] += (c == cBishopAtHome[uColor][1]); ASSERT(pos->uMinorsAtHome[uColor] <= 4); // // Good and bad bishops // i = 16; bbMask = bbColorSq[IS_SQUARE_WHITE(c)]; bb = pHash->bbStationaryPawns[uColor] & bbMask; ASSERT(CountBits(bb) <= 16); while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { // // Consider stationary (rammed or backward) pawns of the same // color as the bishop. // ASSERT(pos->rgSquare[cSquare].pPiece); ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[cSquare].pPiece) == uColor); i += STATIONARY_PAWN_ON_BISHOP_COLOR[cSquare]; } EVAL_TERM(uColor, BISHOP, c, pos->iScore[uColor], i, "good/bad stationary pawns ++"); i = 0; bbPc = ~(pHash->bbStationaryPawns[WHITE] | pHash->bbStationaryPawns[BLACK]); bbPc &= bbMask; bb = pHash->bbPawnLocations[uColor] & bbPc; while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { ASSERT(pos->rgSquare[cSquare].pPiece); ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[cSquare].pPiece) == uColor); ASSERT(pHash->bbPawnLocations[uColor] & COOR_TO_BB(cSquare)); i += TRANSIENT_PAWN_ON_BISHOP_COLOR[cSquare]; } bb = pHash->bbPawnLocations[FLIP(uColor)] & 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(pHash->bbPawnLocations[FLIP(uColor)] & COOR_TO_BB(cSquare)); i += (pos->rgSquare[cSquare|8].bvAttacks[FLIP(uColor)].small.uPawn != 0) * TRANSIENT_PAWN_ON_BISHOP_COLOR[cSquare] / 2; } EVAL_TERM(uColor, BISHOP, c, pos->iScore[uColor], i, "good/bad transient pawns"); // // Bonus to bishops in very open positions. // ASSERT(pos->uClosedScaler <= 32); EVAL_TERM(uColor, BISHOP, c, pos->iScore[uColor], BISHOP_IN_CLOSED_POSITION[pos->uClosedScaler], "in closed/open position"); // // Bishop mobility (and update attack table bits) // u = uMaxMobility = uTotalMobility = 0; pos->bb = bbPc; ASSERT(g_iBDeltas[u] != 0); do { uCurrentMobility = 0; uBit = MINOR_BIT; cSquare = c + g_iBDeltas[u]; while(IS_ON_BOARD(cSquare)) { // // Always toggle attack table bits. // ASSERT((cSquare|8) == (cSquare + 8)); pos->rgSquare[cSquare|8].bvAttacks[uColor].uWholeThing |= uBit; // // What did we hit? // p = pos->rgSquare[cSquare].pPiece; pFun = BMobJumpTable[uColor][p]; ASSERT(pFun); if (TRUE == (*pFun)(pos, cSquare, &uCurrentMobility, &uBit)) { break; } cSquare += g_iBDeltas[u]; } uTotalMobility += uCurrentMobility; ASSERT((uMaxMobility & 0x80000000) == 0); ASSERT((uCurrentMobility & 0x80000000) == 0); uMaxMobility = MAXU(uMaxMobility, uCurrentMobility); ASSERT(uMaxMobility <= 7); u++; } while(g_iBDeltas[u] != 0); ASSERT(uTotalMobility <= 13); EVAL_TERM(uColor, BISHOP, c, pos->iScore[uColor], BISHOP_MOBILITY_BY_SQUARES[uTotalMobility], "bishop mobility"); EVAL_TERM(uColor, BISHOP, c, pos->iScore[uColor], BISHOP_MAX_MOBILITY_IN_A_ROW_BONUS[uMaxMobility], "consecutive bishop mobility"); // // Look for bishops with no mobility who are under attack. These // pieces are trapped! // if (uTotalMobility == 0) { pos->cTrapped[uColor] = c; } uTotalMobility /= 2; ASSERT((pos->uMinMobility[uColor] & 0x80000000) == 0); ASSERT((uTotalMobility & 0x80000000) == 0); pos->uMinMobility[uColor] = MINU(pos->uMinMobility[uColor], uTotalMobility); // // Look for "active bad bishops". See if square c is safe from // enemy pawns. // bb = pHash->bbPawnLocations[FLIP(uColor)] & (~pHash->bbStationaryPawns[FLIP(uColor)]); if (TRUE == _IsSquareSafeFromEnemyPawn(pos, c, bb)) { // // Give a bonus based on distance from enemy king // if (pos->rgSquare[c|8].bvAttacks[uColor].small.uPawn) { #ifdef DEBUG p = BLACK_PAWN | uColor; ASSERT(((IS_ON_BOARD(c + 17 * g_iBehind[uColor]) && (pos->rgSquare[c + 17 * g_iBehind[uColor]].pPiece==p)) || ((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]); i = BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING[u]; EVAL_TERM(uColor, BISHOP, c, pos->iReducedMaterialDownScaler[uColor], i, "[scaled] unassailable"); } } } static void _EvalKnight(IN OUT POSITION *pos, IN COOR c, IN PAWN_HASH_ENTRY *pHash) /** Routine description: Parameters: POSITION *pos, COOR c, PAWN_HASH_ENTRY *pHash, Return value: FLAG **/ { static const int iPawnStart[2] = { -17, +15 }; static const PMOBILITY_HELPER NMobJumpTable[2][14] = { {// (black) _BMinorToEmpty, // EMPTY_SQUARE (0) _InvalidMobilityHelper, // INVALID_PIECE (1) _AnythingToFriendNoXray, // BLACK_PAWN (2) _BMinorToEnemyLess, // WHITE_PAWN (3) _AnythingToFriendNoXray, // BLACK_KNIGHT (4) _EMinorToEnemySame, // WHITE_KNIGHT (5) _AnythingToFriendNoXray, // BLACK_BISHOP (6) _EMinorToEnemySame, // WHITE_BISHOP (7) _AnythingToFriendNoXray, // BLACK_ROOK (8) _EMinorToEnemySame, // WHITE_ROOK (9) _AnythingToFriendNoXray, // BLACK_QUEEN (10) _EMinorToEnemySame, // WHITE_QUEEN (11) _AnythingToFriendNoXray, // BLACK_KING (12) _EMinorToEnemySame, // WHITE_KING (13) }, {// (white) _WMinorToEmpty, // EMPTY_SQUARE (0) _InvalidMobilityHelper, // INVALID_PIECE (1) _WMinorToEnemyLess, // BLACK_PAWN (2) _AnythingToFriendNoXray, // WHITE_PAWN (3) _EMinorToEnemySame, // BLACK_KNIGHT (4) _AnythingToFriendNoXray, // WHITE_KNIGHT (5) _EMinorToEnemySame, // BLACK_BISHOP (6) _AnythingToFriendNoXray, // WHITE_BISHOP (7) _EMinorToEnemySame, // BLACK_ROOK (8) _AnythingToFriendNoXray, // WHITE_ROOK (9) _EMinorToEnemySame, // BLACK_QUEEN (10) _AnythingToFriendNoXray, // WHITE_QUEEN (11) _EMinorToEnemySame, // BLACK_KING (12) _AnythingToFriendNoXray, // WHITE_KING (13) } }; static const COOR cKnightAtHome[2][2] = { { B8, G8 }, // BLACK { B1, G1 } // WHITE }; PIECE p; COOR cSquare; BITBOARD bb; ULONG uColor; ULONG uPawnsSupporting; ULONG u; ULONG uPawnFile; ULONG uMobilitySquares; SCORE i; ULONG uDist; PMOBILITY_HELPER pFun; p = pos->rgSquare[c].pPiece; ASSERT(p && IS_KNIGHT(p)); uColor = GET_COLOR(p); ASSERT(IS_VALID_COLOR(uColor)); #ifdef DEBUG pos->cPiece = c; #endif // // Unmoved piece // pos->uMinorsAtHome[uColor] += (c == cKnightAtHome[uColor][0]); pos->uMinorsAtHome[uColor] += (c == cKnightAtHome[uColor][1]); ASSERT(pos->uMinorsAtHome[uColor] <= 4); // // TODO: Don't block unmoved E2/D2 pawns // // // Centrality bonus // EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor], KNIGHT_CENTRALITY_BONUS[c], "board centrality"); // // Give a bonus to knights on a closed / busy board // ASSERT(pos->uClosedScaler <= 32); EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor], KNIGHT_IN_CLOSED_POSITION[pos->uClosedScaler], "in closed/open position"); // // See if square c is safe from enemy pawns; 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]); ASSERT((uDist > 0) && (uDist <= 8)); bb = pHash->bbPawnLocations[FLIP(uColor)] & (~pHash->bbStationaryPawns[FLIP(uColor)]); if (TRUE == _IsSquareSafeFromEnemyPawn(pos, c, bb)) { // // Count the number of supporting pawns the knight has // uPawnsSupporting = 0; p = BLACK_PAWN | uColor; cSquare = c + iPawnStart[uColor]; uPawnsSupporting = (IS_ON_BOARD(cSquare) && (pos->rgSquare[cSquare].pPiece == p)); cSquare += 2; uPawnsSupporting += (IS_ON_BOARD(cSquare) && (pos->rgSquare[cSquare].pPiece == p)); ASSERT(uPawnsSupporting <= 2); // // Give a bonus based on distance from enemy king // i = KNIGHT_UNASSAILABLE_BY_DIST_FROM_EKING[uDist] + KNIGHT_WITH_N_PAWNS_SUPPORTING[uPawnsSupporting]; EVAL_TERM(uColor, KNIGHT, c, pos->iReducedMaterialDownScaler[uColor], i, "[scaled] unassailable/support"); // // Now, if there's zero or one supporter see if the enemy has a // bishop the color of the knight it could capture with. // if (uPawnsSupporting != 2) { if (IS_SQUARE_WHITE(c)) { if (pos->uWhiteSqBishopCount[FLIP(uColor)]) { EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor], -(i / 2), "not safe from enemy B"); } } else { if (pos->uNonPawnCount[FLIP(uColor)][BISHOP] - pos->uWhiteSqBishopCount[FLIP(uColor)]) { EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor], -(i / 2), "not safe from enemy B"); } } } } else // not outposted { // // Still good to be close to the enemy king. // i = KNIGHT_KING_TROPISM_BONUS[uDist]; EVAL_TERM(uColor, KNIGHT, c, pos->iReducedMaterialDownScaler[uColor], i, "[scaled] enemy king tropism"); } // // Give a bonus for blockading an enemy backward pawn. Note: // we also reward pieces for blocking enemy passers in the // passer code. // cSquare = c + 16 * g_iAhead[uColor]; bb = pHash->bbStationaryPawns[FLIP(uColor)]; if (bb & COOR_TO_BB(cSquare)) { ASSERT(pos->rgSquare[cSquare].pPiece); ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor], KNIGHT_ON_INTERESTING_SQUARE_BY_RANK[uColor][RANK(c)], "blockades enemy pawn"); } // // A knight with an open file behind it is good. // uPawnFile = FILE(c) + 1; EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor], ((pHash->uCountPerFile[uColor][uPawnFile] == 0) * KNIGHT_ON_INTERESTING_SQUARE_BY_RANK[uColor][RANK(c)]), "open file behind"); // // Do mobilility and piece relevance. Also update attack tables. // uMobilitySquares = 0; u = 0; ASSERT(g_iNDeltas[u] != 0); do { cSquare = c + g_iNDeltas[u]; if (IS_ON_BOARD(cSquare)) { // // Always update the attack bits // ASSERT((cSquare + 8) == (cSquare | 8)); pos->rgSquare[cSquare|8].bvAttacks[uColor].small.uMinor = 1; // // See what we hit // p = pos->rgSquare[cSquare].pPiece; pFun = NMobJumpTable[uColor][p]; if (pFun != _AnythingToFriendNoXray) { (void)(*pFun)(pos, cSquare, &uMobilitySquares, NULL); } // // IDEA: bonus for hitting friendly pawn? // } u++; } while(g_iNDeltas[u] != 0); ASSERT(uMobilitySquares >= 0); ASSERT(uMobilitySquares <= 8); EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor], KNIGHT_MOBILITY_BY_COUNT[uMobilitySquares], "knight mobility"); if (uMobilitySquares == 0) { pos->cTrapped[uColor] = c; } ASSERT((pos->uMinMobility[uColor] & 0x80000000) == 0); ASSERT((uMobilitySquares & 0x80000000) == 0); pos->uMinMobility[uColor] = MINU(pos->uMinMobility[uColor], uMobilitySquares); } static void _EvalRook(IN OUT POSITION *pos, IN COOR c, IN PAWN_HASH_ENTRY *pHash) /** Routine description: Parameters: IN POSITION *pos, IN COOR c, IN PAWN_HASH_ENTRY *pHash, Return value: FLAG **/ { static const PMOBILITY_HELPER RMobJumpTable[2][14] = { {// (black) _BRookToEmpty, // EMPTY_SQUARE (0) _InvalidMobilityHelper, // INVALID_PIECE (1) _AnythingToFriendNoXray, // BLACK_PAWN (2) _BRookToEnemyLess, // WHITE_PAWN (3) _AnythingToFriendNoXray, // BLACK_KNIGHT (4) _BRookToEnemyLess, // WHITE_KNIGHT (5) _AnythingToFriendNoXray, // BLACK_BISHOP (6) _BRookToEnemyLess, // WHITE_BISHOP (7) _BRookToFriendRook, // BLACK_ROOK (8) _ERookToEnemySame, // WHITE_ROOK (9) _ERookToFriendQueen, // BLACK_QUEEN (10) _ERookToEnemyGreater, // WHITE_QUEEN (11) _AnythingToFriendNoXray, // BLACK_KING (12) _ERookToEnemyGreater, // WHITE_KING (13) }, {// (white) _WRookToEmpty, // EMPTY_SQUARE (0) _InvalidMobilityHelper, // INVALID_PIECE (1) _WRookToEnemyLess, // BLACK_PAWN (2) _AnythingToFriendNoXray, // WHITE_PAWN (3) _WRookToEnemyLess, // BLACK_KNIGHT (4) _AnythingToFriendNoXray, // WHITE_KNIGHT (5) _WRookToEnemyLess, // BLACK_BISHOP (6) _AnythingToFriendNoXray, // WHITE_BISHOP (7) _ERookToEnemySame, // BLACK_ROOK (8) _WRookToFriendRook, // WHITE_ROOK (9) _ERookToEnemyGreater, // BLACK_QUEEN (10) _ERookToFriendQueen, // WHITE_QUEEN (11) _ERookToEnemyGreater, // BLACK_KING (12) _AnythingToFriendNoXray, // WHITE_KING (13) }, }; PIECE p; PIECE pFriendRook; ULONG uPawnFile = FILE(c) + 1; ULONG uColor; ULONG u; ULONG uCurrentMobility; ULONG uMaxMobility; ULONG uTotalMobility; COOR cSquare; BITBOARD bb; ULONG uBit; PMOBILITY_HELPER pFun; SCORE i; ASSERT(IS_ON_BOARD(c)); p = pos->rgSquare[c].pPiece; ASSERT(p && IS_ROOK(p)); uColor = GET_COLOR(p); ASSERT(IS_VALID_COLOR(uColor)); pos->cPiece = c; // // Reward being on a half open or full open file. // if (0 == pHash->uCountPerFile[uColor][uPawnFile]) { u = FILE_DISTANCE(c, pos->cNonPawns[FLIP(uColor)][0]); if (0 == pHash->uCountPerFile[FLIP(uColor)][uPawnFile]) { // // Full open // EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], ROOK_ON_FULL_OPEN_BY_DIST_FROM_EKING[u], "on full open"); } else { // // Half open, no friendly, just enemy. // EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], ROOK_ON_HALF_OPEN_WITH_ENEMY_BY_DIST_FROM_EKING[u], "on half open"); // // Added bonus if the enemy pawn is a passer. // bb = pHash->bbPasserLocations[FLIP(uColor)] & BBFILE[FILE(c)]; if (bb) { cSquare = CoorFromBitBoardRank8ToRank1(&bb); EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], PASSER_BY_RANK[FLIP(uColor)][RANK(cSquare)] / 4, "hassles enemy passer"); } } } else if (0 == pHash->uCountPerFile[FLIP(uColor)][uPawnFile]) { // // Half open, no enemy pawn, just a friendly // u = FILE_DISTANCE(c, pos->cNonPawns[FLIP(uColor)][0]); EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], ROOK_ON_HALF_OPEN_WITH_FRIEND_BY_DIST_FROM_EKING[u], "on half open"); // // See if the friend is a passed pawn and if the rook's in // front of it or behind it. // bb = pHash->bbPasserLocations[uColor] & BBFILE[FILE(c)]; if (bb) { // // IDEA: Rook behind candidates, helpers or sentries is good // because the file may open soon. // while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { if (uColor == WHITE) { if (cSquare < c) { EVAL_TERM(WHITE, ROOK, c, pos->iScore[WHITE], ROOK_BEHIND_PASSER_BY_PASSER_RANK[WHITE] [RANK(cSquare)], "behind own passer"); } else { EVAL_TERM(WHITE, ROOK, c, pos->iScore[WHITE], ROOK_LEADS_PASSER_BY_PASSER_RANK[WHITE] [RANK(cSquare)], "in the way of passer"); } } else { ASSERT(uColor == BLACK); if (cSquare > c) { EVAL_TERM(BLACK, ROOK, c, pos->iScore[BLACK], ROOK_BEHIND_PASSER_BY_PASSER_RANK[BLACK] [RANK(cSquare)], "behind own passer"); } else { EVAL_TERM(BLACK, ROOK, c, pos->iScore[BLACK], ROOK_LEADS_PASSER_BY_PASSER_RANK[BLACK] [RANK(cSquare)], "in the way of passer"); } } } } } // // Look for rooks that are trapping enemy kings. // if (uColor == WHITE) { pFriendRook = WHITE_ROOK; // // Rook on 7th/8th rank with an enemy king back there. // if ((c < A6) && (pos->cNonPawns[BLACK][0] < A6)) { ASSERT(RANK(c) > 6); ASSERT(RANK(pos->cNonPawns[BLACK][0]) > 6); EVAL_TERM(WHITE, ROOK, c, pos->iScore[WHITE], ROOK_TRAPPING_EKING, "rook trapping enemy king"); } } else { pFriendRook = BLACK_ROOK; // // Rook on 7th/8th rank with an enemy king back there. // if ((c > H3) && (pos->cNonPawns[WHITE][0] > H3)) { ASSERT(RANK(c) < 3); ASSERT(RANK(pos->cNonPawns[WHITE][0]) < 3); EVAL_TERM(BLACK, ROOK, c, pos->iScore[BLACK], ROOK_TRAPPING_EKING, "rook trapping enemy king"); } } // // Rooks increase in value as pawns come off: // // "A further refinement would be to raise the knight's value by // 1/16 and lower the rook's value by 1/8 for each pawn above five // of the side being valued, with the opposite adjustment for each // pawn short of five." // --Larry Kaufman, IM // "Evaluation of Material Imbalance" // EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], ROOK_VALUE_AS_PAWNS_COME_OFF[pos->uPawnCount[uColor] + pos->uPawnCount[FLIP(uColor)]], "increase in value/pawns"); // // Rook mobility // u = uMaxMobility = uTotalMobility = 0; ASSERT(g_iRDeltas[u] != 0); do { uCurrentMobility = 0; uBit = ROOK_BIT; cSquare = c + g_iRDeltas[u]; while(IS_ON_BOARD(cSquare)) { // // Twiddle our attack table bits. // pos->rgSquare[cSquare|8].bvAttacks[uColor].uWholeThing |= uBit; ASSERT((cSquare | 8) == (cSquare + 8)); // // What did we hit? // p = pos->rgSquare[cSquare].pPiece; pFun = RMobJumpTable[uColor][p]; if (TRUE == (*pFun)(pos, cSquare, &uCurrentMobility, &uBit)) { break; } cSquare += g_iRDeltas[u]; } uTotalMobility += uCurrentMobility; ASSERT(uTotalMobility <= 14); ASSERT((uMaxMobility & 0x80000000) == 0); ASSERT((uCurrentMobility & 0x80000000) == 0); uMaxMobility = MAXU(uCurrentMobility, uMaxMobility); ASSERT(uMaxMobility <= 7); u++; } while(g_iRDeltas[u] != 0); ASSERT(uTotalMobility <= 14); ASSERT(pos->uArmyScaler[FLIP(uColor)] <= 31); // Tuning can (and has) pushed this out of its original hand-picked // bound; not a real invariant. //ASSERT(REDUCED_MATERIAL_UP_SCALER[pos->uArmyScaler[FLIP(uColor)]] <= 8); i = ROOK_MOBILITY_BY_SQUARES[uTotalMobility]; i *= (int)(REDUCED_MATERIAL_UP_SCALER[pos->uArmyScaler[FLIP(uColor)]] + 1); i /= 8; EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], i, "rook mobility"); EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], ROOK_MAX_MOBILITY_IN_A_ROW_BONUS[uMaxMobility], "consecutive rook mobility"); ASSERT((pos->uMinMobility[uColor] & 0x80000000) == 0); ASSERT((uTotalMobility & 0x80000000) == 0); pos->uMinMobility[uColor] = MINU(pos->uMinMobility[uColor], uTotalMobility); if (uTotalMobility < 3) { // // Look for rooks with no mobility who are under attack. These // pieces are trapped! // if (uTotalMobility == 0) { pos->cTrapped[uColor] = c; } // // Rook trapped in the corner by a stupid friendly king? // ASSERT(IS_VALID_COLOR(uColor)); if (uColor == WHITE) { ASSERT(IS_ON_BOARD(c)); if (RANK1(c)) { cSquare = pos->cNonPawns[WHITE][0]; ASSERT(IS_ON_BOARD(cSquare)); if (RANK1(cSquare)) { if (((cSquare > E1) && (c > cSquare)) || ((cSquare < D1) && (c < cSquare))) { EVAL_TERM(WHITE, ROOK, c, pos->iScore[WHITE], KING_TRAPPING_ROOK, "king trapping rook"); } } } } else { ASSERT(uColor == BLACK); ASSERT(IS_ON_BOARD(c)); if (RANK8(c)) { cSquare = pos->cNonPawns[BLACK][0]; ASSERT(IS_ON_BOARD(cSquare)); if (RANK8(cSquare)) { if (((cSquare > E8) && (c > cSquare)) || ((cSquare < D8) && (c < cSquare))) { EVAL_TERM(BLACK, ROOK, c, pos->iScore[BLACK], KING_TRAPPING_ROOK, "king trapping rook"); } } } } } // if low mobility } static void _EvalQueen(IN OUT POSITION *pos, IN COOR c, IN PAWN_HASH_ENTRY *pHash) /** Parameters: POSITION *pos, COOR c, PAWN_HASH_ENTRY *pHash, Return value: FLAG **/ { static const PMOBILITY_HELPER QMobJumpTable[2][14] = { { _BQueenToEmpty, // EMPTY_SQUARE (0) _InvalidMobilityHelper, // INVALID_PIECE (1) _AnythingToFriendNoXray, // BLACK_PAWN (2) _BQueenToEnemyLess, // WHITE_PAWN (3) _AnythingToFriendNoXray, // BLACK_KNIGHT (4) _BQueenToEnemyLess, // WHITE_KNIGHT (5) _EQueenToFriendBishop, // BLACK_BISHOP (6) _BQueenToEnemyLess, // WHITE_BISHOP (7) _EQueenToFriendRook, // BLACK_ROOK (8) _BQueenToEnemyLess, // WHITE_ROOK (9) _EQueenToFriendQueen, // BLACK_QUEEN (10) _EQueenToEnemyGreaterEqual, // WHITE_QUEEN (11) _AnythingToFriendNoXray, // BLACK_KING (12) _EQueenToEnemyGreaterEqual, // WHITE_KING (13) }, { _WQueenToEmpty, // EMPTY_SQUARE (0) _InvalidMobilityHelper, // INVALID_PIECE (1) _WQueenToEnemyLess, // BLACK_PAWN (2) _AnythingToFriendNoXray, // WHITE_PAWN (3) _WQueenToEnemyLess, // BLACK_KNIGHT (4) _AnythingToFriendNoXray, // WHITE_KNIGHT (5) _WQueenToEnemyLess, // BLACK_BISHOP (6) _EQueenToFriendBishop, // WHITE_BISHOP (7) _WQueenToEnemyLess, // BLACK_ROOK (8) _EQueenToFriendRook, // WHITE_ROOK (9) _EQueenToEnemyGreaterEqual, // BLACK_QUEEN (10) _EQueenToFriendQueen, // WHITE_QUEEN (11) _EQueenToEnemyGreaterEqual, // BLACK_KING (12) _AnythingToFriendNoXray, // WHITE_KING (13) }, }; PIECE p = pos->rgSquare[c].pPiece; ULONG uColor; COOR cSquare; ULONG uTotalMobility; ULONG u; ULONG uBit; PMOBILITY_HELPER pFun; ULONG uNearKing = 0; COOR cKing; ASSERT(IS_ON_BOARD(c)); ASSERT(p && IS_QUEEN(p)); uColor = GET_COLOR(p); ASSERT(IS_VALID_COLOR(uColor)); pos->cPiece = c; cKing = pos->cNonPawns[FLIP(uColor)][0]; ASSERT(IS_ON_BOARD(cKing)); ASSERT(IS_KING(pos->rgSquare[cKing].pPiece)); if ((FALSE == pos->fCastled[uColor]) && (pos->uMinorsAtHome[uColor] > 1)) { // // Discourage queen being out too early // if (!RANK1(c) && !RANK8(c)) { // Tuning can (and has) flipped this sign; not a real invariant. //ASSERT(QUEEN_OUT_EARLY[pos->uMinorsAtHome[uColor]] < 0); ASSERT(pos->uMinorsAtHome[uColor] <= 4); EVAL_TERM(uColor, QUEEN, c, pos->iScore[uColor], QUEEN_OUT_EARLY[pos->uMinorsAtHome[uColor]], "queen out too early"); } } else { // // Encourage enemy king tropism // u = DISTANCE(c, cKing); ASSERT((u <= 7) && (u > 0)); EVAL_TERM(uColor, QUEEN, c, pos->iScore[uColor], QUEEN_KING_TROPISM[u], "enemy king tropism"); } // // Do queen mobility // uTotalMobility = 0; u = 0; ASSERT(g_iQKDeltas[u] != 0); do { uBit = QUEEN_BIT; cSquare = c + g_iQKDeltas[u]; while(IS_ON_BOARD(cSquare)) { // // Toggle attack table bits. // pos->rgSquare[cSquare|8].bvAttacks[uColor].uWholeThing |= uBit; // // What did we hit? // p = pos->rgSquare[cSquare].pPiece; uNearKing += (DISTANCE(cSquare, cKing) <= 1); pFun = QMobJumpTable[uColor][p]; if (TRUE == (*pFun)(pos, cSquare, &uTotalMobility, &uBit)) { break; } cSquare += g_iQKDeltas[u]; } u++; } while(g_iQKDeltas[u] != 0); ASSERT(uTotalMobility <= 27); EVAL_TERM(uColor, QUEEN, c, pos->iScore[uColor], QUEEN_MOBILITY_BY_SQUARES[uTotalMobility], "queen mobility"); // // Look for queens with no mobility who are under attack. These // pieces are trapped! // if (uTotalMobility == 0) { pos->cTrapped[uColor] = c; } ASSERT((pos->uMinMobility[uColor] & 0x80000000) == 0); ASSERT((uTotalMobility & 0x80000000) == 0); pos->uMinMobility[uColor] = MINU(pos->uMinMobility[uColor], uTotalMobility); // // If queen points near enemy king, that's a good thing too. // ASSERT(uNearKing <= 6); EVAL_TERM(uColor, QUEEN, c, pos->iScore[uColor], (uNearKing * QUEEN_ATTACKS_SQ_NEXT_TO_KING), "pointing near enemy K"); } static void _EvalKing(IN OUT POSITION *pos, IN const COOR c, IN const PAWN_HASH_ENTRY *pHash) /** Routine description: Parameters: POSITION *pos, COOR c, PAWN_HASH_ENTRY *pHash, Return value: FLAG **/ { PIECE p; ULONG uColor, ufColor; COOR cSquare; ULONG u, v; ULONG uCounter; BITV bvAttack; BITV bvXray; BITV bvDefend; BITV bvPattern = 0; ULONG uFlightSquares; BITBOARD bb; SCORE i; SCORE iKingScore = 0; static ULONG KingFlightDefects[9] = { +4, +2, +1, 0, 0, 0, 0, 0, 0 }; static ULONG KingStormingPawnDefects[8] = { +0, +4, +3, +1, +0, +0, +0, +0 }; static ULONG KingFileDefects[3] = { +2, +1, +0 }; static INT KingSafetyDeltas[11] = { -17, -16, -15, -1, +1, +15, +16, +17, -2, +2, 0 }; static ULONG EmptyAttackedSquare[2][11] = { { 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 0 }, { 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0 }, }; static ULONG EmptyUnattackedSquare[2][11] = { { 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, }; static ULONG OccupiedAttackedSquare[2] = { 1, 2 }; ASSERT(IS_ON_BOARD(c)); p = pos->rgSquare[c].pPiece; ASSERT(p && IS_KING(p)); uColor = GET_COLOR(p); ufColor = FLIP(uColor); ASSERT(IS_VALID_COLOR(uColor)); u = pos->uNonPawnMaterial[ufColor]; ASSERT(u >= VALUE_KING); if (u < DO_KING_SAFETY_THRESHOLD) { u = 0; ASSERT(g_iQKDeltas[u] != 0); do { cSquare = c + g_iQKDeltas[u]; if (IS_ON_BOARD(cSquare)) { pos->rgSquare[cSquare|8].bvAttacks[uColor].small.uKing = 1; } u++; } while(g_iQKDeltas[u] != 0); goto skip_safety; } // Prepare to do king safety uCounter = (u >= KEEP_KING_AT_HOME_THRESHOLD) * KING_INITIAL_COUNTER_BY_LOCATION[uColor][c]; #ifdef EVAL_DUMP Trace("%s Initial KS Counter: %u\n", COLOR_NAME(uColor), uCounter); #endif uCounter += pos->uPiecesPointingAtKing[uColor] / 2; #ifdef EVAL_DUMP Trace("%s KS Counter after pieces pointing: %u\n", COLOR_NAME(uColor), uCounter); #endif uFlightSquares = 0; u = 0; ASSERT(KingSafetyDeltas[u] != 0); do { cSquare = c + KingSafetyDeltas[u]; if (IS_ON_BOARD(cSquare)) { p = pos->rgSquare[cSquare].pPiece; cSquare |= 8; bvAttack = pos->rgSquare[cSquare].bvAttacks[ufColor].uSmall; bvXray = pos->rgSquare[cSquare].bvAttacks[ufColor].uXray; pos->rgSquare[cSquare].bvAttacks[uColor].small.uKing = 1; bvDefend = pos->rgSquare[cSquare].bvAttacks[uColor].uSmall; if (bvAttack != 0) { bvPattern |= (bvAttack | bvXray); if (bvXray || (bvAttack & (bvAttack - 1))) { bvDefend &= ~8; } if (IS_EMPTY(p)) { ASSERT(u < 11); uCounter += EmptyAttackedSquare[uColor][u]; } else { uCounter += OccupiedAttackedSquare[OPPOSITE_COLORS(p, uColor)]; } uCounter += (bvDefend == 0); } else { uCounter += (bvXray != 0); ASSERT(u < 11); uCounter += EmptyUnattackedSquare[uColor][u]; ASSERT((IS_EMPTY(p) == 0) || (IS_EMPTY(p) == 1)); uFlightSquares += ((IS_EMPTY(p)) && (u < 8)); } } u++; } while(KingSafetyDeltas[u] != 0); #ifdef EVAL_DUMP Trace("%s KS Counter post-squares: %u\n", COLOR_NAME(uColor), uCounter); #endif if (IS_ON_BOARD(c - 1)) { u = FILE(c - 1) + 1; v = (pHash->uCountPerFile[WHITE][u] > 0) + (pHash->uCountPerFile[BLACK][u] > 0); ASSERT((v >= 0) && (v <= 2)); // Note: open rook files are worse than open interior files uCounter += (KingFileDefects[v] + ((v < 2) && ((u == 1) || (u == 8)))); bb = pHash->bbPawnLocations[ufColor] & BBFILE[u - 1]; if (bb) { if (uColor == WHITE) { cSquare = CoorFromBitBoardRank1ToRank8(&bb); } else { cSquare = CoorFromBitBoardRank8ToRank1(&bb); } ASSERT(IS_ON_BOARD(cSquare)); uCounter += KingStormingPawnDefects[DISTANCE(cSquare, c)]; } } u = FILE(c) + 1; v = (pHash->uCountPerFile[WHITE][u] > 0) + (pHash->uCountPerFile[BLACK][u] > 0); ASSERT((v >= 0) && (v <= 2)); // Note: open rook files are worse than open interior files uCounter += (KingFileDefects[v] + ((v < 2) && ((u == 1) || (u == 8)))); bb = pHash->bbPawnLocations[ufColor] & BBFILE[u - 1]; if (bb) { if (uColor == WHITE) { cSquare = CoorFromBitBoardRank1ToRank8(&bb); } else { cSquare = CoorFromBitBoardRank8ToRank1(&bb); } ASSERT(IS_ON_BOARD(cSquare)); uCounter += KingStormingPawnDefects[DISTANCE(cSquare, c)]; } if (IS_ON_BOARD(c + 1)) { ASSERT((FILE(c + 1) + 1) == (u + 1)); u += 1; v = (pHash->uCountPerFile[WHITE][u] > 0) + (pHash->uCountPerFile[BLACK][u] > 0); ASSERT((v >= 0) && (v <= 2)); // Note: open rook files are worse than open interior files uCounter += (KingFileDefects[v] + ((v < 2) && ((u == 1) || (u == 8)))); bb = pHash->bbPawnLocations[ufColor] & BBFILE[u - 1]; if (bb) { if (uColor == WHITE) { cSquare = CoorFromBitBoardRank1ToRank8(&bb); } else { cSquare = CoorFromBitBoardRank8ToRank1(&bb); } ASSERT(IS_ON_BOARD(cSquare)); uCounter += KingStormingPawnDefects[DISTANCE(cSquare, c)]; } } #ifdef EVAL_DUMP Trace("%s KS Counter post-open file/stormers: %u\n", COLOR_NAME(uColor), uCounter); #endif bvPattern >>= 3; ASSERT(bvPattern >= 0); ASSERT(bvPattern < 32); v = KING_COUNTER_BY_ATTACK_PATTERN[bvPattern]; uCounter += v; #ifdef EVAL_DUMP Trace("%s KS Counter post-attack pattern: %u\n", COLOR_NAME(uColor), uCounter); #endif ASSERT(uFlightSquares <= 8); uCounter += KingFlightDefects[uFlightSquares] * (v > 1); #ifdef EVAL_DUMP 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]; i *= REDUCED_MATERIAL_DOWN_SCALER[pos->uArmyScaler[ufColor]]; i /= 8; EVAL_TERM(uColor, KING, c, iKingScore, i, "king safety"); // // 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]) { if (pos->uNonPawnCount[uColor][0] > 4) { ASSERT(pos->uMinorsAtHome[uColor] <= 4); EVAL_TERM(uColor, 0, ILLEGAL_COOR, pos->iScore[uColor], UNDEVELOPED_MINORS_IN_OPENING[pos->uMinorsAtHome[uColor]], "development"); if (uColor == BLACK) { EVAL_TERM(BLACK, KING, c, iKingScore, (KING_MISSING_ONE_CASTLE_OPTION * ((CASTLE_BLACK_SHORT & pos->bvCastleInfo) == 0)), "can't castle short"); EVAL_TERM(BLACK, KING, c, iKingScore, (KING_MISSING_ONE_CASTLE_OPTION * ((CASTLE_BLACK_LONG & pos->bvCastleInfo) == 0)), "can't castle long"); } else { ASSERT(uColor == WHITE); EVAL_TERM(WHITE, KING, c, iKingScore, (KING_MISSING_ONE_CASTLE_OPTION * ((CASTLE_WHITE_SHORT & pos->bvCastleInfo) == 0)), "can't castle short"); EVAL_TERM(WHITE, KING, c, iKingScore, (KING_MISSING_ONE_CASTLE_OPTION * ((CASTLE_WHITE_LONG & pos->bvCastleInfo) == 0)), "can't castle long"); } } } skip_safety: // // Special code for kings in the endgame // u = pos->uNonPawnCount[WHITE][0] + pos->uNonPawnCount[BLACK][0]; if (u < 8) { // // Encourage kings to come to the center // i = KING_TO_CENTER[c]; EVAL_TERM(uColor, KING, c, iKingScore, i, "centralize king"); // // Kings in front of passers in the late endgame are strong... // cSquare = FILE(c); bb = pHash->bbPasserLocations[uColor] & (BBADJACENT_FILES[cSquare] | BBFILE[cSquare]); if (bb) { while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { if (DISTANCE(cSquare, c) == 1) { ASSERT(abs((INT)FILE(c) - (INT)FILE(cSquare)) <= 1); ASSERT(abs((INT)RANK(c) - (INT)RANK(cSquare)) <= 1); i = 2; i += SUPPORTED_PASSER_BY_RANK[uColor][RANK(cSquare)]; switch(uColor) { case WHITE: i += 8 * (c < (cSquare - 1)); break; case BLACK: i += 8 * (c > (cSquare + 1)); break; } EVAL_TERM(uColor, KING, c, iKingScore, i, "supporting own passer"); } } } // // Detect kings way out of the action in KP endgames: // // 8/8/1p6/p6k/P3K3/8/1P6/8 w - - 0 11 // if ((u == 2) && (uColor == WHITE)) { i = 0; bb = (pHash->bbPawnLocations[WHITE] | pHash->bbPawnLocations[BLACK]); while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { i += (DISTANCE(cSquare, pos->cNonPawns[BLACK][0]) - DISTANCE(cSquare, pos->cNonPawns[WHITE][0])); } EVAL_TERM(WHITE, KING, c, iKingScore, i * 12, "in/out of \"action\""); } } pos->iScore[uColor] += iKingScore; pos->iTempScore = iKingScore; } FLAG EvalPasserRaces(IN OUT POSITION *pos, IN PAWN_HASH_ENTRY *pHash) /** Routine description: Determine if one side has a pawn that is unstoppable. This is broken for positions like: Parameters: POSITION *pos, PAWN_HASH_ENTRY *pHash, Return value: void **/ { BITBOARD bb[2]; ULONG uColor; int d1; COOR cQueen; COOR cKing, c; ULONG uKingDist, uPawnDist, uFriendDist; ULONG uRacerDist[2] = { 99, 99 }; FLAG fDontCountMeOut[2] = { ((RANK(pos->cNonPawns[BLACK][0]) <= 3) && (pos->uPawnCount[BLACK] != 0)), ((RANK(pos->cNonPawns[WHITE][0]) >= 6) && (pos->uPawnCount[WHITE] != 0)) }; bb[BLACK] = pHash->bbPasserLocations[BLACK]; bb[WHITE] = pHash->bbPasserLocations[WHITE]; if (!(bb[BLACK] | bb[WHITE])) { return(FALSE); } ASSERT((pos->uNonPawnCount[WHITE][0] == 1) || (pos->uNonPawnCount[BLACK][0] == 1)) FOREACH_COLOR(uColor) { if (pos->uNonPawnCount[FLIP(uColor)][0] == 1) { d1 = 16 * g_iAhead[uColor]; ASSERT((d1 * 2) == (32 * g_iAhead[uColor])); if (bb[uColor]) { while(IS_ON_BOARD(c = CoorFromBitBoardRank8ToRank1( &(bb[uColor])))) { // If the enemy king can take this passer and is // on the move, all bets are off! cKing = pos->cNonPawns[FLIP(uColor)][0]; ASSERT(IS_KING(pos->rgSquare[cKing].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[cKing].pPiece) != uColor); if (pos->uToMove == FLIP(uColor) && (DISTANCE(cKing, c) == 1)) { // TODO: something clever here about protected pawns continue; } cQueen = FILE_RANK_TO_COOR(FILE(c), QUEENING_RANK[uColor]); ASSERT(FILE(cQueen) == FILE(c)); uPawnDist = RANK_DISTANCE(cQueen, c); if ((RANK(c) == JUMPING_RANK[uColor]) && (IS_EMPTY(pos->rgSquare[c + d1].pPiece) && IS_EMPTY(pos->rgSquare[c + d1 * 2].pPiece))) { uPawnDist--; } ASSERT((uPawnDist >= 1) && (uPawnDist <= 6)); uKingDist = DISTANCE(cKing, cQueen); if (uKingDist > 0) { uKingDist -= (pos->uToMove == FLIP(uColor)); } ASSERT((uKingDist >= 0) && (uPawnDist <= 7)); if (uPawnDist < uKingDist) { uRacerDist[uColor] = MINU(uRacerDist[uColor], uPawnDist); } cKing = pos->cNonPawns[uColor][0]; ASSERT(IS_KING(pos->rgSquare[cKing].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[cKing].pPiece) == uColor); uFriendDist = DISTANCE(cKing, cQueen); if ((uFriendDist <= 1) && (DISTANCE(cKing, c) <= 1)) { if (FILE(c) != FILE(cKing)) { uRacerDist[uColor] = MINU(uRacerDist[uColor], uPawnDist); } else if ((FILE(cKing) != A) && (FILE(cKing) != H)) { uRacerDist[uColor] = MINU(uRacerDist[uColor], (uPawnDist + 1)); } } fDontCountMeOut[uColor] = TRUE; // // TODO: winning connected passers // } } } } if ((uRacerDist[WHITE] < uRacerDist[BLACK]) && (FALSE == fDontCountMeOut[BLACK])) { EVAL_TERM(WHITE, PAWN, ILLEGAL_COOR, pos->iScore[WHITE], RACER_WINS_RACE - uRacerDist[WHITE] * 4, "winning racer pawn"); return(TRUE); } else if ((uRacerDist[BLACK] < uRacerDist[WHITE]) && (FALSE == fDontCountMeOut[WHITE])) { EVAL_TERM(BLACK, PAWN, ILLEGAL_COOR, pos->iScore[BLACK], RACER_WINS_RACE - uRacerDist[BLACK] * 4, "winning racer pawn"); return(TRUE); } return(FALSE); } static void _EvalPassers(IN OUT POSITION *pos, IN PAWN_HASH_ENTRY *pHash) /** Routine description: Parameters: POSITION *pos, PAWN_HASH_ENTRY *pHash, Return value: void **/ { ULONG u; ULONG uColor; COOR c; COOR cSquare; BITBOARD bb; PIECE p; SCORE i; ULONG uNumPassers[2] = { 0, 0 }; int d1; ASSERT(pHash->bbPasserLocations[WHITE] | pHash->bbPasserLocations[BLACK]); FOREACH_COLOR(uColor) { bb = pHash->bbPasserLocations[uColor]; if (bb != 0) { uNumPassers[uColor] = CountBits(bb); ASSERT(uNumPassers[uColor] > 0); d1 = 16 * g_iAhead[uColor]; ASSERT((d1 * 2) == (32 * g_iAhead[uColor])); while(IS_ON_BOARD(c = CoorFromBitBoardRank8ToRank1(&bb))) { // // Consider the control of the square in front of the passer // cSquare = c + d1; ASSERT(IS_ON_BOARD(cSquare)); u = _WhoControlsSquareFast(pos, cSquare); if (u == FLIP(uColor)) { 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 // // Consider distance from friend king / enemy king // i = (8 - DISTANCE(c, pos->cNonPawns[uColor][0])); if (((uColor == WHITE) && ((pos->cNonPawns[BLACK][0] >> 4) <= (c >> 4))) || ((uColor == BLACK) && ((pos->cNonPawns[WHITE][0] >> 4) >= (c >> 4)))) { // // The enemy king is ahead of the passer // i += FILE_DISTANCE(c, pos->cNonPawns[FLIP(uColor)][0]) * 4; } else { // // The enemy king is behind the passer // i += 10 + DISTANCE(c, pos->cNonPawns[FLIP(uColor)][0]) * 4; } i *= PASSER_MATERIAL_UP_SCALER[pos->uArmyScaler[FLIP(uColor)]]; i /= 8; EVAL_TERM(uColor, PAWN, c, 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 (pos->uNonPawnCount[uColor][0] > pos->uNonPawnCount[FLIP(uColor)][0]) { 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 // } } } // // Game stage bonus // u = pos->uArmyScaler[BLACK]; EVAL_TERM(WHITE, PAWN, ILLEGAL_COOR, pos->iScore[WHITE], uNumPassers[WHITE] * PASSER_BONUS_AS_MATERIAL_COMES_OFF[u], "passer value material"); // ASSERT(PASSER_BONUS_AS_MATERIAL_COMES_OFF[u] >= 0); u = pos->uArmyScaler[WHITE]; EVAL_TERM(BLACK, PAWN, ILLEGAL_COOR, pos->iScore[BLACK], uNumPassers[BLACK] * PASSER_BONUS_AS_MATERIAL_COMES_OFF[u], "passer value material"); // ASSERT(PASSER_BONUS_AS_MATERIAL_COMES_OFF[u] >= 0); } static void _EvalBadTrades(IN OUT POSITION *pos) /** Routine description: Parameters: POSITION *pos, Return value: void **/ { 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); #ifdef DEBUG if (pos->uNonPawnMaterial[WHITE] > pos->uNonPawnMaterial[BLACK]) { ASSERT(uAhead == WHITE); ASSERT(uBehind == BLACK); } else { ASSERT(pos->uNonPawnMaterial[BLACK] > pos->uNonPawnMaterial[WHITE]); ASSERT(uAhead == BLACK); 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 // trading pieces and not to trade pawns. This also has the // effect of making the side that is behind in material want to // trade pawns and not pieces. This also gives the side that is // ahead a bit of a "bad trade" bonus. Note that the bonus is not // given if the side "ahead" has no pawns; this is so that the // engine will not like positions like KNB vs KRP - the side with // the pawn has the winning chances. // EVAL_TERM(uAhead, 0, ILLEGAL_COOR, pos->iScore[uAhead], ((pos->uPawnCount[uAhead] != 0) * TRADE_PIECES[uMagnitude][pos->uNonPawnCount[uBehind][0]]), "trade pieces"); // Tuning can (and has) flipped this sign; not a real invariant. //ASSERT(TRADE_PIECES[uMagnitude][pos->uNonPawnCount[uBehind][0]] > 0); EVAL_TERM(uAhead, 0, ILLEGAL_COOR, pos->iScore[uAhead], DONT_TRADE_PAWNS[uMagnitude][pos->uPawnCount[uAhead]], "don't trade pawns"); } static void _EvalLookForDanger(IN OUT SEARCHER_THREAD_CONTEXT *ctx) /** Routine description: This routine looks for squares on the board where the side to move has a piece that is en prise. So far in eval we will have tagged pieces in situations where they are attacked by lesser-valued enemy pieces as "in danger". However, because of the order in which the attack table was generated, we will have missed situations where a piece is undefended (or underdefended) and attacked by an enemy piece that is the same (or greater) value. Note: This routine only considers side to move. Also Note: this routine does not find pawns that are en prise, only pieces. Parameters: SEARCHER_THREAD_CONTEXT *ctx Return value: void **/ { COOR c; ULONG u; POSITION *pos = &ctx->sPosition; ULONG uSide = pos->uToMove; #ifdef DEBUG PIECE p; #endif for (u = 1; u < pos->uNonPawnCount[uSide][0]; u++) { c = pos->cNonPawns[uSide][u]; #ifdef DEBUG ASSERT(IS_ON_BOARD(c)); p = pos->rgSquare[c].pPiece; ASSERT(p); ASSERT(GET_COLOR(p) == uSide); ASSERT(!IS_PAWN(p)); #endif if (_WhoControlsSquareFast(pos, c) == FLIP(uSide)) { RecordEnprisePiece(ctx, c); } } } static void _EvalTrappedPieces(IN OUT SEARCHER_THREAD_CONTEXT *ctx) /** Routine description: Parameters: SEARCHER_THREAD_CONTEXT *ctx Return value: static void **/ { COOR c; ULONG uColor; POSITION *pos = &ctx->sPosition; #ifdef DEBUG PIECE p; #endif FOREACH_COLOR(uColor) { c = pos->cTrapped[uColor]; if (IS_ON_BOARD(c)) { if (_WhoControlsSquareFast(pos, c) == FLIP(uColor)) { #ifdef DEBUG p = pos->rgSquare[c].pPiece; ASSERT(p); ASSERT(!IS_PAWN(p)); ASSERT(GET_COLOR(p) == uColor); #endif if (OPPOSITE_COLORS(uColor, pos->uToMove)) { // uColor is the mover at ctx->uPly - 1 (ply // parity), not here -- see RecordEnprisePieceAtPly. if (ctx->uPly > 0) { RecordEnprisePieceAtPly(ctx, ctx->uPly - 1, c); } } else { RecordTrappedPiece(ctx, c); } } } } } static void _EvalBishopPairs(IN POSITION *pos) /*++ Routine description: Give a bonus to sides that have a viable bishop pair based; scale the bonus by the number of pawns remaining on the board. Parameters: IN POSITION *pos - position Return value: static void --*/ { ULONG uPawnSum = pos->uPawnCount[WHITE] + pos->uPawnCount[BLACK]; FLAG fPair; ULONG uBishopCount = pos->uNonPawnCount[BLACK][BISHOP]; ULONG uWhiteSqBishopCount = pos->uWhiteSqBishopCount[BLACK]; ASSERT(uPawnSum <= 16); ASSERT(uBishopCount <= 10); ASSERT(uWhiteSqBishopCount <= 10); fPair = ((uBishopCount > 1) & (uWhiteSqBishopCount != 0) & (uWhiteSqBishopCount != uBishopCount)); EVAL_TERM(BLACK, 0, ILLEGAL_COOR, pos->iScore[BLACK], BISHOP_PAIR[fPair][uPawnSum], "bishop pair"); uBishopCount = pos->uNonPawnCount[WHITE][BISHOP]; uWhiteSqBishopCount = pos->uWhiteSqBishopCount[WHITE]; ASSERT(uBishopCount <= 10); ASSERT(uWhiteSqBishopCount <= 10); fPair = ((uBishopCount > 1) & (uWhiteSqBishopCount != 0) & (uWhiteSqBishopCount != uBishopCount)); EVAL_TERM(WHITE, 0, ILLEGAL_COOR, pos->iScore[WHITE], BISHOP_PAIR[fPair][uPawnSum], "bishop pair"); } SCORE Eval(IN SEARCHER_THREAD_CONTEXT *ctx, IN SCORE iAlpha, IN SCORE iBeta, OUT SCORE *piPositional) /** Routine description: Parameters: SEARCHER_THREAD_CONTEXT *ctx, SCORE iAlpha, SCORE iBeta, SCORE *piPositional : if non-NULL, filled in with a magnitude (always >= 0) estimating the non-material component of the score -- exact if a full eval ran, a cheap estimate otherwise. Callers must treat it as an estimate either way; it's only ever used to size a pruning margin, never as a hard fact. Return value: SCORE **/ { static const PEVAL_HELPER JumpTable[] = { _InvalidEvaluator, // EMPTY_SQUARE (0) _InvalidEvaluator, // INVALID_PIECE (1) _InvalidEvaluator, // BLACK_PAWN (2) _InvalidEvaluator, // WHITE_PAWN (3) _EvalKnight, // BLACK_KNIGHT (4) _EvalKnight, // WHITE_KNIGHT (5) _EvalBishop, // BLACK_BISHOP (6) _EvalBishop, // WHITE_BISHOP (7) _InvalidEvaluator, // BLACK_ROOK (8) _InvalidEvaluator, // WHITE_ROOK (9) _InvalidEvaluator, // BLACK_QUEEN (10) _InvalidEvaluator, // WHITE_QUEEN (11) _InvalidEvaluator, // BLACK_KING (12) _InvalidEvaluator // WHITE_KING (13) }; ULONG uDefer[2][2]; COOR cDefer[2][2][10]; POSITION *pos = &(ctx->sPosition); SCORE iScoreForSideToMove; SCORE iAlphaMargin, iBetaMargin; PAWN_HASH_ENTRY *pHash; COOR c; PIECE p; ULONG u; ULONG uColor; BITBOARD bb; FLAG fDeferred; #ifdef EVAL_TIME UINT64 uTimer = SystemReadTimeStampCounter(); #endif ASSERT(IS_VALID_SCORE(iAlpha)); ASSERT(IS_VALID_SCORE(iBeta)); ASSERT(iAlpha < iBeta); ASSERT((pos->iMaterialBalance[WHITE] * -1) == pos->iMaterialBalance[BLACK]); // ASSERT(!InCheck(pos, pos->uToMove)); pos->uMinMobility[BLACK] = pos->uMinMobility[WHITE] = 100; pos->cTrapped[BLACK] = pos->cTrapped[WHITE] = ILLEGAL_COOR; pos->iScore[BLACK] = (pos->uPawnMaterial[BLACK] + pos->uNonPawnMaterial[BLACK]); pos->iScore[WHITE] = (pos->uPawnMaterial[WHITE] + pos->uNonPawnMaterial[WHITE]); #ifdef EVAL_DUMP EvalTraceClear(); Trace("Material:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif // // Pawn eval. Note: if fDeferred comes back as TRUE then we have // neither cleared nor initialized the attack tables. This is // because we hope that we can get a lazy eval cutoff here and // save some time. BEFORE ANY CODE BELOW TOUCHES THE ATTACK // TABLES IT NEEDS TO CLEAR/POPULATE THEM THOUGH!!! // pHash = _EvalPawns(ctx, &fDeferred); ASSERT(NULL != pHash); ASSERT(IS_VALID_FLAG(fDeferred)); pos->iScore[WHITE] += pHash->iScore[WHITE]; pos->iScore[BLACK] += pHash->iScore[BLACK]; ASSERT(IS_VALID_SCORE(pos->iScore[WHITE] - pos->iScore[BLACK])); #ifdef EVAL_DUMP Trace("After pawns:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif // // Look for won passers races. Note: This code cannot trust the // state of the attack tables yet! // if ((pos->uNonPawnCount[WHITE][0] == 1) || (pos->uNonPawnCount[BLACK][0] == 1)) { (void)EvalPasserRaces(pos, pHash); #ifdef EVAL_DUMP Trace("After passer races:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif } // // Bad trade code. Right. Note: this code cannot trust the state // of the attack tables... // if (pos->uNonPawnMaterial[WHITE] != pos->uNonPawnMaterial[BLACK]) { _EvalBadTrades(pos); #ifdef EVAL_DUMP Trace("After bad trades:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif } // // Bishop pairs. Again... Note: this code cannot trust the state // of the attack tables. // _EvalBishopPairs(pos); #ifdef EVAL_DUMP Trace("After bishop pair bonus:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif #ifdef LAZY_EVAL // // Compute an estimate of the score for the side to move based on the // eval terms we have already considered: // // 1. Material balance // 2. Pawn structure bonuses/penalties (incl passers/candidates) // 3. Passer races are detected already // 4. "Bad trade" code has already run // 5. We've already detected unwinnable endgames // 6. Bishop pairs // iScoreForSideToMove = (pos->iScore[pos->uToMove] - pos->iScore[FLIP(pos->uToMove)]); ASSERT(IS_VALID_SCORE(iScoreForSideToMove)); // // Eval has not considered several potentially large terms: // // 1. Piece positional components (such as mobility, trapped) // 2. King safety penalties // 3. Other miscellaneous bonuses/penalties // // We build two "margins" to account for these components of the // score. // iAlphaMargin = iBetaMargin = LAZY_EVAL_BASE_MARGIN; // // If (score + alpha_margin) is already > alpha -OR- // (score - beta_margin) is already < beta // // ...then we can stop thinking about lazy eval; the rest of the // computation only increases the margin so we know LE will fail // and can save some work here. // if ((iScoreForSideToMove + iAlphaMargin < iAlpha) || (iScoreForSideToMove - iBetaMargin > iBeta)) { // // Ok, we can't say for sure that we won't take a lazy exit // yet. So do the expensive part of lazy eval estimation and // widen the margin further for king safety issues and passed // pawns. // EstimatePositionalScore(pos, pHash, &iAlphaMargin, &iBetaMargin); if (iScoreForSideToMove + iAlphaMargin <= iAlpha) { INC(ctx->sCounters.tree.u64LazyEvals); if (NULL != piPositional) { *piPositional = iAlphaMargin; } goto end; } else if (iScoreForSideToMove - iBetaMargin >= iBeta) { INC(ctx->sCounters.tree.u64LazyEvals); if (NULL != piPositional) { *piPositional = iBetaMargin; } goto end; } } #endif // // If we have to clear/populate the attack table, do it now that // we know we aren't taking a lazy exit. // if (TRUE == fDeferred) { _PopulatePawnAttackBits(pos); } // // Pre-compute some common terms used in per-piece evals: // // This is a scaler based on the size of the army for each side. // pos->uArmyScaler[BLACK] = pos->uNonPawnMaterial[BLACK] - VALUE_KING; pos->uArmyScaler[WHITE] = pos->uNonPawnMaterial[WHITE] - VALUE_KING; pos->uArmyScaler[BLACK] /= VALUE_PAWN; pos->uArmyScaler[WHITE] /= VALUE_PAWN; ASSERT(!(pos->uArmyScaler[BLACK] & 0x80000000)); ASSERT(!(pos->uArmyScaler[WHITE] & 0x80000000)); pos->uArmyScaler[BLACK] = MINU(31, pos->uArmyScaler[BLACK]); pos->uArmyScaler[WHITE] = MINU(31, pos->uArmyScaler[WHITE]); ASSERT(pos->uArmyScaler[BLACK] >= 0); ASSERT(pos->uArmyScaler[BLACK] <= 31); ASSERT(pos->uArmyScaler[WHITE] >= 0); ASSERT(pos->uArmyScaler[WHITE] <= 31); pos->iReducedMaterialDownScaler[BLACK] = pos->iReducedMaterialDownScaler[WHITE] = 0; // // This is a "position is closed|open" number. // pos->uClosedScaler = (pos->uPawnCount[WHITE] + pos->uPawnCount[BLACK]) - (pHash->uNumUnmovedPawns[WHITE] + pHash->uNumUnmovedPawns[BLACK]) + (pHash->uNumRammedPawns); ASSERT(pos->uClosedScaler >= 0); ASSERT(pos->uClosedScaler <= 32); // // Evaluate individual pieces. // pos->uMinorsAtHome[BLACK] = pos->uMinorsAtHome[WHITE] = 0; uDefer[BLACK][0] = uDefer[BLACK][1] = uDefer[WHITE][0] = uDefer[WHITE][1] = 0; uColor = pos->uToMove; for (u = 1; // skips the king u < pos->uNonPawnCount[uColor][0]; u++) { c = pos->cNonPawns[uColor][u]; ASSERT(IS_ON_BOARD(c)); p = pos->rgSquare[c].pPiece; ASSERT(IS_VALID_PIECE(p)); ASSERT(GET_COLOR(p) == uColor); ASSERT(!IS_PAWN(p) && !IS_KING(p)); if (p & 0x4) { ASSERT(IS_BISHOP(p) || IS_KNIGHT(p)); // // Note: The side on move's pieces of value X are // evaluated and have their mobility counted and added to // the attack table before the other side's pieces. Also, // we do not care if JumpTable tells us this piece is in // danger b/c we will find out later on in // EvalLookForDanger anyway. // ASSERT(JumpTable[p]); (void)(JumpTable[p])(pos, c, pHash); #ifdef EVAL_DUMP Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p), pos->iScore[WHITE], pos->iScore[BLACK]); #endif } else { ASSERT(IS_ROOK(p) || IS_QUEEN(p)); cDefer[uColor][IS_QUEEN(p)][uDefer[uColor][IS_QUEEN(p)]++] = c; } } uColor = FLIP(uColor); ASSERT(uColor != pos->uToMove); for (u = 1; u < pos->uNonPawnCount[uColor][0]; u++) { c = pos->cNonPawns[uColor][u]; ASSERT(IS_ON_BOARD(c)); p = pos->rgSquare[c].pPiece; ASSERT(IS_VALID_PIECE(p)); ASSERT(GET_COLOR(p) == uColor); ASSERT(!IS_PAWN(p) && !IS_KING(p)); if (p & 0x4) { // // This time we are evaluating a piece from the side not // on move. We will not go back later and look at this // piece again so if JumpTable tells us it's in danger // then record it. The drawback, of course, is that the // JumpTable routines only detect danger if a piece is // attacked by an enemy of lesser value! // ASSERT(JumpTable[p]); (void)(JumpTable[p])(pos, c, pHash); #ifdef EVAL_DUMP Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p), pos->iScore[WHITE], pos->iScore[BLACK]); #endif } else { ASSERT(IS_ROOK(p) || IS_QUEEN(p)); cDefer[uColor][IS_QUEEN(p)][uDefer[uColor][IS_QUEEN(p)]++] = c; } } // // Evaluate any rook(s) for side on move then for side not on move. // uColor = FLIP(uColor); for (u = 0; u < uDefer[uColor][0]; u++) { c = cDefer[uColor][0][u]; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ROOK(pos->rgSquare[c].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor); _EvalRook(pos, c, pHash); #ifdef EVAL_DUMP p = BLACK_ROOK | uColor; Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p), pos->iScore[WHITE], pos->iScore[BLACK]); #endif } uColor = FLIP(uColor); for (u = 0; u < uDefer[uColor][0]; u++) { c = cDefer[uColor][0][u]; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_ROOK(pos->rgSquare[c].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor); _EvalRook(pos, c, pHash); #ifdef EVAL_DUMP p = BLACK_ROOK | uColor; Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p), pos->iScore[WHITE], pos->iScore[BLACK]); #endif } // // Evaluate any queen(s) for side on move then for side not on move. // uColor = FLIP(uColor); for (u = 0; u < uDefer[uColor][1]; u++) { c = cDefer[uColor][1][u]; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_QUEEN(pos->rgSquare[c].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor); _EvalQueen(pos, c, pHash); #ifdef EVAL_DUMP p = BLACK_ROOK | uColor; Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p), pos->iScore[WHITE], pos->iScore[BLACK]); #endif } uColor = FLIP(uColor); for (u = 0; u < uDefer[uColor][1]; u++) { c = cDefer[uColor][1][u]; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_QUEEN(pos->rgSquare[c].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor); _EvalQueen(pos, c, pHash); #ifdef EVAL_DUMP p = BLACK_ROOK | uColor; Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p), pos->iScore[WHITE], pos->iScore[BLACK]); #endif } // // Evaluate the two kings last. // c = pos->cNonPawns[BLACK][0]; #ifdef DEBUG ASSERT(IS_ON_BOARD(c)); p = pos->rgSquare[c].pPiece; ASSERT(IS_VALID_PIECE(p)); ASSERT(GET_COLOR(p) == BLACK); ASSERT(IS_KING(p)); ASSERT(JumpTable[p]); #endif _EvalKing(pos, c, pHash); 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 c = pos->cNonPawns[WHITE][0]; #ifdef DEBUG ASSERT(IS_ON_BOARD(c)); p = pos->rgSquare[c].pPiece; ASSERT(IS_VALID_PIECE(p)); ASSERT(GET_COLOR(p) == WHITE); ASSERT(IS_KING(p)); ASSERT(JumpTable[p]); #endif _EvalKing(pos, c, pHash); 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 // // Now that we have the whole attack table generated, think about // passed pawns identified by the pawn eval routine again. Also // see if the side not on move has a trapped piece. // bb = (pHash->bbPasserLocations[WHITE] | pHash->bbPasserLocations[BLACK]); if (0 != bb) { _EvalPassers(pos, pHash); #ifdef EVAL_DUMP Trace("After passers:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif } // // Make one more pass over the piece list for the side on move now // that the full attack table is computed to detect // under/unprotected pieces en prise to more valuable enemy pieces // which we missed when building the attack table incrementally. // ctx->sPlyInfo[ctx->uPly].uMinMobility[BLACK] = pos->uMinMobility[BLACK]; ctx->sPlyInfo[ctx->uPly].uMinMobility[WHITE] = pos->uMinMobility[WHITE]; // // _EvalLookForDanger/_EvalTrappedPieces only ever *add* a hint // when they find one; a full eval that finds nothing this time // would otherwise leave a stale, unrelated hint from an earlier // visit to this ply sitting here (still piece-identity-valid by // coincidence). Clear this ply's self-danger slots first so a // clean full eval reliably means a clean slate. // ctx->cEnprise[ctx->uPly][0] = ctx->cEnprise[ctx->uPly][1] = ILLEGAL_COOR; ctx->cTrapped[ctx->uPly] = ILLEGAL_COOR; _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 = (pHash->bbPawnLocations[WHITE] | pHash->bbPawnLocations[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 } // // Roll in the reduced material down scaler terms. // ASSERT(pos->iReducedMaterialDownScaler[BLACK] > -200); ASSERT(pos->iReducedMaterialDownScaler[BLACK] < +200); iAlphaMargin = (pos->iReducedMaterialDownScaler[BLACK] * (SCORE)REDUCED_MATERIAL_DOWN_SCALER[pos->uArmyScaler[BLACK]]) / 8; pos->iScore[BLACK] += iAlphaMargin; ASSERT(pos->iReducedMaterialDownScaler[WHITE] > -200); ASSERT(pos->iReducedMaterialDownScaler[WHITE] < +200); iAlphaMargin = (pos->iReducedMaterialDownScaler[WHITE] * (SCORE)REDUCED_MATERIAL_DOWN_SCALER[pos->uArmyScaler[WHITE]]) / 8; pos->iScore[WHITE] += iAlphaMargin; // // Almost done // iScoreForSideToMove = (pos->iScore[pos->uToMove] - pos->iScore[FLIP(pos->uToMove)]); #ifdef EVAL_DUMP Trace("At the end:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif // // TODO: detect and discourage blocked positions? // // // TODO: drive the score towards zero as we approach a 50 move w/o // progress draw. // // // Adjust dynamic positional component. // iAlphaMargin = abs(pos->iMaterialBalance[pos->uToMove] - iScoreForSideToMove); if (NULL != piPositional) { *piPositional = iAlphaMargin; } g_Options.iLastEvalScore = iScoreForSideToMove; #ifdef EVAL_TIME ctx->sCounters.tree.u64CyclesInEval += (SystemReadTimeStampCounter() - uTimer); #endif INC(ctx->sCounters.tree.u64FullEvals); end: ASSERT(IS_VALID_SCORE(iScoreForSideToMove)); ASSERT((iScoreForSideToMove > -NMATE) && (iScoreForSideToMove < +NMATE)); ASSERT(abs(ctx->sPlyInfo[ctx->uPly].iKingScore[BLACK]) < 700); ASSERT(abs(ctx->sPlyInfo[ctx->uPly].iKingScore[WHITE]) < 700); ASSERT(ctx->sPlyInfo[ctx->uPly].uMinMobility[BLACK] <= 100); ASSERT(ctx->sPlyInfo[ctx->uPly].uMinMobility[WHITE] <= 100); return(iScoreForSideToMove); }