Material knowledge is not the only chess knowledge that can be instilled using eval.
Following aspects of chess knowledge are coded through eval in TSCP (refer eval.c):
1. Material:
The material values of pieces are coded as lookup arrays
/* the values of the pieces */
int piece_value[6] = {
100, 300, 300, 500, 900, 0
};
2. Piece Square Tables:
Positional knowledge is provided in eval using piece - square tables.
Piece-square tables (pst) define the value of a piece due to its location on the chessboard.
e.g. its a well known saying that "a knight on the rim is grim". Hence we would want our knights to be in centre as much as possible.
In source code they are represented by the array "<piece>_pcsq[64]"
There is also a pst for pawns because usually pawns should capture towards center and the value of a pawn increases as it moves up the board.
There are 2 pst's for king because the evaluation of king depends upon the stage of the game.
A chess game is said to occur in 3 stages, opening, middlegame and endgame.
The evaluation of specially the king changes completely in different stages.
A king is a fighting piece in an endgame while needs to be carefully defended in the middlegame.
Also note that there are no pst's for rook and queen.
This is because, a queen is so powerful that it can reach any square from any square in a very short span of time.
Queen evaluation is more so regulated by the position of other pieces on board rather than centralisation.
Same goes for rooks because usually its the pawn structure that decides upon the effectiveness of a rook (rook on open file) rather than centralisation.There is also an array called as int flip[64].
Basically what this does is that all the pst's are defined as per the White side.
Rather than defining more arrays for black side pst's, just this 1 array 'flip' is used to do so.
It is always good to reduce load on CPU cache by decreasing the number of different arrays to be loaded in CPU memory.
3. Pawn Structure:
Pawn structure often determines gameplay and plans.
As great chess master Philidor once said "pawns are the soul of chess"
Hence code to identify doubled pawns, backward pawns, isolated pawns, passed pawns etc is provided and it is penalised or rewarded accordingly.
In TSCP, to determine pawn structure separate array is used.
int pawn_rank[2][10];
It is already commented that this array contains the rank of the least advanced pawn of color x on file y - 1.
E.g. For White if there are 2 pawns on b file on b3 and b4 and no pawn on the c-file and the a-file, then,
pawn_rank[0][2] will be equal to 5 {ROW (b3)} and pawn_rank[0][3] will be equal to 0 and pawn_rank[0][1] will be equal to 0.
Then when the function int eval_light_pawn(int sq) is called to evaluate pawn on b4 {ROW(b4) = 4},
it will be detected as a doubled pawn.
/* if there's a pawn behind this one, it's doubled */
if (pawn_rank[LIGHT][f] > ROW(sq))
r -= DOUBLED_PAWN_PENALTY;
Also, it will be detected as an isolated pawn
/* if there aren't any friendly pawns on either side of
this one, it's isolated */
if ((pawn_rank[LIGHT][f - 1] == 0) &&
(pawn_rank[LIGHT][f + 1] == 0))
r -= ISOLATED_PAWN_PENALTY;
Note that, the author puts the blocks to check for isolated pawns or backward pawns in an if-else block.
First he checks for isolated and only if it is not isolated, it is checked for backwardness.
Later, it is checked if the pawn is passed or not which depends upon the position of Black pawns.
Also, the author mentions that he has kept the pawn_rank array to contain of 10 files rather than 8. This is because of code to check for isolated pawn or backward pawn etc.
Without this, he would always have to check that if the pawn is on a rook file then check only for 1 side.
4. Rook Position:
The rook's positional value is greatly determined by the pawn structure and code is provided to identify rook on open file, semi open file, rook on 7th rank etc and it is rewarded accordingly.
5. King Safety:
Some code for king safety is provided. This basically involves evaluating pawn structures in front of the king.
King safety of White depends upon the number of pieces available with black.
Also only pieces and not pawns.
Hence, 2 separate arrays are used to store pieces and pawns.
int piece_mat[2]; /* the value of a side's pieces */
int pawn_mat[2]; /* the value of a side's pawns */
Also king safety is scaled as per the pieces the opposite side possesses.
/* scale the king safety value according to the opponent's material;
the premise is that your king safety can only be bad if the
opponent has enough pieces to attack you */
r *= piece_mat[DARK];
Also, if king is castled then its pawn shield is evaluated, else it is just checked whether there are any open files near him.
Pawn shield for castled king is evaluated using int eval_lkp(int f).
It basically checks whether the pawns in front of castled king have moved, status of opponent's pawns and semi open file for opponent.
6. Dependency on Game stage:
A chess game is said to occur in 3 stages, opening, middlegame and endgame.
Thus some code is required to determine the "stage" of the game before the correct eval value can be provided.
This is determined by the piece values of the opposite side.
Hence, in TSCP; the 64 squared board is looped over twice.
First loop to determine material (for stage) and pawn placements.
Second loop is used determine the fine tuned parameters and other stage dependant parameters.
Final comments:
I have finished eval in 1 page and given more pages for Board Rep. This is because eval values are usually determined by continuous testing which is quite boring compared to other activities of chess programming as a hobby.
You need more chess knowledge to intuitively add eval parameters which might affect the strength of the engine.
I have added code with eval function.
Feel free to create your own print eval function to give break-up of different eval parameters