Help with simple approach to king safety

Discussion of chess software programming and technical issues.

Moderator: Ras

jmcd
Posts: 58
Joined: Wed Mar 18, 2020 10:00 pm
Full name: Jonathan McDermid

Help with simple approach to king safety

Post by jmcd »

So I often have big plans with things that I want to add to my engine, and I have a recurring issue of doing a bunch of work, coming up with a solution that seems correct, and then in practice it results in a loss in engine strength. I think my solutions are often very close to correct, but I fail to identify the final missing piece and end up deleting the whole thing.

Last time I was in a scenario like this, it was regarding methods to improve my TT replacement scheme, and I got a lot of helpful information on this forum that helped me make changes that resulted in improvements. This time I am looking to add king safety to my evaluation function. I have already gotten a decent strength gain from it, but I have a feeling there is a lot more to be gained while still keeping it simple.

I've looked at a few different ways of evaluating king safety, and went with the one that made the most sense to me. It involves creating bitboards for inner and outer king rings of each square, and then keeping track of how many threats by enemy pieces are made on those squares. The inner ring is the squares adjacent to the king square, and the outer ring is the squares directly outside of the inner ring. So in reality, the shapes of these really look like squares, and not rings.

For finding the attacked squares, I take all the attacked squares by enemy pieces, and treat queens as transparent for bishop attacks, and queens and rooks as transparent for rook attacks. I also leave out squares attacked by pawns. Whenever a piece is found to be attacking a square on either king ring, I increase a variable for the number of king attackers for that side, and increase the total weight for the number of attacked squares multiplied by a weight for the type of piece attacking.

these are the tunable parameters for king safety (middlegame, endgame)

Code: Select all

Score king_safety_reduction_factor = Score(6, 6);
Score inner_ring_attack[7] = { Score(0, 0), Score(0, 0), Score(3, 9), Score(7, 2), Score(6, 1), Score(5, 0), Score(0, 0), };
Score outer_ring_attack[7] = { Score(0, 0), Score(0, 0), Score(3, 1), Score(1, 3), Score(2, 0), Score(2, 1), Score(0, 0), };
this section occurs in the evaluation loop for each piece

Code: Select all

		if (pt > PAWN && pt < KING)
                    {
                        Bitboard attacks;
                        //queens are transparent for bishops
                        if (pt == BISHOP)
                            attacks = Bitboards::get_attacks(pos.occ_bitboard[BOTH] & ~(pos.piece_bitboard[W_QUEEN] | pos.piece_bitboard[B_QUEEN]), sq, BISHOP);
                         //queens and rooks are transparent for rooks
                        else if (pt == ROOK)
                            attacks = Bitboards::get_attacks(pos.occ_bitboard[BOTH] & ~(pos.piece_bitboard[W_QUEEN] | pos.piece_bitboard[B_QUEEN] | pos.piece_bitboard[W_ROOK] | pos.piece_bitboard[B_ROOK]), sq, ROOK);
                        else
                            attacks = Bitboards::get_attacks(pos.occ_bitboard[BOTH], sq, pt);
                         
                        // outer ring attacked squares bitboard
                        Bitboard or_att_bb = attacks & king_zones[king_sq[them]].outer_ring;
                        // inner ring attacked squares bitboard
                        Bitboard ir_att_bb = attacks & king_zones[king_sq[them]].inner_ring;

                        if (or_att_bb || ir_att_bb) {
                            ++n_king_attackers[us];
                            king_attack_weight[us] += inner_ring_attack[pt] * popcnt(ir_att_bb) + outer_ring_attack[pt] * popcnt(or_att_bb);
                        }
                    }
the final calculation
we only apply it if the opponent has a queen, and there are at least 2 attackers. these are also the conditions used by blunder.

Code: Select all

	    if (n_king_attackers[BLACK] >= 2 && pos.piece_bitboard[B_QUEEN]) 
                score[WHITE] -= (king_attack_weight[BLACK] * king_attack_weight[BLACK]) / (king_safety_reduction_factor + 1);
            
            if (n_king_attackers[WHITE] >= 2 && pos.piece_bitboard[W_QUEEN])
                score[BLACK] -= (king_attack_weight[WHITE] * king_attack_weight[WHITE]) / (king_safety_reduction_factor + 1);
One basic problem I see with this method is that the outer ring squares seem to be a little generous. 2 enemy sliders can very easily be threatening some of these squares when the king is in no real danger at all, and the penalty will be applied all the same. When I looked at stockfish evaluation, I saw that they use a different set of squares for their version of "king ring", but I'm not exactly sure what squares those are as they just describe them as squares that are close.

Are there any blaringly obvious problems with my implementation of king safety? Any obvious improvements to be made? I appreciate any thoughts.
Clovis GitHub
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: Help with simple approach to king safety

Post by Mike Sherwin »

jmcd wrote: Sat Nov 19, 2022 12:16 am So I often have big plans with things that I want to add to my engine, and I have a recurring issue of doing a bunch of work, coming up with a solution that seems correct, and then in practice it results in a loss in engine strength. I think my solutions are often very close to correct, but I fail to identify the final missing piece and end up deleting the whole thing.

Last time I was in a scenario like this, it was regarding methods to improve my TT replacement scheme, and I got a lot of helpful information on this forum that helped me make changes that resulted in improvements. This time I am looking to add king safety to my evaluation function. I have already gotten a decent strength gain from it, but I have a feeling there is a lot more to be gained while still keeping it simple.

I've looked at a few different ways of evaluating king safety, and went with the one that made the most sense to me. It involves creating bitboards for inner and outer king rings of each square, and then keeping track of how many threats by enemy pieces are made on those squares. The inner ring is the squares adjacent to the king square, and the outer ring is the squares directly outside of the inner ring. So in reality, the shapes of these really look like squares, and not rings.

For finding the attacked squares, I take all the attacked squares by enemy pieces, and treat queens as transparent for bishop attacks, and queens and rooks as transparent for rook attacks. I also leave out squares attacked by pawns. Whenever a piece is found to be attacking a square on either king ring, I increase a variable for the number of king attackers for that side, and increase the total weight for the number of attacked squares multiplied by a weight for the type of piece attacking.

these are the tunable parameters for king safety (middlegame, endgame)

Code: Select all

Score king_safety_reduction_factor = Score(6, 6);
Score inner_ring_attack[7] = { Score(0, 0), Score(0, 0), Score(3, 9), Score(7, 2), Score(6, 1), Score(5, 0), Score(0, 0), };
Score outer_ring_attack[7] = { Score(0, 0), Score(0, 0), Score(3, 1), Score(1, 3), Score(2, 0), Score(2, 1), Score(0, 0), };
this section occurs in the evaluation loop for each piece

Code: Select all

		if (pt > PAWN && pt < KING)
                    {
                        Bitboard attacks;
                        //queens are transparent for bishops
                        if (pt == BISHOP)
                            attacks = Bitboards::get_attacks(pos.occ_bitboard[BOTH] & ~(pos.piece_bitboard[W_QUEEN] | pos.piece_bitboard[B_QUEEN]), sq, BISHOP);
                         //queens and rooks are transparent for rooks
                        else if (pt == ROOK)
                            attacks = Bitboards::get_attacks(pos.occ_bitboard[BOTH] & ~(pos.piece_bitboard[W_QUEEN] | pos.piece_bitboard[B_QUEEN] | pos.piece_bitboard[W_ROOK] | pos.piece_bitboard[B_ROOK]), sq, ROOK);
                        else
                            attacks = Bitboards::get_attacks(pos.occ_bitboard[BOTH], sq, pt);
                         
                        // outer ring attacked squares bitboard
                        Bitboard or_att_bb = attacks & king_zones[king_sq[them]].outer_ring;
                        // inner ring attacked squares bitboard
                        Bitboard ir_att_bb = attacks & king_zones[king_sq[them]].inner_ring;

                        if (or_att_bb || ir_att_bb) {
                            ++n_king_attackers[us];
                            king_attack_weight[us] += inner_ring_attack[pt] * popcnt(ir_att_bb) + outer_ring_attack[pt] * popcnt(or_att_bb);
                        }
                    }
the final calculation
we only apply it if the opponent has a queen, and there are at least 2 attackers. these are also the conditions used by blunder.

Code: Select all

	    if (n_king_attackers[BLACK] >= 2 && pos.piece_bitboard[B_QUEEN]) 
                score[WHITE] -= (king_attack_weight[BLACK] * king_attack_weight[BLACK]) / (king_safety_reduction_factor + 1);
            
            if (n_king_attackers[WHITE] >= 2 && pos.piece_bitboard[W_QUEEN])
                score[BLACK] -= (king_attack_weight[WHITE] * king_attack_weight[WHITE]) / (king_safety_reduction_factor + 1);
One basic problem I see with this method is that the outer ring squares seem to be a little generous. 2 enemy sliders can very easily be threatening some of these squares when the king is in no real danger at all, and the penalty will be applied all the same. When I looked at stockfish evaluation, I saw that they use a different set of squares for their version of "king ring", but I'm not exactly sure what squares those are as they just describe them as squares that are close.

Are there any blaringly obvious problems with my implementation of king safety? Any obvious improvements to be made? I appreciate any thoughts.
One idea that you can try is still quite novel and needs some investigation. For every root move count the number of moves for white and black separately and count the number of checkmates for each side. Do some math that makes sense and modify the root scores. If a root move is bad for king safety it will show up in the numbers.
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: Help with simple approach to king safety

Post by Mike Sherwin »

Here is a Bricabrac self play game I did using this idea and only this idea. It was a material only search that counted checkmates and only checkmates. And I did it very badly. You might even say that it was completely broken. And yet the king safety related play is quite evident.
[pgn]1. e3 Nf6 2. Bc4 e6 3. Nc3 Na6 4. h4 h5 5. d4 Ng4 6. d5 Bc5 7. Bb3 c6 8. dxe6 fxe6 9. Nf3 Be7 10. Ke2 O-O 11. a3 c5 12. g3 Rxf3 13. Kxf3 Qf8 14. Kg2 Qxf2 15. Kh3 Qf6 16. Rf1 Nf2 17. Rxf2 Qxf2[/pgn]
If it being completely broken and a material only search that just counts checkmates and it looks this intelligent then just think what it can do if someone did it right!

Here is another self play game counting only the checkmates.
[pgn]1. e3 Nf6 2. Bc4 e6 3. Qf3 d6 4. g4 a5 5. Nh3 h5 6. g5 d5 7. Na3 dxc4 8. gxf6 gxf6 9. Nf4 h4 10. Nxc4 h3 11. a3 f5 12. Ne5 Qd6 13. d4 Bg7 14. Nfd3 Rh4 15. Nc4 Qa6 16. Rg1 Bf8 17. b3 Qa7 18. Nf4 b5 19. Ne5 a4 20. Rg8 Qa5 21. Bd2 Rxf4 22. exf4 Qa6 23. Qh5 Ke7 24. Qxf7 Kd6 25. Qxf8 Kd5 26. Qc5 Ke4 27. f3#[/pgn]

Another stat that can be counted is the number of failed null moves. It can gage how much threat a side in under. Statistics has a future in game tree search. Mar just used this idea (counting beta cuts) in Cheng(?) just for move ordering and gained 20 elo!
jmcd
Posts: 58
Joined: Wed Mar 18, 2020 10:00 pm
Full name: Jonathan McDermid

Re: Help with simple approach to king safety

Post by jmcd »

Mike Sherwin wrote: Sat Nov 19, 2022 2:10 pm One idea that you can try is still quite novel and needs some investigation. For every root move count the number of moves for white and black separately and count the number of checkmates for each side. Do some math that makes sense and modify the root scores. If a root move is bad for king safety it will show up in the numbers.
If a root moves' subtree has a higher number or rate of checkmates than the subtrees of other root moves, is it really fair to say that this is due to king safety? Isn't it possible that the move blunders in some other way, like just regular material loss? Is it fine to assume correlation = causation if you're right most of the time?

These statistical evaluation methods are interesting but the main reason I am hesitant to try them is that I want my entire evaluation to be tunable and deterministic with only a position required as input. Also I like keeping things simple to the point where I can easily understand everything that is happening.
Clovis GitHub
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: Help with simple approach to king safety

Post by Mike Sherwin »

jmcd wrote: Sun Nov 20, 2022 7:19 am
Mike Sherwin wrote: Sat Nov 19, 2022 2:10 pm One idea that you can try is still quite novel and needs some investigation. For every root move count the number of moves for white and black separately and count the number of checkmates for each side. Do some math that makes sense and modify the root scores. If a root move is bad for king safety it will show up in the numbers.
If a root moves' subtree has a higher number or rate of checkmates than the subtrees of other root moves, is it really fair to say that this is due to king safety? Isn't it possible that the move blunders in some other way, like just regular material loss? Is it fine to assume correlation = causation if you're right most of the time?

These statistical evaluation methods are interesting but the main reason I am hesitant to try them is that I want my entire evaluation to be tunable and deterministic with only a position required as input. Also I like keeping things simple to the point where I can easily understand everything that is happening.
Material loss does not necessarily mean more negative checkmates in the short term. Material loss can in the short term mean more positive checkmates! One still has the result of material and evaluation. Basically if white has a bishop on c4 and black can play e6 or e5 with checkmate counting it more likely will play e6. And that is an added bit of king safety.