I currently have a very simple evaluate function, and at the stage of the game that my question pertains to, it's basically just material. Here's the game:
1. e4 e5 2. Nf3 Nf6 3. Nc3 Bb4 4. Bc4 Nc6 5. Kf1 O-O 6. Ke1 Nxe4 7. Ke2 Bxc3 8. bxc3 d5 9. Bb3 Bg4 10. h3 Bxf3+ 11. gxf3 Qh4 12. fxe4 Qxe4+ 13. Kf1 Qxh1+ 14. Ke2 Qe4+ 15. Kf1 Qh1+ 16. Ke2 Qe4+
My engine was playing black. Currently, my engine doesn't flag a move as a checking move, it only indicates whether a move is a capture or quiet move. It looks like it completed a search of depth 6.
I realize there's not enough info to answer my question, but I thought there might be a common issue that's relevant. I suppose the first thing for me to do is to be able to print the entire searched path instead of only the best move found. I also need to make sure it generated a move to capture the pawn at h3, since w/ my simple evaluator, it seems that's what it should've gone for, and what Stockfish suggests (although Stockfish did consider moving the queen back to e4 before deciding to take the pawn.
Is it advisable to check for the 3 repetitions, and if ahead by "enough" either don't generate a move that will allow that draw claim, or score it appropriately?
Why does my engine want to perpetually check?
Moderator: Ras
-
lojic
- Posts: 71
- Joined: Thu Jan 28, 2021 9:50 pm
- Full name: Brian Adkins
-
mvanthoor
- Posts: 1784
- Joined: Wed Jul 03, 2019 4:42 pm
- Location: Netherlands
- Full name: Marcel Vanthoor
Re: Why does my engine want to perpetually check?
If you only have material count in the evaluation, then your engine basically doesn't know what to do with its pieces. Wherever it moves, all moves are the same as long as it doesn't capture anything. A check is the most attacking version of doing nothing (assuming the king can move away).
I have often seen that my engine postpones a recapture of a piece by one or two moves, by playing two useless checks in between.
1. white captures, black check
2. move king, black check
3. move king, recapture (because check is no longer possible)
A check could also be mate... but that can only be seen in the next ply. Therefore from the point of view giving a check is always good. I've often seen engines do this (including my own) even though its not explicitly programmed into it; and I don't know the exact reason why they do it.
I have often seen that my engine postpones a recapture of a piece by one or two moves, by playing two useless checks in between.
1. white captures, black check
2. move king, black check
3. move king, recapture (because check is no longer possible)
A check could also be mate... but that can only be seen in the next ply. Therefore from the point of view giving a check is always good. I've often seen engines do this (including my own) even though its not explicitly programmed into it; and I don't know the exact reason why they do it.
-
lojic
- Posts: 71
- Joined: Thu Jan 28, 2021 9:50 pm
- Full name: Brian Adkins
Re: Why does my engine want to perpetually check?
I just ran divide depth 1, and it did generate the move h1h3 for the Queen to grab the pawn:lojic wrote: ↑Wed Feb 03, 2021 8:13 pm ...
I also need to make sure it generated a move to capture the pawn at h3, since w/ my simple evaluator, it seems that's what it should've gone for, and what Stockfish suggests (although Stockfish did consider moving the queen back to e4 before deciding to take the pawn.
...
a7a5: 1 a7a6: 1 a8b8: 1 a8c8: 1 a8d8: 1 a8e8: 1 b7b5: 1 b7b6: 1 c6a5: 1 c6b4: 1 c6b8: 1 c6d4: 1 c6d8: 1 c6e7: 1 d5d4: 1 e5e4: 1 f7f5: 1 f7f6: 1 f8b8: 1 f8c8: 1 f8d8: 1 f8e8: 1 g7g5: 1 g7g6: 1 g8h8: 1 h1d1: 1 h1e1: 1 h1e4: 1 h1f1: 1 h1f3: 1 h1g1: 1 h1g2: 1 h1h2: 1 h1h3: 1 h7h5: 1 h7h6: 1
-
lojic
- Posts: 71
- Joined: Thu Jan 28, 2021 9:50 pm
- Full name: Brian Adkins
Re: Why does my engine want to perpetually check?
I'm aware my evaluator is currently deficient :) However, since my engine doesn't know about checking (yet!), I'm not sure why it thinks this is a good move :)mvanthoor wrote: ↑Wed Feb 03, 2021 8:23 pm If you only have material count in the evaluation, then your engine basically doesn't know what to do with its pieces. Wherever it moves, all moves are the same as long as it doesn't capture anything. A check is the most attacking version of doing nothing (assuming the king can move away).
I have often seen that my engine postpones a recapture of a piece by one or two moves, by playing two useless checks in between.
1. white captures, black check
2. move king, black check
3. move king, recapture (because check is no longer possible)
A check could also be mate... but that can only be seen in the next ply. Therefore from the point of view giving a check is always good. I've often seen engines do this (including my own) even though its not explicitly programmed into it; and I don't know the exact reason why they do it.
I am only ordering my capture moves at the moment, so I guess it might choose an essentially random quiet move if they all score the same, *but*, they shouldn't all score the same since the Queen can gain a pawn. I need to add some debugging capability to gain insight into what's going on.
Before I just code something up, is there anything analogous to perft/divide for debugging scoring at various depths?
-
mvanthoor
- Posts: 1784
- Joined: Wed Jul 03, 2019 4:42 pm
- Location: Netherlands
- Full name: Marcel Vanthoor
Re: Why does my engine want to perpetually check?
I don't know... I don't always bother with the debugger if the problem is really remote. Then I just put in some print-statements in an IF/THEN block. Sometimes that's faster than creating a conditional debug point (and I can print information that is not immediately available at the checkpoint).
-
Ras
- Posts: 2703
- Joined: Tue Aug 30, 2016 8:19 pm
- Full name: Rasmus Althoff
Re: Why does my engine want to perpetually check?
Within the search tree, i.e. from the search position back to root move, one repetition is sufficient to score that position as draw. If the repetitions only show up before the root position (i.e. in the game history), it's a bit more safe to require three repetitions to score the search tree position as draw.
Always score it appropriately. You may be ahead in material, but unable to defend against a mate thread so that a perpetual is the only way to a draw.and if ahead by "enough" either don't generate a move that will allow that draw claim, or score it appropriately?
Do you score the distance from root in the eval? If the side to move is behind, then adding the distance to root to the eval is good, otherwise subtracting it. The idea is to delay bad positions if possible and prefer good positions sooner. That's especially good for mates because going for a shorter mate makes sure there will be mate progress in the following moves.
Also MVV-LVA will help because the recapture will be higher in the move list, thus searched earlier, and alpha-beta will keep earlier moves unless later ones score higher. A later move with only equal score will not be selected.
Rasmus Althoff
https://www.ct800.net
https://www.ct800.net
-
mvanthoor
- Posts: 1784
- Joined: Wed Jul 03, 2019 4:42 pm
- Location: Netherlands
- Full name: Marcel Vanthoor
Re: Why does my engine want to perpetually check?
Not yet. I see its the same idea as the "minus CHECKMATE + ply" when scoring mate. This is easy to add in the evaluation.Ras wrote: ↑Wed Feb 03, 2021 8:44 pm Do you score the distance from root in the eval? If the side to move is behind, then adding the distance to root to the eval is good, otherwise subtracting it. The idea is to delay bad positions if possible and prefer good positions sooner. That's especially good for mates because going for a shorter mate makes sure there will be mate progress in the following moves.
My engine already has MVV-LVA (and that's the only sorting at the moment.)Also MVV-LVA will help because the recapture will be higher in the move list, thus searched earlier, and alpha-beta will keep earlier moves unless later ones score higher. A later move with only equal score will not be selected.
-
lojic
- Posts: 71
- Joined: Thu Jan 28, 2021 9:50 pm
- Full name: Brian Adkins
Re: Why does my engine want to perpetually check?
My bad, it wasn't so simple. Black queen takes pawn, then white bishop takes pawn. This is apparently a trickier position than my engine can handle at the moment :) The fact that Stockfish considered going back to e4 gives me *some* comfort.lojic wrote: ↑Wed Feb 03, 2021 8:33 pmI'm aware my evaluator is currently deficient :) However, since my engine doesn't know about checking (yet!), I'm not sure why it thinks this is a good move :)mvanthoor wrote: ↑Wed Feb 03, 2021 8:23 pm If you only have material count in the evaluation, then your engine basically doesn't know what to do with its pieces. Wherever it moves, all moves are the same as long as it doesn't capture anything. A check is the most attacking version of doing nothing (assuming the king can move away).
I have often seen that my engine postpones a recapture of a piece by one or two moves, by playing two useless checks in between.
1. white captures, black check
2. move king, black check
3. move king, recapture (because check is no longer possible)
A check could also be mate... but that can only be seen in the next ply. Therefore from the point of view giving a check is always good. I've often seen engines do this (including my own) even though its not explicitly programmed into it; and I don't know the exact reason why they do it.
I am only ordering my capture moves at the moment, so I guess it might choose an essentially random quiet move if they all score the same, *but*, they shouldn't all score the same since the Queen can gain a pawn. I need to add some debugging capability to gain insight into what's going on.
Before I just code something up, is there anything analogous to perft/divide for debugging scoring at various depths?
I also now realize (probably) why Stockfish doesn't sort it's perft/divide output! By printing it w/o sorting, I can see the move ordering at the first level. When I removed sorting from my divide it shows the first two moves are h1d1 followed by h1h3, so w/ no material gain, it chose the first one.
I think an evaluator that penalized for being in the same position later would fix it.
Is it standard practice to store the position hash in the move list along with the move to easily detect a repeated position?
-
lojic
- Posts: 71
- Joined: Thu Jan 28, 2021 9:50 pm
- Full name: Brian Adkins
Re: Why does my engine want to perpetually check?
Sorry, I'm just confusing the issue. It *didn't* chose h1d1, and in fact h1e4 is near the end of the list. Clearly the queen must move, and if you eliminate the obviously stupid moves, you're left with the following in the order they are in the generated move list. h1d1 before h1h3 due to MVV-LVA.lojic wrote: ↑Wed Feb 03, 2021 9:23 pm ...
I also now realize (probably) why Stockfish doesn't sort it's perft/divide output! By printing it w/o sorting, I can see the move ordering at the first level. When I removed sorting from my divide it shows the first two moves are h1d1 followed by h1h3, so w/ no material gain, it chose the first one.
h1d1, h1h3, h1g2, h1e4, h1h2,
So h1e4 was considered after h1he and must've scored better. I think I'll code up a simple function to accept a FEN and show the evaluation :)
-
mvanthoor
- Posts: 1784
- Joined: Wed Jul 03, 2019 4:42 pm
- Location: Netherlands
- Full name: Marcel Vanthoor
Re: Why does my engine want to perpetually check?
No. it makes your move data massive. You store it in a history array that contains game states.
The game state struct in my engine is this:
Code: Select all
pub struct GameState {
pub active_color: u8,
pub castling: u8,
pub halfmove_clock: u8,
pub en_passant: Option<u8>,
pub fullmove_number: u16,
pub zobrist_key: u64,
pub material: [u16; Sides::BOTH],
pub psqt: [i16; Sides::BOTH],
pub next_move: Move,
}
- At the beginning of make_move(), I copy the board's game_state variable.
- Then I add the move that make_move() will be executing to "next_move"
- Then I push the game state INTO the history array.
When the move is done and needs to be unmade:
- I pop the game state FROM the history array, directly into the board's "game_state" variable.
- Remeber that the move made by make_move() is still on the board! That move is stored in next_move, and thus I execute this move in reverse.
- Important: the board's remove_piece() and put_piece() update the Zobrist hash. We don't want that here, because the correct Zobrist hash has already been restored to the board. There are two ways to solve this:
A: pass a boolean parameter into put_piece and remove_piece, to tell the functions if they need to update the Zobrist hash (in make_move), or if they shouldn't (in unmake_move).
B: Make a copy of the functions and give them different names, but leave out the Zobrist updates.
I use option B, by having two functions especially for unmake_move(), local to the playmove.rs module. In my engine, that is almost 8% faster than passing a boolean, and then making a decision each time to do or don't update the zobrist hash. The disadvantage is that I have to remember to always add code to both versions of remove_piece and put_piece. (But this is the only place in my entire engine where code is doubled; and I could avoid that, but consciously don't due to the speed loss.)
PS: You can then detect repetitions by going backwards through your history array.
Last edited by mvanthoor on Wed Feb 03, 2021 10:19 pm, edited 3 times in total.