SebLague / Chess-Challenge

Create your own tiny chess bot!
https://www.youtube.com/watch?v=Ne40a5LkK6A
MIT License
1.78k stars 1.07k forks source link

Impossible to check for threefold repetition (version 1.18) #465

Closed jongdetim closed 10 months ago

jongdetim commented 10 months ago

With the current implementation of board.IsDraw() it is not possible to check if the game is drawn due to threefold repetition. This is because of a choice made regarding the mentioned function:

Note: this function will return true if the same position has occurred twice on the board (rather than 3 times,
which is when the game is actually drawn). This quirk is to help bots avoid repeating positions unnecessarily.

However, this means that it's not possible to check for actual threefold repetitions, and my bot stops exploring lines that involve repeated positions, since it think it's a draw. If the first node (current board state) of the search is seen twice, it's recognized as a draw, the search ends and returns NullMove, since there are no legal moves. To the game logic engine, the game is not finished, since the position has only occurred twice.

Using this FEN: 5K1k/Rr6/6PP/8/8/8/8/8 b - - 18 81 My bot is black, and plays Rb8+, after which i play Kf7. Black plays Rb7, and when I then play Kf8, for some reason it doesn't consider the position drawn for a maxdepth = 1 search, but in any further (deeper) searches, the position in the FEN is recognized as drawn.

Solution: board.IsDraw() should return whether the current board state is a draw. This was the previous behavior, but was changed in version 1.17, but I don't understand why. The bot wouldn't search repeating position unnecessarily, since it recognizes the 3rd occurrence as a draw by repetition. Instead, the bot now considers every repeating move a draw.

jongdetim commented 10 months ago

For completeness sake, I call it in my search function like this:

if (depth == 0 || board.IsCheckmate() || board.IsDraw())
    # handle leaf node
Edward-789 commented 10 months ago
  1. you should definetly handle terminal nodes in search, not eval.
  2. i believe isDraw callls isRepeatedPosition so it should test for repeated positions
  3. your issue of drawing at depth 1 is because i believe the repetition table is cleared after every move
jongdetim commented 10 months ago
  1. Ofcourse, but that's what I'm doing.
  2. Correct, but it returns true if the position has been seen only once before.
  3. Could you elaborate? If the repetition table is cleared after every move, the table wouldn't function. It's clearing the table on captures and pawn moves. It should also clear the entry on undoMove(). I'm just calling board.IsDraw() during search and it's returning True, even though it's nót a draw, as evidenced by the engine not declaring a draw.
jongdetim commented 10 months ago

This is frustrating, as I can't work on this challenge if it's impossible te check whether a game is drawn or not

jongdetim commented 10 months ago

My best guess is that the issue arises when the first node in your search is the root node / current board state, rather than all the possible moves from that state. This means the root node / current board state is seen multiple times during search, without the move being "undone". However, implementing minimax with a PV table is silly without starting search from the root node... because we can store the best move in the transposition table entry associated with the parent node, among other reasons. How can I avoid revisits of the same state during iterative deepening search being recognized als repetitions of that state? The solution seems to be to only check if a state is a repetition, when a move is being made tó that state.

jongdetim commented 10 months ago

Well, i just realized there were 2 patches regarding repetition checks, 1.19 and 1.20, and i was still running version 1.18... oops! I'll merge the changes and hopefully that should resolve it.

jongdetim commented 10 months ago

I can confirm this issue was fixed in version 1.20