Being a little tired of finding bugs in Weini, I started a week-end project last sunday. The idea was the following : "starting from a blank page, how far can I go in 2 days now I know a little more on chess programming".
An expected side effect was to get a simple engine to experiment with and maybe find some Weini's bug in it.
It is a mailbox negamax (pvs) engine, with classic pruning trick, a double bucket TT and only PST eval.
After 2 days the engine was working in xboard, validated in perft, finds mate in 8 from a collection I often use and was able to always win against me at fast TC (but loses against fairy-max).
Since then I corrected some stuff but the engine is still quite bad, but working
Here's the source code : https://github.com/tryingsomestuff/Mini ... r/minic.cc
Some of you probably can easily spot very bad things that makes it so weak.
Maybe, as a 2000 lines c++ code, it may also become an entry point for beginners. I can open the repository for write if some of you want to contribute.
A complete 2000 lines of code engine
Moderators: hgm, Rebel, chrisw
-
- Posts: 1871
- Joined: Sat Nov 25, 2017 2:28 pm
- Location: France
-
- Posts: 4052
- Joined: Thu May 15, 2008 9:57 pm
- Location: Berlin, Germany
- Full name: Sven Schüle
Re: A complete 2000 lines of code engine
The middlegame PST for kings seems to be wrong, it seems to favor moving the king to higher ranks instead of discouraging that.
I do not understand your MvvLvaScores[][] array. First thing is, it has a lot of redundancy by being a 13x13 array while you only need 6x6 (or 7x7). You never have captures of friendly pieces, and White takes Black can be handled with the same data as Black takes White if you use abs(). Next point, MVV/LVA means "most valuable victim/least valuable aggressor" and is usually implemented differently: all moves capturing a piece of type X should be scored higher than all captures of pieces with a lower value than X, and for two captures of the same piece type the move with the lower-valued moving piece should come first. A possible formula would be:
mvvLvaScore(move) = 6 * type_of_captured_piece(move) - type_of_moving_piece(move)
if the piece type is in the range [1 .. 6]. The factor "6" can of course be any number >= 6.
What about the raw speed of this engine? For instance, your move ordering implementation appears to be quite slow since you calculate the ordering scores for each moves within the comparison operator so you calculate it many times for the same move. What about making the ordering score a member of the move and calculating it only once?
There should be a better way (and maybe even one that is more "C++") than always accessing information related to pieces like this throughout the whole code:
where PieceShift is 6 and board[square] is the piece type in the range { -6 .. +6 }. I could imagine some accessor methods of the position class, for instance:
I wonder why you set the hash move as killer move, I can imagine a negative impact on the quality of your move ordering.
I also wonder why you check for "king capture" within the move loop even though you also have an explicit legality check when making a move so that you can never reach a position where the enemy king can be captured. Furthermore, when ignoring that redundancy, the correct score for a position where the king can be captured should be higher than MATE-ply in my opinion since a "mated" position where all pseudo-legal moves lead to capturing the king would already have occurred one ply earlier.
If you would like to know why this engine is so weak I would suggest to disable all pruning code and test that against the original version. Maybe the problem is somewhere within the pruning?
I do not understand your MvvLvaScores[][] array. First thing is, it has a lot of redundancy by being a 13x13 array while you only need 6x6 (or 7x7). You never have captures of friendly pieces, and White takes Black can be handled with the same data as Black takes White if you use abs(). Next point, MVV/LVA means "most valuable victim/least valuable aggressor" and is usually implemented differently: all moves capturing a piece of type X should be scored higher than all captures of pieces with a lower value than X, and for two captures of the same piece type the move with the lower-valued moving piece should come first. A possible formula would be:
mvvLvaScore(move) = 6 * type_of_captured_piece(move) - type_of_moving_piece(move)
if the piece type is in the range [1 .. 6]. The factor "6" can of course be any number >= 6.
What about the raw speed of this engine? For instance, your move ordering implementation appears to be quite slow since you calculate the ordering scores for each moves within the comparison operator so you calculate it many times for the same move. What about making the ordering score a member of the move and calculating it only once?
There should be a better way (and maybe even one that is more "C++") than always accessing information related to pieces like this throughout the whole code:
Code: Select all
SOME_ARRAY[position.board[square] + PieceShift]
Code: Select all
unsigned int Position::pieceIndex(Sq s) const { return board[s] + PieceShift; } // returns a number in [0 .. 12]
Piece Position::pieceType(Sq s) const { return std::abs(board[s]); } // returns a number in [0 .. 6]
Color Position::color(Sq s) const { return Colors[pieceIndex(s)]; }
I also wonder why you check for "king capture" within the move loop even though you also have an explicit legality check when making a move so that you can never reach a position where the enemy king can be captured. Furthermore, when ignoring that redundancy, the correct score for a position where the king can be captured should be higher than MATE-ply in my opinion since a "mated" position where all pseudo-legal moves lead to capturing the king would already have occurred one ply earlier.
If you would like to know why this engine is so weak I would suggest to disable all pruning code and test that against the original version. Maybe the problem is somewhere within the pruning?
Not sure whether a 2-days work is always suitable for that purpose Once you know there are no serious bugs and it has an acceptable strength then go for it, otherwise I suggest first to fix the code before proposing it to beginners.Maybe, as a 2000 lines c++ code, it may also become an entry point for beginners.
Sven Schüle (engine author: Jumbo, KnockOut, Surprise)
-
- Posts: 4052
- Joined: Thu May 15, 2008 9:57 pm
- Location: Berlin, Germany
- Full name: Sven Schüle
Re: A complete 2000 lines of code engine
I may be wrong here, I just saw the PST access is reversed by this code in eval()
Code: Select all
const int s = Signs[p.b[k]+PieceShift];
Square kk = k;
if ( s > 0 ) kk = 63-k;
Sven Schüle (engine author: Jumbo, KnockOut, Surprise)
-
- Posts: 1871
- Joined: Sat Nov 25, 2017 2:28 pm
- Location: France
Re: A complete 2000 lines of code engine
Thanks for the comment. I Will simplify mvvlva table and piece type accessor. move score is already backuped.
-
- Posts: 919
- Joined: Tue Nov 24, 2015 9:11 pm
- Location: upstate
Re: A complete 2000 lines of code engine
A neat little engine to play with indeed. It lost rather quickly to the 1600-Elo rated Monochrome and obtained a big advantage vs. 1060-rated Soberango, only to go unexpectedly for a threefold repetition.
[pgn][Event "?"] [Site "?"] [Date "2018.10.20"] [Round "?"] [White "Minic 0.1"] [Black "Soberango 0.12.0"] [Result "1/2-1/2"] [ECO "B18"] [GameDuration "00:01:27"] [GameEndTime "2018-10-20T12:49:35.059 Eastern Daylight Time"] [GameStartTime "2018-10-20T12:48:07.098 Eastern Daylight Time"] [Opening "Caro-Kann"] [PlyCount "44"] [TimeControl "40/120"] [Variation "Classical, Flohr Variation"] 1. e4 {book} c6 {book} 2. d4 {book} d5 {book} 3. Nc3 {book} dxe4 {book} 4. Nxe4 {book} Bf5 {book} 5. Ng3 {book} Bg6 {book} 6. Nh3 {book} e6 {book} 7. Nf4 {+0.39/9 2.9s} c5 {0.00/6 2.2s} 8. dxc5 {+1.15/8 2.9s} Qxd1+ {0.00/7 1.8s} 9. Kxd1 {+0.48/11 2.9s} Bxc5 {0.00/7 2.6s} 10. Nxg6 {+0.67/10 2.8s} fxg6 {0.00/6 3.6s} 11. Ne4 {+0.98/11 2.9s} b6 {0.00/6 1.4s} 12. Bc4 {+1.50/9 2.9s} Ke7 {-1.02/6 3.7s} 13. Nxc5 {+2.05/10 2.9s} bxc5 {-1.02/6 3.7s} 14. Re1 {+1.87/11 2.9s} Nf6 {-1.02/6 3.7s} 15. Rxe6+ {+2.10/9 2.9s} Kd7 {-1.03/6 2.3s} 16. Bf4 {+2.61/9 2.9s} Kc8 {-1.03/5 3.8s} 17. Kc1 {+2.71/8 2.9s} Kd7 {-1.03/5 1.6s} 18. c3 {+2.95/8 2.9s} Rc8 {-1.03/5 3.9s} 19. Rd6+ {+2.84/8 2.9s} Ke7 {-1.03/6 1.4s} 20. Re6+ {+2.94/8 2.9s} Kd7 {-1.03/5 4.0s} 21. Rd6+ {+2.84/8 2.9s} Ke7 {-1.03/6 2.7s} 22. Re6+ {+2.80/8 2.9s} Kd7 {0.00/2 0s, Draw by 3-fold repetition} 1/2-1/2 [/pgn]
Here are MinGW builds (32- and 64-bit) of today's code:
minic-01-TP.zip
Note that the execs require -xboard on the command line to enable the Winboard/Xboard mode.
[pgn][Event "?"] [Site "?"] [Date "2018.10.20"] [Round "?"] [White "Minic 0.1"] [Black "Soberango 0.12.0"] [Result "1/2-1/2"] [ECO "B18"] [GameDuration "00:01:27"] [GameEndTime "2018-10-20T12:49:35.059 Eastern Daylight Time"] [GameStartTime "2018-10-20T12:48:07.098 Eastern Daylight Time"] [Opening "Caro-Kann"] [PlyCount "44"] [TimeControl "40/120"] [Variation "Classical, Flohr Variation"] 1. e4 {book} c6 {book} 2. d4 {book} d5 {book} 3. Nc3 {book} dxe4 {book} 4. Nxe4 {book} Bf5 {book} 5. Ng3 {book} Bg6 {book} 6. Nh3 {book} e6 {book} 7. Nf4 {+0.39/9 2.9s} c5 {0.00/6 2.2s} 8. dxc5 {+1.15/8 2.9s} Qxd1+ {0.00/7 1.8s} 9. Kxd1 {+0.48/11 2.9s} Bxc5 {0.00/7 2.6s} 10. Nxg6 {+0.67/10 2.8s} fxg6 {0.00/6 3.6s} 11. Ne4 {+0.98/11 2.9s} b6 {0.00/6 1.4s} 12. Bc4 {+1.50/9 2.9s} Ke7 {-1.02/6 3.7s} 13. Nxc5 {+2.05/10 2.9s} bxc5 {-1.02/6 3.7s} 14. Re1 {+1.87/11 2.9s} Nf6 {-1.02/6 3.7s} 15. Rxe6+ {+2.10/9 2.9s} Kd7 {-1.03/6 2.3s} 16. Bf4 {+2.61/9 2.9s} Kc8 {-1.03/5 3.8s} 17. Kc1 {+2.71/8 2.9s} Kd7 {-1.03/5 1.6s} 18. c3 {+2.95/8 2.9s} Rc8 {-1.03/5 3.9s} 19. Rd6+ {+2.84/8 2.9s} Ke7 {-1.03/6 1.4s} 20. Re6+ {+2.94/8 2.9s} Kd7 {-1.03/5 4.0s} 21. Rd6+ {+2.84/8 2.9s} Ke7 {-1.03/6 2.7s} 22. Re6+ {+2.80/8 2.9s} Kd7 {0.00/2 0s, Draw by 3-fold repetition} 1/2-1/2 [/pgn]
Here are MinGW builds (32- and 64-bit) of today's code:
minic-01-TP.zip
Note that the execs require -xboard on the command line to enable the Winboard/Xboard mode.
Tirsa Poppins
CCRL
CCRL
-
- Posts: 919
- Joined: Tue Nov 24, 2015 9:11 pm
- Location: upstate
Re: A complete 2000 lines of code engine
Minic can win a KRK endgame. Amazing! I can name at least a dozen engines many hundreds Elo higher that can only draw.
[pgn][Event "?"] [Site "?"] [Date "2018.10.20"] [Round "?"] [White "Minic 0.1 64-bit"] [Black "__Thinker"] [Result "1-0"] [FEN "7R/5K2/8/5k2/8/8/8/8 w - - 0 1"] [GameDuration "00:01:38"] [GameEndTime "2018-10-20T14:47:28.517 Eastern Daylight Time"] [GameStartTime "2018-10-20T14:45:49.816 Eastern Daylight Time"] [PlyCount "47"] [SetUp "1"] [TimeControl "40/120"] 1. Rh3 {+5.58/13 2.8s} Kg4 {9.4s} 2. Rh1 {+5.58/13 2.9s} Kf3 {0.94s} 3. Ke6 {+5.65/12 2.8s} Ke3 {1.4s} 4. Kd5 {+5.65/12 2.8s} Kf3 {0.72s} 5. Ke5 {+5.75/12 2.9s} Kg2 {3.3s} 6. Rc1 {+5.81/13 2.9s} Kf3 {2.1s} 7. Rc3+ {+5.95/12 2.9s} Ke2 {1.9s} 8. Ke4 {+5.89/12 2.8s} Kd2 {1.9s} 9. Rc4 {+5.95/12 2.9s} Ke2 {1.9s} 10. Rc2+ {+5.95/12 2.8s} Kd1 11. Rc6 {+5.95/14 2.9s} Kd2 {3.2s} 12. Rc4 {+5.95/13 2.9s} Ke2 13. Rc1 {+5.95/12 2.9s} Kd2 {3.3s} 14. Rc7 {+5.95/13 2.9s} Ke2 {0.75s} 15. Rc2+ {+5.95/12 2.9s} Kd1 16. Rh2 {+5.95/14 2.9s} Ke1 {1.4s} 17. Ra2 {+5.95/12 2.9s} Kd1 {0.53s} 18. Kd3 {+6.15/13 2.8s} Ke1 19. Re2+ {+5.98/11 2.8s} Kf1 20. Ke3 {+89.90/11 2.9s} Kg1 21. Kf3 {+89.92/11 2.9s} Kf1 22. Re5 {+89.94/17 2.9s} Kg1 23. Rh5 {+89.96/15 2.9s} Kf1 24. Rh1# {+89.98/64 0.001s, White mates} 1-0 [/pgn]
[pgn][Event "?"] [Site "?"] [Date "2018.10.20"] [Round "?"] [White "Minic 0.1 64-bit"] [Black "__Thinker"] [Result "1-0"] [FEN "7R/5K2/8/5k2/8/8/8/8 w - - 0 1"] [GameDuration "00:01:38"] [GameEndTime "2018-10-20T14:47:28.517 Eastern Daylight Time"] [GameStartTime "2018-10-20T14:45:49.816 Eastern Daylight Time"] [PlyCount "47"] [SetUp "1"] [TimeControl "40/120"] 1. Rh3 {+5.58/13 2.8s} Kg4 {9.4s} 2. Rh1 {+5.58/13 2.9s} Kf3 {0.94s} 3. Ke6 {+5.65/12 2.8s} Ke3 {1.4s} 4. Kd5 {+5.65/12 2.8s} Kf3 {0.72s} 5. Ke5 {+5.75/12 2.9s} Kg2 {3.3s} 6. Rc1 {+5.81/13 2.9s} Kf3 {2.1s} 7. Rc3+ {+5.95/12 2.9s} Ke2 {1.9s} 8. Ke4 {+5.89/12 2.8s} Kd2 {1.9s} 9. Rc4 {+5.95/12 2.9s} Ke2 {1.9s} 10. Rc2+ {+5.95/12 2.8s} Kd1 11. Rc6 {+5.95/14 2.9s} Kd2 {3.2s} 12. Rc4 {+5.95/13 2.9s} Ke2 13. Rc1 {+5.95/12 2.9s} Kd2 {3.3s} 14. Rc7 {+5.95/13 2.9s} Ke2 {0.75s} 15. Rc2+ {+5.95/12 2.9s} Kd1 16. Rh2 {+5.95/14 2.9s} Ke1 {1.4s} 17. Ra2 {+5.95/12 2.9s} Kd1 {0.53s} 18. Kd3 {+6.15/13 2.8s} Ke1 19. Re2+ {+5.98/11 2.8s} Kf1 20. Ke3 {+89.90/11 2.9s} Kg1 21. Kf3 {+89.92/11 2.9s} Kf1 22. Re5 {+89.94/17 2.9s} Kg1 23. Rh5 {+89.96/15 2.9s} Kf1 24. Rh1# {+89.98/64 0.001s, White mates} 1-0 [/pgn]
Tirsa Poppins
CCRL
CCRL
-
- Posts: 1871
- Joined: Sat Nov 25, 2017 2:28 pm
- Location: France
Re: A complete 2000 lines of code engine
Well using rofchade PST it can even win against fairy-max sometimes ... Thanks for testing it.
-
- Posts: 1871
- Joined: Sat Nov 25, 2017 2:28 pm
- Location: France
Re: A complete 2000 lines of code engine
TC : 20sec/40moves, 12 moves from book
Score of fairymax vs Minic: 54 - 14 - 32 [0.700]
Elo difference: 147.19 +/- 59.21
100 of 100 games finished.
Score of fairymax vs Minic: 54 - 14 - 32 [0.700]
Elo difference: 147.19 +/- 59.21
100 of 100 games finished.
-
- Posts: 1871
- Joined: Sat Nov 25, 2017 2:28 pm
- Location: France
Re: A complete 2000 lines of code engine
Version 0.4 is available and is less stupid than the previous ones ...
-
- Posts: 919
- Joined: Tue Nov 24, 2015 9:11 pm
- Location: upstate
Re: A complete 2000 lines of code engine
Tirsa Poppins
CCRL
CCRL