| | | 1 | | using Rudim.Common; |
| | | 2 | | using System.Numerics; |
| | | 3 | | |
| | | 4 | | namespace Rudim.Board |
| | | 5 | | { |
| | | 6 | | public static class PawnStructureEvaluation |
| | | 7 | | { |
| | | 8 | | private const int DoubledPawnPenalty = 10; |
| | | 9 | | private const int IsolatedPawnPenalty = 20; |
| | | 10 | | |
| | | 11 | | // Passed pawn bonus indexed by row (0 = rank 8, 7 = rank 1). |
| | | 12 | | // Row 0 and row 7 are 0 because white pawns promote at rank 8 (row 0) before reaching it, |
| | | 13 | | // and rank 1 (row 7) is below white's starting rank so unreachable as a pawn. |
| | | 14 | | // Smaller row index = further advanced for white = larger bonus. |
| | 1 | 15 | | private static readonly int[] PassedPawnBonus = [0, 100, 70, 50, 30, 20, 10, 0]; |
| | | 16 | | |
| | 1 | 17 | | private static readonly ulong[] FileMasks = new ulong[8]; |
| | 1 | 18 | | private static readonly ulong[] AdjacentFileMasks = new ulong[8]; |
| | | 19 | | |
| | | 20 | | // PassedPawnMasks[side, square] = squares in front of the pawn on same/adjacent files |
| | 1 | 21 | | private static readonly ulong[,] PassedPawnMasks = new ulong[2, Constants.Squares]; |
| | | 22 | | |
| | | 23 | | static PawnStructureEvaluation() |
| | | 24 | | { |
| | 18 | 25 | | for (int file = 0; file < 8; file++) |
| | | 26 | | { |
| | 8 | 27 | | ulong mask = 0; |
| | 144 | 28 | | for (int row = 0; row < 8; row++) |
| | 64 | 29 | | mask |= 1ul << (row * 8 + file); |
| | 8 | 30 | | FileMasks[file] = mask; |
| | | 31 | | } |
| | | 32 | | |
| | 18 | 33 | | for (int file = 0; file < 8; file++) |
| | | 34 | | { |
| | 8 | 35 | | AdjacentFileMasks[file] = 0; |
| | 15 | 36 | | if (file > 0) AdjacentFileMasks[file] |= FileMasks[file - 1]; |
| | 15 | 37 | | if (file < 7) AdjacentFileMasks[file] |= FileMasks[file + 1]; |
| | | 38 | | } |
| | | 39 | | |
| | 130 | 40 | | for (int sq = 0; sq < Constants.Squares; sq++) |
| | | 41 | | { |
| | 64 | 42 | | int file = sq & 7; |
| | 64 | 43 | | int row = sq >> 3; |
| | | 44 | | |
| | 64 | 45 | | ulong whiteMask = 0; |
| | 576 | 46 | | for (int r = 0; r < row; r++) |
| | | 47 | | { |
| | 224 | 48 | | whiteMask |= 1ul << (r * 8 + file); |
| | 420 | 49 | | if (file > 0) whiteMask |= 1ul << (r * 8 + file - 1); |
| | 420 | 50 | | if (file < 7) whiteMask |= 1ul << (r * 8 + file + 1); |
| | | 51 | | } |
| | 64 | 52 | | PassedPawnMasks[(int)Side.White, sq] = whiteMask; |
| | | 53 | | |
| | 64 | 54 | | ulong blackMask = 0; |
| | 576 | 55 | | for (int r = row + 1; r < 8; r++) |
| | | 56 | | { |
| | 224 | 57 | | blackMask |= 1ul << (r * 8 + file); |
| | 420 | 58 | | if (file > 0) blackMask |= 1ul << (r * 8 + file - 1); |
| | 420 | 59 | | if (file < 7) blackMask |= 1ul << (r * 8 + file + 1); |
| | | 60 | | } |
| | 64 | 61 | | PassedPawnMasks[(int)Side.Black, sq] = blackMask; |
| | | 62 | | } |
| | 1 | 63 | | } |
| | | 64 | | |
| | | 65 | | // Returns score from white's perspective (positive = good for white) |
| | | 66 | | public static int Evaluate(BoardState boardState) |
| | | 67 | | { |
| | 3839688 | 68 | | ulong whitePawns = boardState.Pieces[(int)Side.White, (int)Piece.Pawn].Board; |
| | 3839688 | 69 | | ulong blackPawns = boardState.Pieces[(int)Side.Black, (int)Piece.Pawn].Board; |
| | | 70 | | |
| | 3839688 | 71 | | int score = 0; |
| | 3839688 | 72 | | score += ScoreDoubledPawns(whitePawns, blackPawns); |
| | 3839688 | 73 | | score += ScorePawnFeatures(whitePawns, blackPawns); |
| | 3839688 | 74 | | return score; |
| | | 75 | | } |
| | | 76 | | |
| | | 77 | | private static int ScoreDoubledPawns(ulong whitePawns, ulong blackPawns) |
| | | 78 | | { |
| | 3839688 | 79 | | int score = 0; |
| | 69114384 | 80 | | for (int file = 0; file < 8; file++) |
| | | 81 | | { |
| | 30717504 | 82 | | int whiteCount = BitOperations.PopCount(whitePawns & FileMasks[file]); |
| | 30717504 | 83 | | int blackCount = BitOperations.PopCount(blackPawns & FileMasks[file]); |
| | 31939781 | 84 | | if (whiteCount > 1) score -= (whiteCount - 1) * DoubledPawnPenalty; |
| | 32664113 | 85 | | if (blackCount > 1) score += (blackCount - 1) * DoubledPawnPenalty; |
| | | 86 | | } |
| | 3839688 | 87 | | return score; |
| | | 88 | | } |
| | | 89 | | |
| | | 90 | | private static int ScorePawnFeatures(ulong whitePawns, ulong blackPawns) |
| | | 91 | | { |
| | 3839688 | 92 | | int score = 0; |
| | 3839688 | 93 | | ulong wp = whitePawns; |
| | 29541167 | 94 | | while (wp != 0) |
| | | 95 | | { |
| | 25701479 | 96 | | int sq = BitOperations.TrailingZeroCount(wp); |
| | 25701479 | 97 | | wp &= wp - 1; |
| | 25701479 | 98 | | if ((whitePawns & AdjacentFileMasks[sq & 7]) == 0) |
| | 3778434 | 99 | | score -= IsolatedPawnPenalty; |
| | 25701479 | 100 | | if ((blackPawns & PassedPawnMasks[(int)Side.White, sq]) == 0) |
| | 690599 | 101 | | score += PassedPawnBonus[sq >> 3]; |
| | | 102 | | } |
| | | 103 | | |
| | 3839688 | 104 | | ulong bp = blackPawns; |
| | 28275913 | 105 | | while (bp != 0) |
| | | 106 | | { |
| | 24436225 | 107 | | int sq = BitOperations.TrailingZeroCount(bp); |
| | 24436225 | 108 | | bp &= bp - 1; |
| | 24436225 | 109 | | if ((blackPawns & AdjacentFileMasks[sq & 7]) == 0) |
| | 4449301 | 110 | | score += IsolatedPawnPenalty; |
| | 24436225 | 111 | | if ((whitePawns & PassedPawnMasks[(int)Side.Black, sq]) == 0) |
| | 943684 | 112 | | score -= PassedPawnBonus[7 - (sq >> 3)]; |
| | | 113 | | } |
| | 3839688 | 114 | | return score; |
| | | 115 | | } |
| | | 116 | | } |
| | | 117 | | } |