|
@@ -57,6 +57,8 @@ pub enum SearchResult {
|
|
|
Finished(Move, PosValue),
|
|
|
NoMove(PosValue),
|
|
|
Cancelled(Option<(Move, PosValue)>),
|
|
|
+ FailHigh(PosValue),
|
|
|
+ FailLow(PosValue),
|
|
|
PerftFinished,
|
|
|
Invalid
|
|
|
}
|
|
@@ -164,28 +166,32 @@ impl SearchControl {
|
|
|
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 {
|
|
|
+ match result {
|
|
|
+ SearchResult::FailHigh(_val) => {
|
|
|
+ beta_coeff *= 3;
|
|
|
+ beta = expected_value + initial_window_size * beta_coeff;
|
|
|
+ if beta_coeff >= 9 {
|
|
|
+ beta = MAX_VALUE;
|
|
|
+ }
|
|
|
+ result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
|
|
|
+ },
|
|
|
+ SearchResult::FailLow(_val) => {
|
|
|
alpha_coeff *= 3;
|
|
|
alpha = expected_value - initial_window_size * alpha_coeff;
|
|
|
+ if alpha_coeff >= 9 {
|
|
|
+ alpha = MIN_VALUE;
|
|
|
+ }
|
|
|
result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
|
|
|
+ },
|
|
|
+ SearchResult::Finished(_mov, val) => {
|
|
|
+ // successful search within bounds
|
|
|
+ break;
|
|
|
}
|
|
|
- if val >= beta {
|
|
|
- beta_coeff *= 3;
|
|
|
- beta = expected_value + initial_window_size * beta_coeff;
|
|
|
+ SearchResult::Invalid => {
|
|
|
+ alpha = MIN_VALUE; beta = MAX_VALUE;
|
|
|
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;
|
|
|
+ _ => break,
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -226,7 +232,7 @@ impl SearchControl {
|
|
|
let pv = &pv_array.iter().filter(|&m| !m.is_null()).map(|m| m.to_string()).fold(String::new(), |a, b| a + " " + &b)[0..];
|
|
|
|
|
|
|
|
|
- if let Some(plies) = crate::evaluate::is_mate_in_p1(val) {
|
|
|
+ if let Some(plies) = crate::evaluate::is_mate_in_plies(val) {
|
|
|
//println!("plies = {}", plies);
|
|
|
let turns = (plies + if plies > 0 { 1 } else { -1 }) / 2;
|
|
|
let infostring = format!("info depth {} score mate {} time {} nodes {} nps {} hashfull {} pv{}", depth, turns, elapsed.as_millis(), nodes, nps, hashfull, pv);
|
|
@@ -294,11 +300,6 @@ pub fn search(mut board: Board, sc: &mut SearchControl, hashs: Arc<RwLock<Cache>
|
|
|
|
|
|
info!("moves: {:?}", moves.iter().map(|mv| mv.to_string()).collect::<Vec<String>>());
|
|
|
|
|
|
- // use a slight offset for the alpha value in the root node in order to
|
|
|
- // determine possibly multiple good moves
|
|
|
- const ALPHA_OFFSET: PosValue = 0 as PosValue;
|
|
|
-
|
|
|
- let mut valued_moves: Vec<(Move, PosValue)> = Vec::with_capacity(moves.len());
|
|
|
let mut cancelled = false;
|
|
|
|
|
|
if moves.is_empty() {
|
|
@@ -310,13 +311,17 @@ pub fn search(mut board: Board, sc: &mut SearchControl, hashs: Arc<RwLock<Cache>
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ let mut best_move: Option<Move> = None;
|
|
|
+
|
|
|
for mov in moves {
|
|
|
let undo = board.apply(mov);
|
|
|
|
|
|
- let val = -negamax(&mut board, sc, &mut hash, decrease_mate_in(-beta), decrease_mate_in(-alpha), depth - 1);
|
|
|
+ let val = increase_mate_in(
|
|
|
+ -negamax(&mut board, sc, &mut hash, decrease_mate_in(-beta), decrease_mate_in(-alpha), depth - 1)
|
|
|
+ );
|
|
|
|
|
|
//info!("moveval {} -> {}\n", mov.to_string(), val);
|
|
|
-
|
|
|
+
|
|
|
board.undo_move(undo);
|
|
|
|
|
|
if sc.stopping {
|
|
@@ -324,35 +329,30 @@ pub fn search(mut board: Board, sc: &mut SearchControl, hashs: Arc<RwLock<Cache>
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- if increase_mate_in(val) > alpha {
|
|
|
- alpha = increase_mate_in(val) - ALPHA_OFFSET;
|
|
|
- valued_moves.push((mov, -alpha));
|
|
|
+ if val >= beta {
|
|
|
+ hash.cache(&board, CacheEntry::new_lower(depth as _, sc.halfmove_age as _, mov.to_simple(), val));
|
|
|
+ return SearchResult::FailHigh(val);
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- valued_moves.sort_by_key(|mv| mv.1);
|
|
|
+ if val > alpha {
|
|
|
+ alpha = val;
|
|
|
+ best_move = Some(mov);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if valued_moves.len() > 0 {
|
|
|
- let min_val = valued_moves[0].1;
|
|
|
- let best_moves = valued_moves.iter().filter(|mv| mv.1 == min_val).collect::<Vec<&(Move, PosValue)>>();
|
|
|
+ if cancelled {
|
|
|
+ return SearchResult::Cancelled(None);
|
|
|
+ }
|
|
|
|
|
|
- let mut rng = rand::thread_rng();
|
|
|
- let chosen_mov = best_moves[(rng.next_u64() % best_moves.len() as u64) as usize];
|
|
|
- if cancelled {
|
|
|
- return SearchResult::Cancelled(Some((chosen_mov.0, -chosen_mov.1)));
|
|
|
- }
|
|
|
- else {
|
|
|
- hash.cache(&board, CacheEntry::new_value(depth as _, sc.halfmove_age as _, chosen_mov.0.to_simple(), chosen_mov.1 as _));
|
|
|
- return SearchResult::Finished(chosen_mov.0, -chosen_mov.1);
|
|
|
- }
|
|
|
+ if let Some(mov) = best_move {
|
|
|
+ // alpha is exact
|
|
|
+ let ce = CacheEntry::new_value(depth as _, sc.halfmove_age as _, mov.to_simple(), alpha);
|
|
|
+ hash.cache(&board, ce);
|
|
|
+ return SearchResult::Finished(mov, alpha);
|
|
|
}
|
|
|
else {
|
|
|
- if cancelled {
|
|
|
- return SearchResult::Cancelled(None);
|
|
|
- }
|
|
|
- else {
|
|
|
- return SearchResult::Invalid;
|
|
|
- }
|
|
|
+ hash.cache(&board, CacheEntry::new_upper(depth as _, sc.halfmove_age as _, SimpleMove { from: 0, to: 0 }, alpha));
|
|
|
+ return SearchResult::FailLow(alpha);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -362,11 +362,11 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
|
|
|
let last_move = sc.last_move;
|
|
|
|
|
|
// we can't beat an alpha this good
|
|
|
- if alpha >= mate_in_p1(1) {
|
|
|
+ if alpha >= mate_in_plies(1) {
|
|
|
return alpha;
|
|
|
}
|
|
|
|
|
|
- let cache_entry = hash.lookup(game);
|
|
|
+ let cache_entry = hash.lookup(game).map(CacheEntry::clone);
|
|
|
|
|
|
if let Some(e) = &cache_entry {
|
|
|
if e.depth() as i32 >= depth {
|
|
@@ -379,6 +379,7 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
|
|
|
EntryType::UpperBound => {
|
|
|
if e.value() < alpha { return alpha; }
|
|
|
},
|
|
|
+ EntryType::NoBound => {}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -413,7 +414,7 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
|
|
|
|
|
|
let mut moves = MoveGenerator::generate_legal_moves(
|
|
|
game,
|
|
|
- cache_entry,
|
|
|
+ cache_entry.as_ref(),
|
|
|
&sc.killer_moves[ply_depth],
|
|
|
if depth >= 3 { last_move } else { None },
|
|
|
sc.countermoves.clone(),
|
|
@@ -447,7 +448,7 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
|
|
|
sc.reductions_allowed = true;
|
|
|
game.undo_move(undo);
|
|
|
|
|
|
- if is_mate_in_p1(val).is_none() {
|
|
|
+ if is_mate_in_plies(val).is_none() {
|
|
|
if val >= beta {
|
|
|
depth -= reduce - 1;
|
|
|
//return beta;
|
|
@@ -519,12 +520,15 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
|
|
|
if val > alpha {
|
|
|
alpha = val;
|
|
|
best_move = Some(mov);
|
|
|
+ if sc.initial_depth >= 10 && cache_entry.is_some() && cache_entry.as_ref().unwrap().entry_type == EntryType::Value {
|
|
|
+ //println!("mov: {}", mov.to_string());
|
|
|
+ }
|
|
|
if depth >= 2 {
|
|
|
if let Some(_lm) = last_move {
|
|
|
//sc.countermoves.as_ref().lock().unwrap().update_score(lm, mov, (depth * depth) as i16);
|
|
|
}
|
|
|
}
|
|
|
- if alpha >= mate_in_p1(1) {
|
|
|
+ if alpha >= mate_in_plies(1) {
|
|
|
break;
|
|
|
}
|
|
|
}
|