After I implemented a basic version of search into my chess engine, the final part is to let the engine evaluate positions.
There are many different factors that one can use for the evaluation function, but I decided to keep it very simple for now. My first priority is to get the engine done and I can always tweak the evaluation later.
Piece Values
I mainly used the values from this article from the chessprogramming wiki. I found the discussion about the relationships between the values of different pieces very interesting. For example, it said that a knight and bishop should be valued a bit higher than 3 pawns, since otherwise the engine will often give up a piece for 3 pawns when it's unjustified.
I used the same values as in the article, namely
Pawn = 100
Knight = 320
Bishop = 330
Rook = 500
Queen = 900
Piece-Square Tables
The value of a piece also depends a lot on its position, so most programs use piece-square tables. These are just values that are added or subtracted from the original value of the piece, depending on the square it's standing on. Here is the piece-square table for the knight:
As you can see, knights in the centre receive a boost to their value while knights on the rim lose some of their value.
I found these piece-square tables very interesting, since they effectively encode positional information without any explicit rules. Looking back at the knight table, one can see that knights with more legal move are considered to be worth more, without ever stating that rule.
I wrote a function to count the material on the board by adding the offsets in the piece-square tables for each piece to the basic value of the piece.
The evaluation function is (for now) just the material. Of course, one also has to tell the engine that delivering checkmate is good and that draws are evaluated as 0. Here is the current version of the evaluation function:
There are many more factors that could be added into the evaluation, like pawn structure or piece activity. Also the values can be tweaked to increase the strength of the engine. But as I mentioned in the beginning, I'll leave all this for later and focus on finishing a basic version of the engine first.
First Game
Now that all basic elements of the engine are in place, I thought that I should play against it for the first time.
Trying to play the game also helped to fix some bugs and add some features that I skipped earlier on. For example, until now I haven't specified what checkmate is, which is somewhat important knowledge when one wants to play chess.
The game was played at a constant depth of 5 (the speed of the engine is still an issue), I played with Black and I didn't quite know what to expect. I ended up being positively surprised by the game. You can find it here or watch it below.
I was especially impressed by the opening and the overall play was more coherent than I had expected. Many of the queen moves are a bit puzzling, but I think that the issue is the horizon effect. At the end of a line, the engine sees that it can take a pawn or piece with the queen, without being able to calculate further to see that the piece was actually defended. In such cases, the search has to be extended until a quiet position is reached.
Next Steps
As mentioned before, the performance of the move generation is still a big handicap for the engine. So my next goal is to improve the performance so that the engine can play at a somewhat reasonable depth.
After that, I'll try to mitigate the horizon effect by implementing quiescence search. This is a smaller search with the goal to only evaluate quite positions at the end of lines. So when a piece is hanging at the end of a line, it would continue the search until a quite position is reached.
When all this is done, I'll probably look into implementing the UCI protocol to make the engine usable with chess GUIs.
As always, you can check out the code for the engine if you're interested.