Chess Board Evaluation

From what information I could gather on the internet, it seems that most experts agree that the evaluation function of a chess engine has the greatest capability to dictate the strength of your chess game. Rybka, currently considered to be the top computer chess engine in the world, is not famous for its search speed but rather for its advanced chess board evaluation written by an international chess master. Furthermore the evaluation function can make your move searching faster by allowing your chess engine to search better moves first.

Unfortunately for you Rybka’s evaluation function is not available. To make things worse I am not considered by anyone to be a chess master.

Chess Piece Evaluation

As seen in the Chess Piece Class, the following values are assigned to chess pieces:

  • Pawn 100
  • Knight 320
  • Bishop 325
  • Rook 500
  • Queen 975
  • King 32767

About the numbers.

Writing your chess board evaluation method, think about the numbers as percentages of a pawn. In my chess engine a pawn is worth 100 points. If you award 10 points to a tactical position you are telling the chess engine that this position is worth 1/10th of a pawn. The problem can arise when a move combines several tactical advantages for the moving side and several tactical penalties for the opponent. This can result in unnecessary pawn sacrifice for set of minor tactical advantages. When writing your evaluation function always keep this in mind.

The Chess Board Score is represented by a signed integer. The higher the score the better it is for White. The lower the score the better it is for black. Using a single integer makes everything a bit faster since I can always just reference one variable.

For example if the value of a single pawn is 100 points and there was only one single black pawn on the chess board the score would be -100. If there were two pawns, one white and one black the score would be 0. If white had 2 pawns and black 1 pawn the score would be 100.

On to the code

Chess Bin Board evaluation is implemented as a static class with two static methods.

internal static class Evaluation

We first declare our Piece Square Tables discussed in the previous post:

private static readonly short[] PawnTable = new short[]
{
  0,  0,  0,  0,  0,  0,  0,  0,
 50, 50, 50, 50, 50, 50, 50, 50,
 10, 10, 20, 30, 30, 20, 10, 10,
  5,  5, 10, 27, 27, 10,  5,  5,
  0,  0,  0, 25, 25,  0,  0,  0,
  5, -5,-10,  0,  0,-10, -5,  5,
  5, 10, 10,-25,-25, 10, 10,  5,
  0,  0,  0,  0,  0,  0,  0,  0
};
private static readonly short[] KnightTable = new short[]
{
 -50,-40,-30,-30,-30,-30,-40,-50,
 -40,-20,  0,  0,  0,  0,-20,-40,
 -30,  0, 10, 15, 15, 10,  0,-30,
 -30,  5, 15, 20, 20, 15,  5,-30,
 -30,  0, 15, 20, 20, 15,  0,-30,
 -30,  5, 10, 15, 15, 10,  5,-30,
 -40,-20,  0,  5,  5,  0,-20,-40,
 -50,-40,-20,-30,-30,-20,-40,-50,
};

private static readonly short[] BishopTable = new short[]
{
 -20,-10,-10,-10,-10,-10,-10,-20,
 -10,  0,  0,  0,  0,  0,  0,-10,
 -10,  0,  5, 10, 10,  5,  0,-10,
 -10,  5,  5, 10, 10,  5,  5,-10,
 -10,  0, 10, 10, 10, 10,  0,-10,
 -10, 10, 10, 10, 10, 10, 10,-10,
 -10,  5,  0,  0,  0,  0,  5,-10,
 -20,-10,-40,-10,-10,-40,-10,-20,
};

private static readonly short[] KingTable = new short[]
{
  -30, -40, -40, -50, -50, -40, -40, -30,
  -30, -40, -40, -50, -50, -40, -40, -30,
  -30, -40, -40, -50, -50, -40, -40, -30,
  -30, -40, -40, -50, -50, -40, -40, -30,
  -20, -30, -30, -40, -40, -30, -30, -20,
  -10, -20, -20, -20, -20, -20, -20, -10, 
   20,  20,   0,   0,   0,   0,  20,  20,
   20,  30,  10,   0,   0,  10,  30,  20
};

private static readonly short[] KingTableEndGame = new short[]
{
 -50,-40,-30,-20,-20,-30,-40,-50,
 -30,-20,-10,  0,  0,-10,-20,-30,
 -30,-10, 20, 30, 30, 20,-10,-30,
 -30,-10, 30, 40, 40, 30,-10,-30,
 -30,-10, 30, 40, 40, 30,-10,-30,
 -30,-10, 20, 30, 30, 20,-10,-30,
 -30,-30,  0,  0,  0,  0,-30,-30,
 -50,-30,-30,-30,-30,-30,-30,-50
};

The first method will evaluate the score for a single chess piece on the board.

private static int EvaluatePieceScore(Square square, byte position, bool castled, bool endGamePhase,ref byte bishopCount, ref bool insufficientMaterial)

We declare a single variable representing the score for the current chess piece. This score will always start at 0 and go up if the position is evaluated to be better, and go down if the position is evaluated to be worse. When the score is returned to the main Evaluation Function, it will be subtracted for black and added for white.

int score = 0;

We also declare one local variable that will hold a position we will use to look up the Pieces Piece Square Table value. As you saw above the Piece Square Table is an array of 64 elements, basically representing the chess board from white’s perspective. In that case we can simply use white’s position value to lookup it’s Piece Square Table value. However if the chess piece is black, we need to invert its position to use the same tables for both white and black.

byte index = position;
if (square.Piece.PieceColor == ChessPieceColor.Black)
{
 index = (byte)(63-position);
}

Each piece type has a value. For example a pawn is worth 100 points, a Rook 500 etc. We add that value to the score.

score += square.Piece.PieceValue;

During move generation we record how many pieces are protecting each piece on the board. This is done by adding the protecting pieces PieceProtectionValue to the protected pieces ProtectedValue. In the evaluation method we add this Protected Value to the score. I know this sounds confusing. The trick here is that I don’t want to simply count the number of pieces protecting or attacking another piece. Rather I want to give more points for being attacked or protected by a Pawn and fewer points for being attacked or protected by a minor or major piece.

score += square.Piece.DefendedValue;

Similarly during move generation we added each attacking piece’s Piece Attack Value to the attacked pieces Attacked Value. We now subtract the attacked value from the score. The idea here is that we reward the computer for protecting its pieces and penalize it for having the pieces attacked.

score -= square.Piece.AttackedValue;

Furthermore if the chess piece is getting attacked and it is not protected then will consider that piece EnPrise, meaning we are about to lose it. There is an additional penalty applied by subtracting the Attack Value from the Score again.

//Double Penalty for Hanging Pieces
if (square.Piece.DefendedValue < square.Piece.AttackedValue)
{
 score -= ((square.Piece.AttackedValue - square.Piece.DefendedValue)* 10);
}
}

We will also add score for mobility. This discourages trapped pieces and blocked pawns.

if (square.Piece.ValidMoves != null)
{
 score += square.Piece.ValidMoves.Count;
}

The remainder of the code is chess piece specific, starting with Pawns.

The following code will perform 3 Evaluations:

  • Remove some points for pawns on the edge of the board. The idea is that since a pawn of the edge can only attack one way it is worth 15% less.
  • Give an extra bonus for pawns that are on the 6th and 7th rank as long as they are not attacked in any way.
  • Add points based on the Pawn Piece Square Table Lookup

We will also keep track in what column we find each pawn. This will not be a pawn count but a value of what that pawn means in that column if we later find him to be isolated passed or doubled. This information will be used later to score isolated passed and doubled pawns.

if (square.Piece.PieceType == ChessPieceType.Pawn)
{
 insufficientMaterial = false;
 if (position % 8 == 0 || position % 8 == 7)
 {
  //Rook Pawns are worth 15% less because they can only attack one way
  score -= 15;
 }

 //Calculate Position Values
 score += PawnTable[index];

 if (square.Piece.PieceColor == ChessPieceColor.White)
 {
  if (whitePawnCount[position % 8] > 0)
  {
   //Doubled Pawn
   score -= 16;
  }

  if (position >= 8 && position <= 15)
  {
   if (square.Piece.AttackedValue == 0)
   {
    whitePawnCount[position % 8] += 200;

    if (square.Piece.DefendedValue != 0)
     whitePawnCount[position % 8] += 50;
   }
  }
  else if (position >= 16 && position <= 23)
  {
   if (square.Piece.AttackedValue == 0)
   {
    whitePawnCount[position % 8] += 100;


    if (square.Piece.DefendedValue != 0)
     whitePawnCount[position % 8] += 25;
   }
  }

  whitePawnCount[position % 8]+=10;
 }
 else
 {
  if (blackPawnCount[position % 8] > 0)
  {
   //Doubled Pawn
   score -= 16;
  }

  if (position >= 48 && position <= 55)
  {
   if (square.Piece.AttackedValue == 0)
   {
    blackPawnCount[position % 8] += 200;

    if (square.Piece.DefendedValue != 0)
     blackPawnCount[position % 8] += 50;
   }
  }
  //Pawns in 6th Row that are not attacked are worth more points.
  else if (position >= 40 && position <= 47)
  {
   if (square.Piece.AttackedValue == 0)
   {
    blackPawnCount[position % 8] += 100;

    if (square.Piece.DefendedValue != 0)
     blackPawnCount[position % 8] += 25;
   }
  }

  blackPawnCount[position % 8] += 10;
  
 }
}

Knights also get a value from the Piece Square Table.  However knights are worth less in the end game since it is difficult to mate with a knight hence they lose 10 points during the end game.

else if (square.Piece.PieceType == ChessPieceType.Knight)
{
 score += KnightTable[index];
 //In the end game remove a few points for Knights since they are worth less
 if (endGamePhase)
 {
  score -= 10;
 }

}

Opposite to Knights, Bishops are worth more in the end game, also we add a small bonus for having 2 bishops since they complement each other by controlling different ranks.

else if (square.Piece.PieceType == ChessPieceType.Bishop)
{
 bishopCount++;
 if (bishopCount >= 2)
 {
  //2 Bishops receive a bonus
  score += 10;
 }

 //In the end game Bishops are worth more
 if (endGamePhase)
 {
  score += 10;
 }

 score += BishopTable[index];
}

We want to encourage Rooks not to move out of their corner positions before castling has occurred. Also if a rook is present on the board we will set a variable called Insufficient Material to false. This allows us to catch a tie scenario if insufficient material is present on the board.

else if (square.Piece.PieceType == ChessPieceType.Rook)
{
 insufficientMaterial = false;
 if (square.Piece.Moved && castled == false)
 {
  score -= 10;
 }
}

Furthermore we want to discourage Queen movement in the beginning of the game so we give a small penalty if the Queen has moved.

else if (square.Piece.PieceType == ChessPieceType.Queen)
{
 insufficientMaterial = false;
 if (square.Piece.Moved && !endGamePhase)
 {
  score -= 10;
 }
}

Finally in order to encourage the computer to castle we will remove points if castling is no longer possible. Furthermore we will also take away points if the king has less than 2 moves. The idea here is that if the king has only one move, we are possibly one move away from a mate.

else if (square.Piece.PieceType == ChessPieceType.King)
{
 if (square.Piece.ValidMoves.Count < 2)
 {
  score -= 5;
 } 
 if (endGamePhase)
 {
  score += KingTableEndGame[index];
 }
 else
 {
  score += KingTable[index];
  if (square.Piece.Moved && castled == false)
  {
   score -= 30;
  }
 }
}

At the end our method simply returns the score

return score;

The second method in the Board Evaluation Class is a static method that accepts the chess board and the currently moving side. This is the main Evaluation Function used in the chess engine. It will evaluate board specific events such as check and mate as well as loop through all of the chess pieces on the board and call the above described Evaluate Piece Score for each piece.

internal static void EvaluateBoardScore(Board board)

At the beginning of the evaluation the score will always be set to 0.

board.Score = 0;

We will also declare a variable called insufficient material that will be set to true only if enough chess pieces (material) are found to prevent the Insufficient Material Tie Rule.

bool insufficientMaterial = true;

The evaluation method will first examine board wide specific events. Is there a check, stale mate, has any side castled etc. Most of the work on figuring out these events has already been done in the Chess Piece Motion or Chess Board classes; all we are doing now is summing up the score.

if (board.StaleMate)
{
    return;
}
if (board.FiftyMoveCount >= 50)
{
    return;
}
if (board.RepeatedMoveCount >= 3)
{
    return;
}

Next we give bonuses and penalties for board level scenarios such as King Checks, Mates and Castles.

if (board.BlackMate)
{
 board.Score = 32767;
 return;
}
if (board.WhiteMate)
{
 board.Score = -32767;
 return;
}
if (board.BlackCheck)
{
 board.Score += 75;
 if (board.EndGamePhase)
  board.Score += 10;
}
else if (board.WhiteCheck)
{
 board.Score -= 75;
 if (board.EndGamePhase)
  board.Score -= 10;
}
if (board.BlackCastled)
{
 board.Score -= 40;
}
if (board.WhiteCastled)
{
 board.Score += 40;
}
//Add a small bonus for tempo (turn)
if (board.WhoseMove == ChessPieceColor.White)
{
 board.Score += 10;
}
else
{
 board.Score -= 10;
}

The following two integers will keep track of how many bishops and knights are remaining on the board.  This will allow us to give an additional bonus if a player has both bishops.  Also if there 2 are 2 knights we can’t call the Insufficient Material Tie rule.

byte blackBishopCount = 0;
byte whiteBishopCount = 0;
byte knightCount = 0;

The following integer will calculate remaining material on the board.  We will use this later on to make the decision if we are currently in the middle or end game.  End game evaluation can differ from the middle game.  For example in the middle game Knights are very useful since they can hop behind enemy lines and take out vulnerable pieces, however in an end game Knights have a hard time placing the king in a corner for a mate so their value is slightly diminished.  Also castling in an end game has a diminished bonus.

int RemainingPieces = 0;

We also need to keep track how many pawns exist in each column so that later we can figure out if we have any isolated and doubled pawns.

blackPawnCount = new short[8];
whitePawnCount = new short[8];

The next step of the evaluation is a loop through all of the chess pieces on the chess board and call the EvaluatePieceScore method defined above.

for (byte x = 0; x < 64; x++)
{
 Square square = board.Squares[x];
 if (square.Piece == null)
  continue;

 //Calculate Remaining Material for end game determination
 remainingPieces++;

 if (square.Piece.PieceColor == ChessPieceColor.White)
 {
  board.Score += EvaluatePieceScore(square, x, board.WhiteCastled, board.EndGamePhase,
   ref whiteBishopCount, ref insufficientMaterial);
 }
 else if (square.Piece.PieceColor == ChessPieceColor.Black)
 {
  board.Score -= EvaluatePieceScore(square, x, board.BlackCastled, board.EndGamePhase,
   ref blackBishopCount, ref insufficientMaterial);
 }

 if (square.Piece.PieceType == ChessPieceType.Knight)
 {
  knightCount++;

  if (knightCount > 1)
  {
   insufficientMaterial = false;
  }
 }

 if ((blackBishopCount + whiteBishopCount) > 1)
 {
  insufficientMaterial = false;
 }
}

Next section does will handle the remaining board level events, such as calling a tie if there is insufficient material on the chess board.

After looping through all of the chess pieces we also know how many pieces are remaining on the chess board.  If there are less than 10 pieces we will mark the chess board as being in an endgame.  This way the next evaluation will change slightly based on the end game rules defined.

if (insufficientMaterial)
{
 board.Score = 0;
 board.StaleMate = true;
 board.InsufficientMaterial = true;
 return;
}
if (remainingPieces < 10)
{
 board.EndGamePhase = true;

 if (board.BlackCheck)
 {
  board.Score += 10;
 }
 else if (board.WhiteCheck)
 {
  board.Score -= 10;
 }
}

The last section of code uses the previously stored pawn position information and figures out if there are any doubled and isolated pawns.  Each one of these pawns are given a strong penalty.

//Black Isolated Pawns
if (blackPawnCount[0] >= 1 && blackPawnCount[1] == 0)
{
 board.Score += 12;
}
if (blackPawnCount[1] >= 1 && blackPawnCount[0] == 0 &&
 blackPawnCount[2] == 0)
{
 board.Score += 14;
}
if (blackPawnCount[2] >= 1 && blackPawnCount[1] == 0 &&
 blackPawnCount[3] == 0)
{
 board.Score += 16;
}
if (blackPawnCount[3] >= 1 && blackPawnCount[2] == 0 &&
 blackPawnCount[4] == 0)
{
 board.Score += 20;
}
if (blackPawnCount[4] >= 1 && blackPawnCount[3] == 0 &&
 blackPawnCount[5] == 0)
{
 board.Score += 20;
}
if (blackPawnCount[5] >= 1 && blackPawnCount[4] == 0 &&
 blackPawnCount[6] == 0)
{
 board.Score += 16;
}
if (blackPawnCount[6] >= 1 && blackPawnCount[5] == 0 &&
 blackPawnCount[7] == 0)
{
 board.Score += 14;
}
if (blackPawnCount[7] >= 1 && blackPawnCount[6] == 0)
{
 board.Score += 12;
}
//White Isolated Pawns
if (whitePawnCount[0] >= 1 && whitePawnCount[1] == 0)
{
 board.Score -= 12;
}
if (whitePawnCount[1] >= 1 && whitePawnCount[0] == 0 &&
 whitePawnCount[2] == 0)
{
 board.Score -= 14;
}
if (whitePawnCount[2] >= 1 && whitePawnCount[1] == 0 &&
 whitePawnCount[3] == 0)
{
 board.Score -= 16;
}
if (whitePawnCount[3] >= 1 && whitePawnCount[2] == 0 &&
 whitePawnCount[4] == 0)
{
 board.Score -= 20;
}
if (whitePawnCount[4] >= 1 && whitePawnCount[3] == 0 &&
 whitePawnCount[5] == 0)
{
 board.Score -= 20;
}
if (whitePawnCount[5] >= 1 && whitePawnCount[4] == 0 &&
 whitePawnCount[6] == 0)
{
 board.Score -= 16;
}
if (whitePawnCount[6] >= 1 && whitePawnCount[5] == 0 &&
 whitePawnCount[7] == 0)
{
 board.Score -= 14;
}
if (whitePawnCount[7] >= 1 && whitePawnCount[6] == 0)
{
 board.Score -= 12;
}

//Black Passed Pawns
if (blackPawnCount[0] >= 1 && whitePawnCount[0] == 0)
{
 board.Score -= blackPawnCount[0];
}
if (blackPawnCount[1] >= 1 && whitePawnCount[1] == 0)
{
 board.Score -= blackPawnCount[1];
}
if (blackPawnCount[2] >= 1 && whitePawnCount[2] == 0)
{
 board.Score -= blackPawnCount[2];
}
if (blackPawnCount[3] >= 1 && whitePawnCount[3] == 0)
{
 board.Score -= blackPawnCount[3];
}
if (blackPawnCount[4] >= 1 && whitePawnCount[4] == 0)
{
 board.Score -= blackPawnCount[4];
}
if (blackPawnCount[5] >= 1 && whitePawnCount[5] == 0)
{
 board.Score -= blackPawnCount[5];
}
if (blackPawnCount[6] >= 1 && whitePawnCount[6] == 0)
{
 board.Score -= blackPawnCount[6];
}
if (blackPawnCount[7] >= 1 && whitePawnCount[7] == 0)
{
 board.Score -= blackPawnCount[7];
}

//White Passed Pawns
if (whitePawnCount[0] >= 1 && blackPawnCount[1] == 0)
{
 board.Score += whitePawnCount[0];
}
if (whitePawnCount[1] >= 1 && blackPawnCount[1] == 0)
{
 board.Score += whitePawnCount[1];
}
if (whitePawnCount[2] >= 1 && blackPawnCount[2] == 0)
{
 board.Score += whitePawnCount[2];
}
if (whitePawnCount[3] >= 1 && blackPawnCount[3] == 0)
{
 board.Score += whitePawnCount[3];
}
if (whitePawnCount[4] >= 1 && blackPawnCount[4] == 0)
{
 board.Score += whitePawnCount[4];
}
if (whitePawnCount[5] >= 1 && blackPawnCount[5] == 0)
{
 board.Score += whitePawnCount[5];
}
if (whitePawnCount[6] >= 1 && blackPawnCount[6] == 0)
{
 board.Score += whitePawnCount[6];
}
if (whitePawnCount[7] >= 1 && blackPawnCount[7] == 0)
{
 board.Score += whitePawnCount[7];
}

This concludes the Chess Piece Evaluation. The ChessBin evaluation function is by no means complete. I will be spending some additional time on this evaluation function soon and post any updates to this page.

In my experience modifying the evaluation function is extremely dangerous. Even small changes can significantly weaken your chess engine. The problem is that we are dealing here with a system of values that relate to each other in various and complicated ways. It is difficult to tell ahead of time how a small change in a bonus or penalty will affect how the engine will interpret this change in various situations. When modifying the chess engine evaluation function is best to always test against the previous version. I always play the new version of my chess engine against the previously released one. This means that you should save different version of your chess game as you go along. Furthermore if you find your chess engine made a blunder, save the game, correct the mistake in your code then load the save game to see if that solved it. Over time you will have a few save games that will allow you to quickly test your new code. I found that often new code re-introduced some old bug I solved in an older save game and actually made the chess engine weaker.

Want to skip to the end, download the full chess engine source code on GitHub