123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- use movegen::*;
- use game::Game;
- use evaluate::*;
- use log::info;
- use rand::prelude::*;
- use hash::*;
- pub struct SearchControl<'a> {
- /// node counter
- pub nodes: usize,
- pub pv: Vec<Move>,
- pub killer_moves: Vec<[Move; 2]>,
- /// function to check if the search should be exited
- pub check: &'a mut dyn FnMut() -> bool,
- pub stopping: bool,
- pub move_history: &'a mut RepetitionTable,
- /// depth the search was started at
- pub initial_depth: i32,
- }
- pub enum SearchResult {
- Finished(Move, PosValue),
- Cancelled(Option<(Move, PosValue)>),
- Invalid
- }
- impl<'a> SearchControl<'a> {
- pub fn new(check: &'a mut dyn FnMut() -> bool, move_history: &'a mut RepetitionTable, depth: i32) -> Self {
- SearchControl {
- nodes: 0,
- pv: Vec::with_capacity(depth as usize),
- killer_moves: vec![[Move::Nullmove; 2]; depth as usize],
- check,
- stopping: false,
- move_history,
- initial_depth: depth
- }
- }
- pub fn set_depth(&mut self, depth: i32) {
- self.initial_depth = depth;
- self.killer_moves = vec![[Move::Nullmove; 2]; depth as usize];
- self.pv = vec![Move::Nullmove; depth as _];
- }
- pub fn insert_killer(&mut self, ply_depth: usize, killer: Move) {
- if self.is_killer(ply_depth, &killer) {
- return;
- }
- let nkm = self.killer_moves[ply_depth].len();
- for i in 1..nkm {
- self.killer_moves[ply_depth][i - 1] = self.killer_moves[ply_depth][i];
- }
- self.killer_moves[ply_depth][nkm - 1] = killer;
- }
- pub fn is_killer(&self, ply_depth: usize, killer: &Move) -> bool {
- self.killer_moves[ply_depth].contains(killer)
- }
- }
- /**
- * searches for moves and returns the best move found plus its value
- */
- pub fn search(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alpha: PosValue, beta: PosValue, depth: i32) -> SearchResult {
- if depth == 0 {
- return SearchResult::Invalid;
- }
- let mut moves = generate_legal_moves(game, game.turn);
- let mut rng = rand::thread_rng();
- let ply_depth = (sc.initial_depth - depth) as usize;
- sort_moves(game, hash, &sc.killer_moves[ply_depth], &mut moves);
-
- info!("moves: {:?}", moves.iter().map(|mv| mv.to_string()).collect::<Vec<String>>());
- //let mut alpha: PosValue = MIN_VALUE;
- //let mut beta: PosValue = MAX_VALUE;
- // 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;
- for mov in moves {
- let undo = game.apply(mov);
- //assert_eq!(new_game, *game, );
- //info!("searching {}", mov.to_string());
- let val = -negamax(game, sc, hash, decrease_mate_in(-beta), decrease_mate_in(-alpha), depth - 1);
- game.undo_move(undo);
- if sc.stopping {
- //return (Move::default(), 0);
- cancelled = true;
- break;
- }
- if val >= mate_in_p1(1) {
- // mate in 1 --- can't get better than that
- info!("yay mate!");
- //return SearchResult::Finished(mov, increase_mate_in(val));
- }
- //info!("searched {} -> {}", mov.to_string(), val);
- if increase_mate_in(val) > alpha {
- alpha = increase_mate_in(val) - ALPHA_OFFSET;
- valued_moves.push((mov, -alpha));
- }
- }
- //info!("movvalues: {:?}", valued_moves.iter().map(|mv| mv.0.to_string() + " - " + &mv.1.to_string()).collect::<Vec<String>>());
- valued_moves.sort_by_key(|mv| mv.1);
- //info!("best movvalues: {:?}", valued_moves.iter().map(|mv| mv.0.to_string() + " - " + &mv.1.to_string()).collect::<Vec<String>>());
- 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)>>();
- //info!("bestmove value {}", -min_val);
- 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(game, CacheEntry::new_value(depth, chosen_mov.1));
- sc.pv[0] = chosen_mov.0;
- return SearchResult::Finished(chosen_mov.0, -chosen_mov.1);
- }
- }
- else {
- return SearchResult::Invalid;
- }
- }
- pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alpha: PosValue, beta: PosValue, depth: i32) -> PosValue {
- if let Some(e) = hash.lookup(game) {
- if e.depth >= depth {
- //println!("TABLE HIT!");
- match e.entry_type {
- EntryType::Value => { return e.value; },
- //EntryType::Value => { if e.value >= alpha { return (e.value, false); } else { return (alpha, false); } },
- //EntryType::LowerBound => { if e.value > alpha { return (e.value, false) } },
- EntryType::LowerBound => {
- if e.value < alpha { return alpha; }
- //if e.value >= beta { return (beta, false); }
- },
- //EntryType::UpperBound => { if e.value >= beta { return (beta, false); } },
- EntryType::UpperBound => {
- if e.value >= beta { return beta; }
- },
- }
- }
- }
- if depth == 0 {
- return quiescence_search(game, sc, hash, alpha, beta, 9);
- }
- sc.nodes += 1;
- if sc.nodes % 1024 == 0 {
- if (sc.check)() {
- sc.stopping = true;
- return 0;
- }
- }
- let ply_depth = (sc.initial_depth - depth) as usize;
- let moves = generate_legal_sorted_moves(game, hash, &sc.killer_moves[ply_depth], game.turn);
- //info!("nega moves: {:?}", moves.iter().map(|mv| mv.to_string()).collect::<Vec<String>>());
- let check = is_check(game, game.turn);
- if moves.len() == 0 {
- if check {
- // mate
- return -mate();
- }
- else {
- // stalemate
- return 0;
- }
- }
- // Nullmove
- if false && !check && depth >= 4 && game_lateness(game) < 80 {
- let nmov = Move::Nullmove;
- let undo = game.apply(nmov);
- let val = -negamax(game, sc, hash, -beta, -alpha, depth / 2 - 1);
- game.undo_move(undo);
- if sc.stopping {
- return alpha;
- }
- if is_mate_in_p1(val).is_none() {
- if val >= beta {
- return beta;
- }
- }
- }
- //sort_moves(game, hash, &sc.killer_moves, &mut moves);
- //moves.sort_by_cached_key(|m| if sc.is_killer(*m) { -1 } else { 0 });
- let mut alpha_is_exact = false;
- let mut best_move = Move::Nullmove;
- // we can't beat an alpha this good
- if alpha >= mate() {
- return alpha;
- }
- for mov in moves {
- let undo = game.apply(mov);
- let val = -negamax(game, sc, hash, -decrease_mate_in(beta), -decrease_mate_in(alpha), depth - 1);
- game.undo_move(undo);
- //val = increase_mate_in(val);
- // return if the search has been cancelled
- if sc.stopping {
- return alpha;
- }
- if val >= beta {
- hash.cache(game, CacheEntry::new_upper(depth, val));
- //println!("but ret {}: {}", depth, beta);
- //info!("{} causes beta cutoff at {} ", mov.to_string(), val);
- if !mov.is_capture() {
- sc.insert_killer(ply_depth, mov);
- }
- return beta;
- }
- if increase_mate_in(val) > alpha {
- alpha = increase_mate_in(val);
- alpha_is_exact = true;
- best_move = mov;
- }
- }
- if alpha_is_exact {
- hash.cache(game, CacheEntry::new_value(depth, alpha));
- let cur_depth = (sc.initial_depth - depth) as usize;
- sc.pv[cur_depth] = best_move;
- }
- else {
- hash.cache(game, CacheEntry::new_lower(depth, alpha));
- }
- //info!("best alpha {}", alpha);
- return alpha;
- }
- fn quiescence_search(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alpha: PosValue, beta: PosValue, depth: i32) -> PosValue {
- sc.nodes += 1;
- if sc.nodes % 1024 == 0 {
- if (sc.check)() {
- sc.stopping = true;
- return 0;
- }
- }
- let val = evaluate(game);
- if val >= beta {
- return beta;
- }
- if val > alpha {
- alpha = val;
- }
- if depth <= 0 {
- return alpha;
- }
- if game.get_piece(KING, game.turn) == 0 { return -mate(); }
- let mut moves = generate_attacking_moves(game, game.turn);
- sort_moves_no_hash(game, &mut moves);
- for mov in moves {
- let undo = game.apply(mov);
- let val = -quiescence_search(game, sc, hash, decrease_mate_in(-beta), decrease_mate_in(-alpha), depth - 1);
- game.undo_move(undo);
- if sc.stopping {
- return alpha
- }
- if val >= beta {
- return beta;
- }
- if increase_mate_in(val) > alpha {
- alpha = increase_mate_in(val);
- }
- }
- return alpha;
- }
- pub fn perft(game: &mut Game, sc: &mut SearchControl, depth: i32) -> bool {
- let moves = generate_legal_moves(game, game.turn);
- if depth <= 1 {
- sc.nodes += moves.len();
- if sc.nodes % 1024 < moves.len() {
- if (sc.check)() {
- return true;
- }
- }
- return false;
- }
-
- for mov in moves {
- let undo = game.apply(mov);
- let do_return = perft(game, sc, depth - 1);
- game.undo_move(undo);
- if do_return {
- return true;
- }
- }
- return false;
- }
- #[cfg(test)]
- mod tests {
- use super::*;
- #[test]
- fn test_move_generation() {
- let positions = [
- "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8",
- "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10"
- ];
- let perft_results: [Vec<usize>; 2] = [
- vec![44, 1486, 62379, 2103487],
- vec![46, 2079, 89890, 3894594]
- ];
- for (i, &position) in positions.iter().enumerate() {
- let mut game = Game::from_fen_str(position).unwrap();
- for (j, &p_res) in perft_results[i].iter().enumerate() {
- let depth = j + 1;
- let mut check_fn = || false;
- let mut rt = RepetitionTable::new();
- let mut sc = SearchControl::new(&mut check_fn, &mut rt, depth as _);
- perft(&mut game, &mut sc, depth as _);
- assert_eq!(sc.nodes, p_res);
- }
- }
- }
- }
|