Skip to content

Commit

Permalink
Fix draw by repetition handling bug
Browse files Browse the repository at this point in the history
The previous fix included a bug once the ply count exceeded `128`, where the code would lose track
of where in the stack states it was supposed to look. The fix ensures the stack states are as
expected. The repetition check then works as intented. Included is a new test case with one of the
regression that was observed from live games.
  • Loading branch information
bsamseth committed Sep 21, 2024
1 parent 4f429fe commit 9f4b166
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 15 deletions.
21 changes: 20 additions & 1 deletion engine/src/search/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,9 @@ mod tests {

#[test]
fn test_is_draw_by_repetition() {
let position: Position = "fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 moves g1f3 b8c6 f3g1 c6b8 g1f3 b8c6 f3g1".parse().unwrap();
let position: Position = "startpos moves g1f3 b8c6 f3g1 c6b8 g1f3 b8c6 f3g1"
.parse()
.unwrap();
let mut tt = TranspositionTable::default();
let mut searcher = Searcher::new(&position, &[], Arc::default(), &mut tt, None);
assert!(!searcher.is_draw(&position.start_pos, Ply::ZERO));
Expand All @@ -234,4 +236,21 @@ mod tests {

assert!(searcher.is_draw(&board, Ply::ONE));
}

#[test]
fn test_draw_by_repetition_regression() {
let position: Position = "startpos moves e2e4 e7e5 g1f3 b8c6 f1b5 g8f6 e1g1 f6e4 d2d4 e4d6 b5c6 d7c6 d4e5 d6f5 d1d8 e8d8 b1c3 d8e8 b2b3 c8e6 f1d1 h7h6 c1b2 a8d8 h2h3 f8e7 a2a4 d8d1 a1d1 h8f8 g2g4 f5h4 f3h4 e7h4 b2a3 h4e7 a3e7 e8e7 f2f4 g7g6 c3e4 h6h5 e4f6 h5h4 f6h7 f8g8 h7g5 a7a5 g1f2 g8e8 f2f3 b7b5 g5e6 f7e6 a4b5 c6b5 g4g5 a5a4 b3a4 b5a4 d1a1 e8a8 f3g4 a4a3 g4h4 a3a2 h4g4 e7f7 h3h4 c7c5 c2c4 a8a3 h4h5 g6h5 g4h5 a3h3 h5g4 h3h2 a1d1 f7g6 d1a1 h2b2 g4f3 g6f5 g5g6 f5g6 a1g1 g6f7 g1a1 b2c2 f3e3 c2c3 e3e4 c3c4 e4d3 c4a4 d3c3 f7g6 c3b3 a4a5 a1a2 a5a2 b3a2 g6f5 a2b3 f5f4 b3c4 f4e5 c4c5 e5f4 c5c6 e6e5 c6d5 e5e4 d5d4 e4e3 d4d5 e3e2 d5d4 e2e1q d4d5 e1h1 d5c5 h1e4 c5d6 f4f3 d6d7 e4d3 d7c7 d3d2 c7c6 d2d1 c6c5 d1e2 c5d5 e2d3 d5c5 d3c2 c5d5 c2d2 d5c5 d2g5 c5d6 g5g8 d6d7 g8c4 d7d6 c4d3 d6c5 d3f5 c5d6 f5e4 d6d7 e4a4 d7d6 a4a2 d6d7 a2c2 d7e7 c2e4".parse().unwrap();
let draw_move = "e7d7".parse().unwrap();

let mut tt = TranspositionTable::default();
let mut searcher = Searcher::new(&position, &[], Arc::default(), &mut tt, None);

assert!(!searcher.is_draw(&searcher.root_position, Ply::ZERO));

let board = searcher.root_position;
let mut new_board = board;
searcher.make_move(&board, draw_move, &mut new_board, Ply::ZERO);

assert!(searcher.is_draw(&board, Ply::ONE));
}
}
31 changes: 17 additions & 14 deletions engine/src/search/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use super::newtypes::Ply;
use super::tt::TranspositionTable;
use crate::board::BoardExt;
use crate::evaluate::Evaluate;
use crate::newtypes::Depth;
use fathom::Tablebase;
use stackstate::StackState;
use uci::Position;
Expand All @@ -27,7 +26,7 @@ use uci::Position;
pub struct Searcher<'a> {
root_position: Board,
root_position_ply: Ply,
ss: [StackState; Ply::MAX.as_usize() + 1],
ss: Vec<StackState>,
limits: Limits,
logger: Logger,
stop_signal: Arc<AtomicBool>,
Expand All @@ -54,26 +53,30 @@ impl<'a> Searcher<'a> {
let mut halfmove_clock = position.starting_halfmove_clock;
let mut root_position_ply = Ply::ZERO;

let mut stack_states = [StackState::default(); Ply::MAX.as_usize() + 1];
stack_states[0].eval = Some(root_position.evaluate());
stack_states[0].zobrist = root_position.get_hash();
stack_states[0].halfmove_clock = halfmove_clock;
let mut stack_states =
vec![StackState::default(); Ply::MAX.as_usize() + position.moves.len() + 1];
stack_states[0] = StackState {
eval: Some(root_position.evaluate()),
zobrist: root_position.get_hash(),
halfmove_clock,
..Default::default()
};

for (i, mv) in position.moves.iter().enumerate() {
for mv in &position.moves {
if root_position.halfmove_reset(*mv) {
halfmove_clock = 0;
} else {
halfmove_clock += 1;
}
root_position = root_position.make_move_new(*mv);
stack_states[i + 1].eval = Some(root_position.evaluate());
stack_states[i + 1].zobrist = root_position.get_hash();
stack_states[i + 1].halfmove_clock = halfmove_clock;
root_position_ply = root_position_ply.increment();

// Remember pre-root moves, but leave enough stack states for a max search depth.
if position.moves.len() - i < Depth::MAX.as_usize() {
root_position_ply = root_position_ply.increment();
}
stack_states[root_position_ply.as_usize()] = StackState {
eval: Some(root_position.evaluate()),
zobrist: root_position.get_hash(),
halfmove_clock,
..Default::default()
};
}

let root_moves: MoveVec = MoveGen::new_legal(&root_position).into();
Expand Down

0 comments on commit 9f4b166

Please sign in to comment.