|
@@ -51,6 +51,7 @@ pub struct SearchControl {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+#[derive(Debug)]
|
|
pub enum SearchResult {
|
|
pub enum SearchResult {
|
|
Finished(Move, PosValue),
|
|
Finished(Move, PosValue),
|
|
NoMove(PosValue),
|
|
NoMove(PosValue),
|
|
@@ -151,13 +152,52 @@ impl SearchControl {
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if self.stopping {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
- pub fn iterative_deepening(&mut self, max_depth: Option<i32>) -> bool {
|
|
|
|
- let mut alpha = MIN_VALUE;
|
|
|
|
- let mut beta = MAX_VALUE;
|
|
|
|
|
|
+ fn windowed_search(&mut self, depth: i32, expected_value: PosValue, initial_window_size: i32) -> SearchResult {
|
|
|
|
+ let mut alpha_coeff = 1;
|
|
|
|
+ let mut beta_coeff = 1;
|
|
|
|
+ let mut alpha = expected_value - initial_window_size * alpha_coeff;
|
|
|
|
+ let mut beta = expected_value + initial_window_size * beta_coeff;
|
|
|
|
+
|
|
|
|
+ let mut result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
|
|
|
|
+
|
|
|
|
+ loop {
|
|
|
|
+ if let SearchResult::Finished(_mov, val) = result {
|
|
|
|
+ if val > alpha && val < beta {
|
|
|
|
+ // successful search within bounds
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ if val <= alpha {
|
|
|
|
+ alpha_coeff *= 3;
|
|
|
|
+ alpha = expected_value - initial_window_size * alpha_coeff;
|
|
|
|
+ result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
|
|
|
|
+ }
|
|
|
|
+ if val >= beta {
|
|
|
|
+ beta_coeff *= 3;
|
|
|
|
+ beta = expected_value + initial_window_size * beta_coeff;
|
|
|
|
+ result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if let SearchResult::Invalid = result {
|
|
|
|
+ alpha = MIN_VALUE; beta = MAX_VALUE;
|
|
|
|
+ result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pub fn iterative_deepening(&mut self, max_depth: Option<i32>) -> bool {
|
|
let mut best_move: Option<Move> = None;
|
|
let mut best_move: Option<Move> = None;
|
|
let mut best_val: Option<PosValue> = None;
|
|
let mut best_val: Option<PosValue> = None;
|
|
|
|
|
|
@@ -165,40 +205,15 @@ impl SearchControl {
|
|
self.halfmove_age = self.board.ply_number() as _;
|
|
self.halfmove_age = self.board.ply_number() as _;
|
|
|
|
|
|
for depth in 1.. {
|
|
for depth in 1.. {
|
|
- let board = self.board.clone();
|
|
|
|
- let hash = self.hash.clone();
|
|
|
|
|
|
|
|
self.set_depth(depth);
|
|
self.set_depth(depth);
|
|
|
|
|
|
let result =
|
|
let result =
|
|
if let Some(bv) = best_val {
|
|
if let Some(bv) = best_val {
|
|
- const WINDOW_RADIUS: PosValue = 30;
|
|
|
|
- alpha = bv - WINDOW_RADIUS;
|
|
|
|
- beta = bv + WINDOW_RADIUS;
|
|
|
|
-
|
|
|
|
- let mut result = search(board, self, hash, alpha, beta, depth);
|
|
|
|
- loop {
|
|
|
|
- if let SearchResult::Finished(_mov, val) = result {
|
|
|
|
- if val <= alpha || val >= beta {
|
|
|
|
- alpha = MIN_VALUE; beta = MAX_VALUE;
|
|
|
|
- result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth)
|
|
|
|
- }
|
|
|
|
- else {
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- else if let SearchResult::Invalid = result {
|
|
|
|
- alpha = MIN_VALUE; beta = MAX_VALUE;
|
|
|
|
- result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth)
|
|
|
|
- }
|
|
|
|
- else {
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- result
|
|
|
|
|
|
+ const WINDOW_RADIUS: PosValue = 10;
|
|
|
|
+ self.windowed_search(depth, bv, WINDOW_RADIUS)
|
|
} else {
|
|
} else {
|
|
- alpha = MIN_VALUE; beta = MAX_VALUE;
|
|
|
|
- search(board, self, hash, alpha, beta, depth)
|
|
|
|
|
|
+ search(self.board.clone(), self, self.hash.clone(), MIN_VALUE, MAX_VALUE, depth)
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -336,7 +351,12 @@ pub fn search(mut board: Board, sc: &mut SearchControl, hashs: Arc<RwLock<Cache>
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
else {
|
|
- return SearchResult::Invalid;
|
|
|
|
|
|
+ if cancelled {
|
|
|
|
+ return SearchResult::Cancelled(None);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ return SearchResult::Invalid;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -429,6 +449,15 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // futility pruning
|
|
|
|
+ if false && depth == 1 && !check && game_lateness(game) < 60 {
|
|
|
|
+ const FUTILITY_THRESHOLD: PosValue = 240;
|
|
|
|
+ let curr_eval = quiescence_search(game, sc, hash, alpha, beta, 9);
|
|
|
|
+ if curr_eval + FUTILITY_THRESHOLD <= alpha {
|
|
|
|
+ return alpha;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
let mut best_move: Option<Move> = None;
|
|
let mut best_move: Option<Move> = None;
|
|
|
|
|
|
while let Some(mov) = moves.next() {
|
|
while let Some(mov) = moves.next() {
|
|
@@ -529,7 +558,7 @@ fn quiescence_search(board: &mut Board, sc: &mut SearchControl, hash: &mut Cache
|
|
];
|
|
];
|
|
const SAFETY_MARGIN: i32 = 200;
|
|
const SAFETY_MARGIN: i32 = 200;
|
|
let target_sq = mov.to_simple().to;
|
|
let target_sq = mov.to_simple().to;
|
|
- if let Some((piece_type, side)) = board.get_square(target_sq) {
|
|
|
|
|
|
+ if let Some((piece_type, _side)) = board.get_square(target_sq) {
|
|
if val + PIECE_VALUES[piece_type as usize] + SAFETY_MARGIN < alpha {
|
|
if val + PIECE_VALUES[piece_type as usize] + SAFETY_MARGIN < alpha {
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|