For reference the chess-programming wiki already mentions quite a few standard evaluation terms in respect to a knights value, specifically outposts and trapped pieces amongst others none of which I've actually implemented yet with exception of PST values which in the case of the knight fairly accurately describe its activity. In this post I will be describing the evaluation terms I use instead of the first evaluation term ("decreasing value as pawns disappear") mentioned by the CPW in my engine, The Machine.
First off I think the rule is a fine rule of thumb to implement in a fairly primitive engine as it is correct in a lot of positions. That being said however, there are a few positions it does not really capture the essence of a few positions. To start off, consider the following positions:
Position 1
[d]6k1/ppp2ppp/2n5/4p3/4P3/8/PPP1BPPP/6K1 b - - 0 1
Position 2
[d]6k1/ppp2ppp/2n1p3/8/8/4P3/PPP1BPPP/6K1 b - - 0 1
Position 3
[d]6k1/pp3ppp/2n1p3/8/8/4P3/PP2BPPP/6K1 b - - 0 1
Position 4
[d]6k1/p4ppp/2n1p3/8/8/4P3/P3BPPP/6K1 b - - 0 1
In my opinion these positions are increasingly promising for white. The first position is out of the bishops perspective an improved version of the structure found in the famous game Saidy - Fischer 1964 which however was extremely beneficial for the knight.
[pgn]
[Event "US Championship 1963/64"]
[Site "New York City, NY USA"]
[Date "1964.01.01"]
[EventDate "1963.??.??"]
[Round "11"]
[Result "0-1"]
[White "Anthony Saidy"]
[Black "Robert James Fischer"]
[ECO "A33"]
[WhiteElo "?"]
[BlackElo "?"]
[PlyCount "112"]
1. c4 c5 2. Nf3 Nc6 3. d4 cxd4 4. Nxd4 Nf6 5. Nc3 e6 6. Ndb5
Bb4 7. a3 Bxc3+ 8. Nxc3 d5 9. e3 O-O 10. cxd5 exd5 11. Be2 Bf5
12. Nb5 Qb6 13. O-O a6 14. Nd4 Nxd4 15. Qxd4 Qxd4 16. exd4
Rac8 17. Bd1 Bc2 18. Be3 Bxd1 19. Rfxd1 Rc2 20. Rd2 Rfc8
21. Rxc2 Rxc2 22. Rc1 Rxc1+ 23. Bxc1 Nd7 24. Kf1 Nf8 25. Ke2
Ne6 26. Kd3 h5 27. Be3 Kh7 28. f3 Kg6 29. a4 Kf5 30. Ke2 g5
31. Kf2 Nd8 32. Bd2 Kg6 33. Ke3 Ne6 34. Kd3 Kf5 35. Be3 f6
36. Ke2 Kg6 37. Kd3 f5 38. Ke2 f4 39. Bf2 Ng7 40. h3 Nf5
41. Kd3 g4 42. hxg4 hxg4 43. fxg4 Nh6 44. Be1 Nxg4 45. Bd2 Kf5
46. Be1 Nf6 47. Bh4 Ne4 48. Be1 Kg4 49. Ke2 Ng3+ 50. Kd3 Nf5
51. Bf2 Nh4 52. a5 Nxg2 53. Kc3 Kf3 54. Bg1 Ke2 55. Bh2 f3
56. Bg3 Ne3 0-1
[/pgn]
The second position is a further clear improvement for the bishop as the pawns are more elastic and the knight has more trouble finding useful squares. In order to describe that the structural benefit the knight has in the first diagram compared to the second I added an evaluation heuristic in The Machine which gives a bonus to the knight for each pawn blocked by a pawn of the opposite color.
From The Machine's source code:
Code: Select all
#ifdef BLOCKED_PAWN_KNIGHT_BONUS
value = popCount(north(wPawns) & bPawns) * (Board::pieceCount[WHITE][KNIGHT] - Board::pieceCount[BLACK][KNIGHT]);
scoreOp += knightBlockedPawnBonus[OPENING] * value;
scoreEnd += knightBlockedPawnBonus[ENDING] * value;
#endif
[d]6k1/5ppp/2n1p3/8/8/4P3/4BPPP/6K1 b - - 0 1
According to CPW's aforementioned diagram this is a further improvement for the bishop, however this is not the case! With the pawns all on the same side of the board the bishop has absolutely no benefit over the knight. So what is going on? Well simply counting pawns is usually right since as more pawns are found on the board, more pawns blocking one another are likely to exist. Furthermore the more pawns exist the more likely a knight is to be doing it's part in the struggle for victory. The problem the knight has is if a games fight can quickly change sides of the board from one move to another the knight has trouble keeping up. The further apart pawns get the more likely it is for the knight to be overwhelmed compared to the bishop. With less pawns on the board, it becomes less likely that the pawns are together and less likely the knight will be doing something useful.
This is where my next evaluation term comes into play: Pawn Gap Penalty for Knights. What I do is that I calculate the fileset of all the pawns on the board. Then I calculate the greatest consecutive number of non set bits between two set bits. Though you could give a different penalty for each value this returns I simply multiply this number by a flat penalty for simplicity and then add this penalty for each knight on the board. Originally I considered calculating the pawn gaps for white and or black pawns separately and adding them on top of the penalty I just described but I haven't tried it out and was worried about orthogonality issues. My implementation is further sped up by calculating pawn gap values in advance at program initialization and entering them into an array which may be referenced by the fileset byte.
From my source:
Code: Select all
#ifdef PAWN_GAP_KNIGHT_PENALTY
value = (Magic::getPawnGap(bPawnsSouthFill | wPawnsSouthFill)) * (Board::pieceCount[WHITE][KNIGHT] - Board::pieceCount[BLACK][KNIGHT]);
scoreOp += knightGapPenalty[OPENING] * value;
scoreEnd += knightGapPenalty[ENDING] * value;
#endif
Code: Select all
unsigned char getPawnGap(const unsigned char fileSet) {
return pawnGap[fileSet];
}
Code: Select all
void generatePawnGap() {
for (unsigned int i = 0; i < 256; i++) {
unsigned char c = 1;
char max = -1, current = 0;
while (c) {
if ((c & ~i)) {if (max != -1) current++;}
else {
if (current > max) max = current;
current = 0;
}
c <<= 1;
}
if (max == -1) max = 0;
pawnGap[i] = max;
}
}
Finally one last useful position to think about or possibly discuss:
[d]8/pp2k3/4n3/8/8/4B3/3K2PP/8 b - - 0 1
Obviously according to pawn gaps this should be a troublesome position for the knight. Though with how reduced material is, I imagine black holds.