Piece Square Tables

    Piece square tables are a very simple technique used for basic evaluation. For every piece type and square, PSTs have a value for that piece being on that square. Fruit uses a clear and simple but effective way of calculating the tables. Looking at Rybka's PSTs, we will see that they are calculated using these exact same constants except with different weights. Also, note that here too that the PST values are hardcoded into the Rybka executable file, they are not calculated at startup like Fruit's. The code shown here is simply the functional equivalent; it calculates the Rybka PSTs.

Constants

    Fruit's PSTs are based on a small set of constants, which allow for a compact representation of the values. For most pieces, the entire set of 64 squares is compressed into 16 constants (8 for ranks, 8 for files) plus two weights.

Constants in Fruit
static const int PawnFile[8] = { -3, -1, +0, +1, +1, +0, -1, -3 };
static const int KnightLine[8] = { -4, -2, +0, +1, +1, +0, -2, -4 };
static const int KnightRank[8] = { -2, -1, +0, +1, +2, +3, +2, +1 };
static const int BishopLine[8] = { -3, -1, +0, +1, +1, +0, -1, -3 };
static const int RookFile[8] = { -2, -1, +0, +1, +1, +0, -1, -2 };
static const int QueenLine[8] = { -3, -1, +0, +1, +1, +0, -1, -3 };
static const int KingLine[8] = { -3, -1, +0, +1, +1, +0, -1, -3 };
static const int KingFile[8] = { +3, +4, +2, +0, +0, +2, +4, +3 };
static const int KingRank[8] = { +1, +0, -2, -3, -4, -5, -6, -7 };

Pawns

    First we have pawns. The pawn PSTs are just based on the file. We also add in a bonus for some of the center squares. Rybka is the same, but it adds in an endgame bonus, and also only the bonuses for D5/E5 are added.

Fruit Rybka
static const int PawnFileOpening = 5;

...

for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += PawnFile[square_file(sq)] *
PawnFileOpening;
}

P(piece,D3,Opening) += 10;
P(piece,E3,Opening) += 10;

P(piece,D4,Opening) += 20;
P(piece,E4,Opening) += 20;

P(piece,D5,Opening) += 10;
P(piece,E5,Opening) += 10;
static const int PawnFileOpening = 181;
static const int PawnFileEndgame = -97;

...

for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += PawnFile[square_file(sq)] *
PawnFileOpening;
P(piece,sq,Endgame) += PawnFile[square_file(sq)] *
PawnFileEndgame;
}

P(piece,D5,Opening) += 74;
P(piece,E5,Opening) += 74;

Knights

    Next there are knights. Knight PSTs are based on the rank and file, with a "center" term counting for both ranks and files, and also a separate rank bonus. Two corrections are then applied: a "trapped" penalty for knights on A8/H8, and a "back rank" penalty for knights on the first rank (to help development). Also note that the "back rank" penalty has a weight of 0 in both programs, so it doesn't appear in the PSTs.

Fruit Rybka
static const int KnightCentreOpening = 5;
static const int KnightCentreEndgame = 5;
static const int KnightRankOpening = 5;
static const int KnightBackRankOpening = 0;
static const int KnightTrapped = 100;

...

for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += KnightLine[square_file(sq)] *
KnightCentreOpening;
P(piece,sq,Opening) += KnightLine[square_rank(sq)] *
KnightCentreOpening;
P(piece,sq,Endgame) += KnightLine[square_file(sq)] *
KnightCentreEndgame;
P(piece,sq,Endgame) += KnightLine[square_rank(sq)] *
KnightCentreEndgame;
}
for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += KnightRank[square_rank(sq)] *
KnightRankOpening;
}
for (sq = A1; sq <= H1; sq++) {
P(piece,sq,Opening) -= KnightBackRankOpening;
}
P(piece,A8,Opening) -= KnightTrapped;
P(piece,H8,Opening) -= KnightTrapped;
static const int KnightCentreOpening = 347;
static const int KnightCentreEndgame = 56;
static const int KnightRankOpening = 358;
static const int KnightBackRankOpening = 0;
static const int KnightTrapped = 3200;

...

for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += KnightLine[square_file(sq)] *
KnightCentreOpening;
P(piece,sq,Opening) += KnightLine[square_rank(sq)] *
KnightCentreOpening;
P(piece,sq,Endgame) += KnightLine[square_file(sq)] *
KnightCentreEndgame;
P(piece,sq,Endgame) += KnightLine[square_rank(sq)] *
KnightCentreEndgame;
}
for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += KnightRank[square_rank(sq)] *
KnightRankOpening;
}
for (sq = A1; sq <= H1; sq++) {
P(piece,sq,Opening) -= KnightBackRankOpening;
}
P(piece,A8,Opening) -= KnightTrapped;
P(piece,H8,Opening) -= KnightTrapped;

Bishops

    Next are the bishops. Bishop PSTs are based on the rank and file, with a "center" term counting equally for both ranks and files. There is also a bonus for being on either of the main diagonals, and there is an additional penalty for being on the back rank.

Fruit Rybka
static const int BishopCentreOpening = 2;
static const int BishopCentreEndgame = 3;
static const int BishopBackRankOpening = 10;
static const int BishopDiagonalOpening = 4;
...

for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += BishopLine[square_file(sq)] *
BishopCentreOpening;
P(piece,sq,Opening) += BishopLine[square_rank(sq)] *
BishopCentreOpening;
P(piece,sq,Endgame) += BishopLine[square_file(sq)] *
BishopCentreEndgame;
P(piece,sq,Endgame) += BishopLine[square_rank(sq)] *
BishopCentreEndgame;
}
for (sq = A1; sq <= H1; sq++) {
P(piece,sq,Opening) -= BishopBackRankOpening;
}
for (i = 0; i < 8; i++) {
sq = square_make(i,i);
P(piece,sq,Opening) += BishopDiagonalOpening;
P(piece,square_opp(sq),Opening) += BishopDiagonalOpening;
}
static const int BishopCentreOpening = 147;
static const int BishopCentreEndgame = 49;
static const int BishopBackRankOpening = 251;
static const int BishopDiagonalOpening = 378;

...

for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += BishopLine[square_file(sq)] *
BishopCentreOpening;
P(piece,sq,Opening) += BishopLine[square_rank(sq)] *
BishopCentreOpening;
P(piece,sq,Endgame) += BishopLine[square_file(sq)] *
BishopCentreEndgame;
P(piece,sq,Endgame) += BishopLine[square_rank(sq)] *
BishopCentreEndgame;
}
for (sq = A1; sq <= H1; sq++) {
P(piece,sq,Opening) -= BishopBackRankOpening;
}
for (i = 0; i < 8; i++) {
sq = square_make(i,i);
P(piece,sq,Opening) += BishopDiagonalOpening;
P(piece,square_opp(sq),Opening) += BishopDiagonalOpening;
}

Rooks

    Next are the rooks. Rooks PSTs are very simple, and are only based on the file.

Fruit Rybka
static const int RookFileOpening = 3;

...

for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += RookFile[square_file(sq)] *
RookFileOpening;
}
static const int RookFileOpening = 104;

...

for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += RookFile[square_file(sq)] *
RookFileOpening;
}

Queens

    Next are the queens. Queens are based on the center bonus (weighting rank and file equally), with an additional correction for being on the back rank.

Fruit Rybka
static const int QueenCentreOpening = 0;
static const int QueenCentreEndgame = 4;
static const int QueenBackRankOpening = 5;

...

for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += QueenLine[square_file(sq)] *
QueenCentreOpening;
P(piece,sq,Opening) += QueenLine[square_rank(sq)] *
QueenCentreOpening;
P(piece,sq,Endgame) += QueenLine[square_file(sq)] *
QueenCentreEndgame;
P(piece,sq,Endgame) += QueenLine[square_rank(sq)] *
QueenCentreEndgame;
}
for (sq = A1; sq <= H1; sq++) {
P(piece,sq,Opening) -= QueenBackRankOpening;
}
static const int QueenCentreOpening = 98;
static const int QueenCentreEndgame = 108;
static const int QueenBackRankOpening = 201;

...

for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += QueenLine[square_file(sq)] *
QueenCentreOpening;
P(piece,sq,Opening) += QueenLine[square_rank(sq)] *
QueenCentreOpening;
P(piece,sq,Endgame) += QueenLine[square_file(sq)] *
QueenCentreEndgame;
P(piece,sq,Endgame) += QueenLine[square_rank(sq)] *
QueenCentreEndgame;
}
for (sq = A1; sq <= H1; sq++) {
P(piece,sq,Opening) -= QueenBackRankOpening;
}

Kings

    Lastly, we evaluate the king. In the opening, we have bonuses for the rank and file, and in the endgame, there is simply a center bonus.

Fruit Rybka
static const int KingCentreEndgame = 12;
static const int KingFileOpening = 10;
static const int KingRankOpening = 10;

...

for (sq = 0; sq < 64; sq++) {
P(piece,sq,Endgame) += KingLine[square_file(sq)] *
KingCentreEndgame;
P(piece,sq,Endgame) += KingLine[square_rank(sq)] *
KingCentreEndgame;
}
for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += KingFile[square_file(sq)] *
KingFileOpening;
}
for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += KingRank[square_rank(sq)] *
KingRankOpening;
}
static const int KingCentreEndgame = 401;
static const int KingFileOpening = 469;
static const int KingRankOpening = 0;

...

for (sq = 0; sq < 64; sq++) {
P(piece,sq,Endgame) += KingLine[square_file(sq)] *
KingCentreEndgame;
P(piece,sq,Endgame) += KingLine[square_rank(sq)] *
KingCentreEndgame;
}
for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += KingFile[square_file(sq)] *
KingFileOpening;
}
for (sq = 0; sq < 64; sq++) {
P(piece,sq,Opening) += KingRank[square_rank(sq)] *
KingRankOpening;
}

Conclusion

    We have found that, looking at the PST values of Fruit and Rybka, that Rybka's PSTs can be calculated using Fruit's code with a minimum of changes. The only differences are the various weights (the constants found near the top of pst.cpp in Fruit) and the bonuses for center pawns. Because of Fruit's unique PST initialization code, the origin of Rybka's PSTs in Fruit is clear.