Newbie chess program - Hansel

Discussion of chess software programming and technical issues.

Moderator: Ras

okidoki
Posts: 17
Joined: Thu Jun 30, 2022 12:46 pm
Full name: Kabir Kumar

Re: Newbie chess program - Hansel

Post by okidoki »

Most of the times Hansel beats TSCP (out of 37 games Hansel scored 30.5 and TSCP 6.5)
Here is a loss by Hansel which stumped me.
Hansel has Q-Search and the following evaluation features:
1. Material weights
2. Piece square table weights
3. Penalty for double pawns and Isolated pawns.
4. Bonus for passed pawns.
5. Attackers near king's ring (x-rays passing through rooks and queens)
6. Tapered evaluation (stockfish's ideas and almost the code),
Just to try out, following is being used. Please note this is an older version

Code: Select all

 while (bbWhiteQueenBlack > 0)
            {
                int sqFrom = Utilities.bitscan_forward(bbWhiteQueenBlack);

                ulong occupancyQueenBishop = ~Defs.BB_EMPTY;
                occupancyQueenBishop &= MagicBitBoards.BISHOP_ATTACK_INNER_MASK[sqFrom];
                occupancyQueenBishop *= MagicBitBoards.BishopMagic[sqFrom];
                occupancyQueenBishop >>= 64 - MagicBitBoards.BitForBishopAttack[sqFrom];
                ulong movesQueenAttackingKingRing = (MagicBitBoards.BishopAttacks[sqFrom, occupancyQueenBishop] & occupancyPiecesBlack) & kingRingWhite;

                queenAttackersBlack += Utilities.count_bits(movesQueenAttackingKingRing);

                ulong occupancyQueenRook = ~Defs.BB_EMPTY;
                occupancyQueenRook &= MagicBitBoards.ROOKS_ATTACK_INNER_MASK[sqFrom];
                occupancyQueenRook *= MagicBitBoards.RookMagic[sqFrom];
                occupancyQueenRook >>= 64 - MagicBitBoards.BitForRookAttack[sqFrom];
                ulong movesQueenRookAttackingKingRing = (MagicBitBoards.RookAttacks[sqFrom, occupancyQueenRook] & occupancyPiecesBlack) & kingRingWhite;

                queenAttackersBlack += Utilities.count_bits(movesQueenRookAttackingKingRing);

                bbWhiteQueenBlack &= bbWhiteQueenBlack - 1;

            }

            // Compute the difference.

            int whiteAttackCount = 2 * queenAttackers + 3 * rookAttackers + 4 * bishopAttackers;
            int blackAttackCount = 2 * queenAttackersBlack + 3 * rookAttackersBlack + 4 * bishopAttackersBlack;
            


What stumped me was it was seeing the position as +3.. and from there on it went to lose. From the game, it was quiet evident to play 38. Bc4 and almost after few captures, White gets a passed d-pawn and is winning.
Can somebody explain which feature I need to correct / add to avoid this loss.
TheBlackPlague
Posts: 1
Joined: Thu Jun 09, 2022 12:49 am
Full name: Shaheryar Sohail

Re: Newbie chess program - Hansel

Post by TheBlackPlague »

Hello.

I'm glad you're interested in writing a chess engine in C#. Like you, very recently, I started writing my own chess engine in C#: StockNemo.

StockNemo has been majorly optimized and uses some of the best techniques available. For move generation, StockNemo has pre-generated tables wherever possible including Black Magic Fixed-Shift Tables for Sliding Pieces (Rook, Bishop, Queen). The idea is to move as much processing to compile or startup time to make runtime as fast as possible. This is a continued effort in regards to StockNemo, as I firmly believe it can be even faster. However, with move generation down to 8ns per piece on average, it is already reasonably fast.

For PERFT, to give StockNemo an insane advantage over other PERFT generators and engines, it has a multi-threaded PERFT algorithm. This allows StockNemo to hit higher depths than many other PERFT generators or engines. The algorithm has been optimized to use threads efficiently (ex. not wasting threads on lower depths which can be done faster rather than copying data for parallelization) as well as optimized for stack utilization.

Furthermore, if one wants to hit even higher depths, StockNemo features a Transposition Table designed specifically for PERFT. This allows for it to skip having to recompute PERFT at certain positions and depths.

Combining parallelization and transposition table, StockNemo has PERFT speeds as high as 168.3B nps. It goes to show C# code can be fast.

GitHub: https://github.com/TheBlackPlague/StockNemo
User avatar
hgm
Posts: 28353
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Newbie chess program - Hansel

Post by hgm »

okidoki wrote: Thu Jul 07, 2022 4:21 pm What stumped me was it was seeing the position as +3.. and from there on it went to lose. From the game, it was quiet evident to play 38. Bc4 and almost after few captures, White gets a passed d-pawn and is winning.
Can somebody explain which feature I need to correct / add to avoid this loss.
Well, look at the PV after 39. Rxg6. This PV ends with a3-a2, and that is the position where the evaluation thinks that white has a +3.6 advantage. While in fact it cannot stop the promotion of the Pawn on a2. At the very least it should have evaluated that Pawn as nearly the same value as a minor (because 7th-rank Pawns are always passers, so that you need to dedicate one of your pieces to prevent their promotion). I am not sure if that would have been enough to dissuade the engine from trading the Rooks, though; capturing two Pawns might still seem attractive compared to the rise in value of the passer.

Of course you would have seen that if you had only searched 1 ply deeper, assuming you search promotions in QS. Some engines give an extension when the opponent has a Pawn on the 7th rank (or when a new Pawn reaches the 7th rank), so that you can be sure it cannot ignore the promotion.

But of course you might not search deep enough even with this extension to see the danger. The best solution would indeed be to have an evaluation term that recognizes the danger of an 'unstoppable passer'. Such passers are very common in Pawn endings, and relatively easy to recognize there. Humans use the 'rule of the square' for this, to see whether the King can reach the promotion square in time. That way the evaluation can already take into account that the Pawn is worth about a Queen. Here the situation is a bit more complex, since there still are Bishops. But after Kxc6 the a-pawn has indeed become 'unstoppable'.

Evaluating a highly advance passer is tricky. It usually either becomes a Queen (+8) or you will lose it (-1). That makes a very large difference. Evaluating it in between, like +4, will be very far off in all cases. The situation after Kxc6 is suspect, though, because white only has a Bishop. Which is on the light shade, while there is only a single light square on the path of the Pawn. The King is outside the area where it could prevent promotion. (Even if it were inside, on a crowded board, it might not be able to prevent the promotion because it is blocked and has to take a detour.) If the only other piece was a Knight, it can stop the Pawn on any shade, but, like the King, it can easily be too far away to get there in time.

In this case the Bishop can actually attack a2 in two moves, c3 + Bb1. Problem is that after c3 black plays Nd2, and then there is no square left for the Bishop to attack a2 where it is safe from the Knight. (b3 is also no good.) So there is a lot of relevant tactics here, and you cannot hope that a static evaluation would be able to find out how that ends. You really need the accuracy of search. So the logical solution is to award an extension when in doubt. You assume that a passer outside the reach of the enemy King will be able to promote when there is only a single other piece (by evaluating as +6 or so) that does not block or attack the path to promotion, and give an extension of nearly 1 ply to award extra depth in this game-deciding branch of the tree. This is pretty much what a human would do. As long as you have not determined with certainty whether that passer will promote or not, the fate of all other move sequences is completely irrelevant (unless there would be a checkmate there).
okidoki
Posts: 17
Joined: Thu Jun 30, 2022 12:46 pm
Full name: Kabir Kumar

Re: Newbie chess program - Hansel

Post by okidoki »

hgm wrote: Thu Jul 07, 2022 5:34 pm
okidoki wrote: Thu Jul 07, 2022 4:21 pm What stumped me was it was seeing the position as +3.. and from there on it went to lose. From the game, it was quiet evident to play 38. Bc4 and almost after few captures, White gets a passed d-pawn and is winning.
Can somebody explain which feature I need to correct / add to avoid this loss.
Well, look at the PV after 39. Rxg6. This PV ends with a3-a2, and that is the position where the evaluation thinks that white has a +3.6 advantage. While in fact it cannot stop the promotion of the Pawn on a2. At the very least it should have evaluated that Pawn as nearly the same value as a minor (because 7th-rank Pawns are always passers, so that you need to dedicate one of your pieces to prevent their promotion). I am not sure if that would have been enough to dissuade the engine from trading the Rooks, though; capturing two Pawns might still seem attractive compared to the rise in value of the passer.

Of course you would have seen that if you had only searched 1 ply deeper, assuming you search promotions in QS. Some engines give an extension when the opponent has a Pawn on the 7th rank (or when a new Pawn reaches the 7th rank), so that you can be sure it cannot ignore the promotion.

But of course you might not search deep enough even with this extension to see the danger. The best solution would indeed be to have an evaluation term that recognizes the danger of an 'unstoppable passer'. Such passers are very common in Pawn endings, and relatively easy to recognize there. Humans use the 'rule of the square' for this, to see whether the King can reach the promotion square in time. That way the evaluation can already take into account that the Pawn is worth about a Queen. Here the situation is a bit more complex, since there still are Bishops. But after Kxc6 the a-pawn has indeed become 'unstoppable'.

Evaluating a highly advance passer is tricky. It usually either becomes a Queen (+8) or you will lose it (-1). That makes a very large difference. Evaluating it in between, like +4, will be very far off in all cases. The situation after Kxc6 is suspect, though, because white only has a Bishop. Which is on the light shade, while there is only a single light square on the path of the Pawn. The King is outside the area where it could prevent promotion. (Even if it were inside, on a crowded board, it might not be able to prevent the promotion because it is blocked and has to take a detour.) If the only other piece was a Knight, it can stop the Pawn on any shade, but, like the King, it can easily be too far away to get there in time.

In this case the Bishop can actually attack a2 in two moves, c3 + Bb1. Problem is that after c3 black plays Nd2, and then there is no square left for the Bishop to attack a2 where it is safe from the Knight. (b3 is also no good.) So there is a lot of relevant tactics here, and you cannot hope that a static evaluation would be able to find out how that ends. You really need the accuracy of search. So the logical solution is to award an extension when in doubt. You assume that a passer outside the reach of the enemy King will be able to promote when there is only a single other piece (by evaluating as +6 or so) that does not block or attack the path to promotion, and give an extension of nearly 1 ply to award extra depth in this game-deciding branch of the tree. This is pretty much what a human would do. As long as you have not determined with certainty whether that passer will promote or not, the fate of all other move sequences is completely irrelevant (unless there would be a checkmate there).
Thanks for explaining me this. It seemed intuitive although by your reply I agree that search should do the work here smartly by having evaluation as its guidelines.
TheBlackPlague wrote: Thu Jul 07, 2022 4:29 pm Hello.

I'm glad you're interested in writing a chess engine in C#. Like you, very recently, I started writing my own chess engine in C#: StockNemo.

StockNemo has been majorly optimized and uses some of the best techniques available. For move generation, StockNemo has pre-generated tables wherever possible including Black Magic Fixed-Shift Tables for Sliding Pieces (Rook, Bishop, Queen). The idea is to move as much processing to compile or startup time to make runtime as fast as possible. This is a continued effort in regards to StockNemo, as I firmly believe it can be even faster. However, with move generation down to 8ns per piece on average, it is already reasonably fast.

For PERFT, to give StockNemo an insane advantage over other PERFT generators and engines, it has a multi-threaded PERFT algorithm. This allows StockNemo to hit higher depths than many other PERFT generators or engines. The algorithm has been optimized to use threads efficiently (ex. not wasting threads on lower depths which can be done faster rather than copying data for parallelization) as well as optimized for stack utilization.

Furthermore, if one wants to hit even higher depths, StockNemo features a Transposition Table designed specifically for PERFT. This allows for it to skip having to recompute PERFT at certain positions and depths.

Combining parallelization and transposition table, StockNemo has PERFT speeds as high as 168.3B nps. It goes to show C# code can be fast.

GitHub: https://github.com/TheBlackPlague/StockNemo
I saw your code, and it seems it is written very nicely. However due to my lack of understanding of C#, it is difficult for me to assimilate.
Like your code, I changed mine to utilize structs and make the move list to be instance based and expected speed gain, however this is not the case.
I would like to understand why is mine slower (single threaded without TT) than yours. If it is not too much of hassle for you, can you go through my code and provide a feedback if it is not too much to ask.

Here is the github link for my code : https://github.com/frankcastle64/Hansel ... StaticImpl

Thank you.
okidoki
Posts: 17
Joined: Thu Jun 30, 2022 12:46 pm
Full name: Kabir Kumar

Re: Newbie chess program - Hansel

Post by okidoki »

I have rewritten the program since I ran into many difficulties in my first attempt.

Now my program has the following features.
1. Negamax with alpha-beta pruning. (no internal iterative, no null move, no LMR, no TT).
2. QSearch expanding captures only.
3. Move ordering via MVV/LVA.
4. Evaluation using material values and PSQT. (This is taken from Rofchade version of TSCP)
5. Evaluation uses tapered eval.

Few more details, it on an average gets to 1.5 Mnps+ nodes for processing and reaches depth of 6 in less than 1 sec and 7 in about 3secs.
My aim here is to have a fast search and light eval, allowing engine to make its own decision guided by tapered eval and mobility.

Here are couple of the losses it had with TSCP. :cry: Please feel free to analyse them and if possible share some insights where it can be improved.

Please let me know if this is expected or if in your experience the moves played are buggy.
Thank you :)