From 612e6ffc15da099d4d4683bbca265e576383ccda Mon Sep 17 00:00:00 2001 From: dogeystamp Date: Sun, 29 Dec 2024 22:32:00 -0500 Subject: [PATCH] perf: repetition draw avoidance now only looks at necessary moves uses half-move counter. --- src/lib.rs | 49 ++++++++++++++++++++++++++++++++++++++++--------- src/main.rs | 12 +++++------- src/search.rs | 3 +-- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3536e3e..597cad7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,10 +19,10 @@ use std::str::FromStr; pub mod coordination; pub mod eval; -pub mod nnue; pub mod fen; mod hash; pub mod movegen; +pub mod nnue; pub mod random; pub mod search; @@ -554,7 +554,7 @@ const HISTORY_SIZE: usize = 100; impl BoardHistory { /// Counts occurences of this hash in the history. - fn count(&self, hash: Zobrist) -> usize { + fn _count(&self, hash: Zobrist) -> usize { let mut ans = 0; let mut i = self.ptr_start; @@ -568,6 +568,20 @@ impl BoardHistory { ans } + /// Find if there are at least `n` matches for a hash in the last `recent` plies. + fn at_least_in_recent(&self, mut n: usize, recent: usize, hash: Zobrist) -> bool { + let mut i = self.ptr_end - recent; + + while i != self.ptr_end && n > 0 { + if self.hashes[usize::from(i)] == hash { + n -= 1; + } + i += 1; + } + + n == 0 + } + /// Add (push) hash to history. fn push(&mut self, hash: Zobrist) { self.hashes[usize::from(self.ptr_end)] = hash; @@ -633,6 +647,12 @@ impl Board { self.history.push(self.zobrist); } + /// Is this position a draw by three repetitions? + pub fn is_repetition(&mut self) -> bool { + self.history + .at_least_in_recent(2, self.half_moves, self.zobrist) + } + /// Get iterator over all squares. pub fn squares() -> impl Iterator { (0..N_SQUARES).map(Square::try_from).map(|x| x.unwrap()) @@ -948,20 +968,30 @@ mod tests { history.push(board.zobrist); } - assert_eq!(history.count(board.zobrist), HISTORY_SIZE - 1); + assert_eq!(history._count(board.zobrist), HISTORY_SIZE - 1); + assert!(history.at_least_in_recent(1, 1, board.zobrist)); + assert!(history.at_least_in_recent(2, 3, board.zobrist)); + assert!(history.at_least_in_recent(1, 3, board.zobrist)); let board_empty = Board::default(); history.push(board_empty.zobrist); - assert_eq!(history.count(board.zobrist), HISTORY_SIZE - 2); - assert_eq!(history.count(board_empty.zobrist), 1); + assert!(!history.at_least_in_recent(1, 1, board.zobrist)); + assert!(history.at_least_in_recent(1, 2, board.zobrist)); + + assert_eq!(history._count(board.zobrist), HISTORY_SIZE - 2); + assert_eq!(history._count(board_empty.zobrist), 1); + assert!(history.at_least_in_recent(1, 3, board.zobrist)); + assert!(history.at_least_in_recent(1, 20, board_empty.zobrist)); + assert!(history.at_least_in_recent(1, 15, board_empty.zobrist)); + assert!(history.at_least_in_recent(1, 1, board_empty.zobrist)); for _ in 0..3 { history.push(board_empty.zobrist); } - assert_eq!(history.count(board_empty.zobrist), 4); - assert_eq!(history.count(board.zobrist), HISTORY_SIZE - 5); + assert_eq!(history._count(board_empty.zobrist), 4); + assert_eq!(history._count(board.zobrist), HISTORY_SIZE - 5); } #[test] @@ -982,7 +1012,8 @@ mod tests { } // this is the third occurence, but beforehand there are two occurences - assert_eq!(board.history.count(board.zobrist), 2); + assert_eq!(board.history._count(board.zobrist), 2); + assert!(board.is_repetition(), "fen: {}", board.to_fen()); } /// engine should take advantage of the three time repetition rule @@ -1032,7 +1063,7 @@ mod tests { expected_bestmv.make(&mut board); eprintln!( "after expected mv, board repeated {} times", - board.history.count(board.zobrist) + board.history._count(board.zobrist) ); assert_eq!( diff --git a/src/main.rs b/src/main.rs index cfc47f2..fbdb597 100644 --- a/src/main.rs +++ b/src/main.rs @@ -370,20 +370,18 @@ fn task_engine(tx_main: Sender, rx_engine: Receiver) { let mut info: Vec = Vec::new(); if state.config.nnue_train_info { let is_quiet = chess_inator::search::is_quiescent_position(&board, eval); - let is_quiet = if is_quiet {"quiet"} else {"non-quiet"}; + let is_quiet = if is_quiet { "quiet" } else { "non-quiet" }; let board_tensor = chess_inator::nnue::InputTensor::from_board(&board); let abs_eval = EvalInt::from(eval) * EvalInt::from(board.get_turn().sign()); - info.push(format!("NNUETrainInfo {} {} {}", is_quiet, abs_eval, {board_tensor})) + info.push(format!("NNUETrainInfo {} {} {}", is_quiet, abs_eval, { + board_tensor + })) } tx_main - .send(MsgToMain::Bestmove(MsgBestmove { - pv, - eval, - info, - })) + .send(MsgToMain::Bestmove(MsgBestmove { pv, eval, info })) .unwrap(); } MsgToEngine::Stop => {} diff --git a/src/search.rs b/src/search.rs index d70cd69..2c0bf51 100644 --- a/src/search.rs +++ b/src/search.rs @@ -224,7 +224,7 @@ fn minmax(board: &mut Board, state: &mut EngineState, mm: MinmaxState) -> (Vec= 2; + let is_repetition_draw = board.is_repetition(); let phase_factor = EvalInt::from(board.eval.min_maj_pieces / 5); // positive here since we're looking from the opposite perspective. // if white caused a draw, then we'd be black here. @@ -311,7 +311,6 @@ fn minmax(board: &mut Board, state: &mut EngineState, mm: MinmaxState) -> (Vec