use bitboard::Bitboard; use game::Game; use search::*; use movegen::*; use evaluate::PosValue; use log::{info}; use std::time::{Duration, Instant}; use std::collections::{VecDeque}; use zobrist::ZobristTable; use hash::{Cache, RepetitionTable}; use std::sync::Arc; use std::sync::mpsc::{Receiver, Sender}; use std::panic; /// /// Message types that are sent from the uci interface to the engine thread. /// pub enum EngineMsg { SetBoard{ pos: Game, moves: Vec }, SetPiece(Bitboard), Search(SearchInfo), Print, IsReady, Ping, Stop, NewGame, GetState, GetInfo, } /// /// Structure holding information for a search command. /// pub struct SearchInfo { pub depth: Option, pub movetime: Option, pub wtime: Option, pub btime: Option, pub winc: Option, pub binc: Option, pub movestogo: Option, pub perft: bool, pub infinite: bool } pub enum InterfaceMsg { } /// /// Stores the state of the chess engine. /// /// The engine is run in a dedicated thread and will receive messages on [Engine::r] /// pub struct Engine { game: Game, move_history: RepetitionTable, messages: VecDeque, r: Receiver, #[allow(unused)] s: Sender, hash: Cache, zobrist_table: Arc } impl SearchInfo { pub fn new() -> Self { SearchInfo { depth: None, movetime: None, wtime: None, btime: None, winc: None, binc: None, movestogo: None, perft: false, infinite: false } } } impl Engine { pub fn new(r: Receiver, s: Sender) -> Self { let mut eng = Engine { game: Game::default(), move_history: RepetitionTable::new(), messages: VecDeque::new(), r, s, hash: Cache::new(), zobrist_table: Arc::new(ZobristTable::new()) }; eng.game.zobrist = Some((eng.zobrist_table.clone(), eng.game.calculate_zobrist(&eng.zobrist_table))); eng } pub fn run(&mut self) { panic::set_hook(Box::new(|panic_info| { if let Some(s) = panic_info.payload().downcast_ref::<&str>() { info!("panic occurred: {s:?}"); } else { info!("panic occurred"); } })); while let Some(msg) = self.dequeue_message() { match msg { EngineMsg::SetBoard{ pos, moves } => { self.set_position(pos, &moves); }, EngineMsg::Search(ref info) => { self.start_search(info); }, EngineMsg::Print => { println!("{}", self.game.beautiful_print()); }, EngineMsg::IsReady => { println!("readyok"); } _ => {} } } } fn set_position(&mut self, pos: Game, moves: &Vec) { self.game = pos; self.game.zobrist = Some((self.zobrist_table.clone(), self.game.calculate_zobrist(&self.zobrist_table))); self.move_history.clear(); self.move_history.increment(self.game.calculate_zobrist(&self.zobrist_table)); for mov in moves { let m = self.game.parse_move(&mov); if let Ok(mm) = m { self.game.apply(mm); self.move_history.increment(self.game.calculate_zobrist(&self.zobrist_table)); } else { println!("{}", self.game.beautiful_print()); println!("error parsing move {}", mov); break; } } } /** * blocks until a message is available */ fn dequeue_message(&mut self) -> Option { if self.messages.is_empty() { self.r.recv().ok() } else { self.messages.pop_front() } } #[allow(unused)] fn try_dequeue_message(&mut self) -> Option { if self.messages.is_empty() { self.r.try_recv().ok() } else { self.messages.pop_front() } } fn reconstruct_pv(game: &Game, hash: &Cache) -> Vec { let mut pv: Vec = Vec::new(); let mut g = game.clone(); loop { let mce = hash.lookup(&g); if let Some(ce) = mce { pv.push(ce.mov); g.apply(ce.mov); } else { break; } } return pv; } fn start_search(&mut self, si: &SearchInfo) { if let None = self.game.zobrist { self.game.zobrist = Some((self.zobrist_table.clone(), self.game.calculate_zobrist(&self.zobrist_table))); } let side = self.game.turn; let receiver = &mut self.r; let messages = &mut self.messages; let before = Instant::now(); let mut depth = 1; let mut check_fn = || -> bool { if !messages.is_empty() { if let EngineMsg::Stop = messages[0] { return true; } } if let Ok(msg) = receiver.try_recv() { if let EngineMsg::Stop = msg { messages.push_back(msg); return true; } if let EngineMsg::IsReady = msg { println!("readyok"); return false; } else { messages.push_back(msg); } } if let Some(millis) = si.movetime { if before.elapsed() > Duration::from_millis(millis as _) { return true; } } else if side == WHITE { if let Some(wtime) = si.wtime { if before.elapsed() > Duration::from_millis((wtime / 17) as _) { return true; } } } else if side == BLACK { if let Some(btime) = si.btime { if before.elapsed() > Duration::from_millis((btime / 17) as _) { return true; } } } return false; }; let mut sc = SearchControl::new(&mut check_fn, &mut self.move_history, 0); let mut best_move = Move::default(); let mut best_val: PosValue; if si.perft { if let Some(dep) = si.depth { depth = dep; } let invalid = perft(&mut self.game, &mut sc, depth as i32); if !invalid { let elapsed = before.elapsed(); let nodes = sc.nodes; let nps = (nodes as f64 / elapsed.as_nanos() as f64 * 1000000000.0) as i64; println!("info depth {} nodes {} nps {}", depth, nodes, nps); } return; } let mut alpha = crate::evaluate::MIN_VALUE; let mut beta = crate::evaluate::MAX_VALUE; let window_size = 10; let mut is_windowed = false; loop { sc.set_depth(depth); let search_result = search(&mut self.game, &mut sc, &mut self.hash, alpha, beta, depth as i32); if let SearchResult::Invalid = search_result { if is_windowed { alpha = crate::evaluate::MIN_VALUE; beta = crate::evaluate::MAX_VALUE; is_windowed = false; continue; } } if let SearchResult::Finished(bm, bv) = search_result { info!("depth: {} bm: {}, bv: {}", depth, bm.to_string(), bv); if bv >= beta || bv <= alpha { alpha = crate::evaluate::MIN_VALUE; beta = crate::evaluate::MAX_VALUE; is_windowed = false; continue; } else { //alpha = crate::evaluate::MIN_VALUE; //beta = crate::evaluate::MAX_VALUE; is_windowed = true; alpha = bv - window_size; beta = bv + window_size; } best_move = bm; best_val = bv; let elapsed = before.elapsed(); let nodes = sc.nodes; let nps = (nodes as f64 / elapsed.as_nanos() as f64 * 1000000000.0) as i64; let cp = best_val as i64; let hashfull = self.hash.fullness_permill(); let pv_array = Self::reconstruct_pv(&self.game, &self.hash); 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(bv) { let turns = plies / 2; let infostring = format!("info depth {} score mate {} time {} nodes {} nps {} hashfull {} pv{}", depth, turns, elapsed.as_millis(), nodes, nps, hashfull, pv); println!("{}", infostring); info!("{}", infostring); } else { let infostring = format!("info depth {} score cp {} time {} nodes {} nps {} hashfull {} pv{}", depth, cp, elapsed.as_millis(), nodes, nps, hashfull, pv); println!("{}", infostring); info!("{}", infostring); } depth += 1; } else if let SearchResult::Cancelled(Some((bm, _bv))) = search_result { if best_move == Move::Nullmove { best_move = bm; //best_val = bv; } break; } else { break; } if let Some(d) = si.depth { if depth > d { break; } } } info!("bestmove {}", best_move.to_string()); println!("bestmove {}", best_move.to_string()); } } /* fn dequeue_message(queue: &mut VecDeque, r: &Receiver) -> Option { if queue.is_empty() { r.recv().ok() } else { queue.pop_front() } } pub fn run_engine(r: Receiver, _s: Sender) { let mut game = Game::default(); let mut messages: VecDeque = VecDeque::new(); loop { let msg = dequeue_message(&mut messages, &r).unwrap(); match msg { EngineMsg::SetBoard(g) => { game = g; }, EngineMsg::SetPiece(_) => { println!("SetPiece") }, EngineMsg::Search(info) => { match info { SearchInfo::Depth(depth) => { let mut check_fn = || -> bool { if let Ok(msg) = r.try_recv() { if let EngineMsg::Stop = msg { return true; } else { messages.push_back(msg); } } return false; }; let mut sc = SearchControl{ nodes: 0, check: &mut check_fn }; let before = Instant::now(); let best_move = search(&game, &mut sc, depth as i32); let time = before.elapsed(); let nps = (sc.nodes as f64 / time.as_millis() as f64 * 1000.0) as i64; info!("bestmove {}", best_move.to_string()); info!("searched {} nodes in {} ms ({} nodes/s)", sc.nodes, time.as_millis(), (sc.nodes as f64 / time.as_millis() as f64 * 1000.0) as i64); println!("info nps {}", nps.to_string()); println!("bestmove {}", best_move.to_string()); }, SearchInfo::Movetime(millis) => { let before = Instant::now(); let mut depth = 1; let mut check_fn = || -> bool { if let Ok(msg) = r.try_recv() { if let EngineMsg::Stop = msg { return true; } else { messages.push_back(msg); } } if before.elapsed() > Duration::from_millis(millis as _) { return true; } return false; }; let mut sc = SearchControl{ nodes: 0, check: &mut check_fn }; let mut best_move = Move::default(); loop { let bm = search(&game, &mut sc, depth as i32); if bm != Move::default() { best_move = bm; } else { break; } depth += 1; } println!("bestmove {}", best_move.to_string()); }, _ => {} } }, EngineMsg::Ping => { println!("Ping") }, EngineMsg::Stop => { println!("Stop") }, EngineMsg::NewGame => { //println!("NewGame") }, EngineMsg::GetState => { println!("GetState") }, EngineMsg::GetInfo => { println!("GetInfo") }, } //println!("{}", game.beautiful_print()); //let moves = generate_moves(&game, WHITE); } } */