|
@@ -7,15 +7,16 @@ use log::{info};
|
|
|
use std::time::{Duration, Instant};
|
|
|
use std::collections::{VecDeque};
|
|
|
use zobrist::ZobristTable;
|
|
|
-use hash::Cache;
|
|
|
+use hash::{Cache, RepetitionTable};
|
|
|
|
|
|
use std::sync::Arc;
|
|
|
use std::sync::mpsc::{Receiver, Sender};
|
|
|
|
|
|
pub enum EngineMsg {
|
|
|
- SetBoard(Game),
|
|
|
+ SetBoard{ pos: Game, moves: Vec<String> },
|
|
|
SetPiece(Bitboard),
|
|
|
Search(SearchInfo),
|
|
|
+ Print,
|
|
|
Ping,
|
|
|
Stop,
|
|
|
NewGame,
|
|
@@ -23,18 +24,15 @@ pub enum EngineMsg {
|
|
|
GetInfo,
|
|
|
}
|
|
|
|
|
|
-pub enum SearchInfo {
|
|
|
- Depth(isize),
|
|
|
- Movetime(isize),
|
|
|
- Time {
|
|
|
- wtime: isize,
|
|
|
- btime: isize,
|
|
|
- winc: isize,
|
|
|
- binc: isize,
|
|
|
- movestogo: isize
|
|
|
- },
|
|
|
- Infinite,
|
|
|
-
|
|
|
+pub struct SearchInfo {
|
|
|
+ pub depth: Option<i32>,
|
|
|
+ pub movetime: Option<i32>,
|
|
|
+ pub wtime: Option<isize>,
|
|
|
+ pub btime: Option<isize>,
|
|
|
+ pub winc: Option<isize>,
|
|
|
+ pub binc: Option<isize>,
|
|
|
+ pub movestogo: Option<isize>,
|
|
|
+ pub infinite: bool
|
|
|
}
|
|
|
|
|
|
pub enum InterfaceMsg {
|
|
@@ -42,6 +40,7 @@ pub enum InterfaceMsg {
|
|
|
|
|
|
pub struct Engine {
|
|
|
game: Game,
|
|
|
+ move_history: RepetitionTable,
|
|
|
messages: VecDeque<EngineMsg>,
|
|
|
r: Receiver<EngineMsg>,
|
|
|
s: Sender<InterfaceMsg>,
|
|
@@ -49,10 +48,26 @@ pub struct Engine {
|
|
|
zobrist_table: Arc<ZobristTable>
|
|
|
}
|
|
|
|
|
|
+impl SearchInfo {
|
|
|
+ pub fn new() -> Self {
|
|
|
+ SearchInfo {
|
|
|
+ depth: None,
|
|
|
+ movetime: None,
|
|
|
+ wtime: None,
|
|
|
+ btime: None,
|
|
|
+ winc: None,
|
|
|
+ binc: None,
|
|
|
+ movestogo: None,
|
|
|
+ infinite: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
impl Engine {
|
|
|
pub fn new(r: Receiver<EngineMsg>, s: Sender<InterfaceMsg>) -> Self {
|
|
|
let mut eng = Engine {
|
|
|
game: Game::default(),
|
|
|
+ move_history: RepetitionTable::new(),
|
|
|
messages: VecDeque::new(),
|
|
|
r, s,
|
|
|
hash: Cache::new(),
|
|
@@ -65,11 +80,29 @@ impl Engine {
|
|
|
pub fn run(&mut self) {
|
|
|
while let Some(msg) = self.dequeue_message() {
|
|
|
match msg {
|
|
|
- EngineMsg::SetBoard(g) => {
|
|
|
- self.game = g;
|
|
|
+ EngineMsg::SetBoard{ pos, moves } => {
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
},
|
|
|
EngineMsg::Search(ref info) => {
|
|
|
self.start_search(info);
|
|
|
+ },
|
|
|
+ EngineMsg::Print => {
|
|
|
+ println!("{}", self.game.beautiful_print());
|
|
|
}
|
|
|
_ => {}
|
|
|
}
|
|
@@ -88,94 +121,106 @@ impl Engine {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ fn try_dequeue_message(&mut self) -> Option<EngineMsg> {
|
|
|
+ if self.messages.is_empty() {
|
|
|
+ self.r.try_recv().ok()
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ self.messages.pop_front()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
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)));
|
|
|
}
|
|
|
- match si {
|
|
|
- SearchInfo::Depth(depth) => {
|
|
|
- let receiver = &mut self.r;
|
|
|
- let messages = &mut self.messages;
|
|
|
- let mut check_fn = || -> bool {
|
|
|
- if let Ok(msg) = receiver.try_recv() {
|
|
|
- if let EngineMsg::Stop = msg {
|
|
|
- return true;
|
|
|
- }
|
|
|
- else {
|
|
|
- messages.push_back(msg);
|
|
|
- }
|
|
|
+ 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;
|
|
|
}
|
|
|
- return false;
|
|
|
- };
|
|
|
- let mut sc = SearchControl{ nodes: 0, check: &mut check_fn };
|
|
|
- let before = Instant::now();
|
|
|
- let search_result = search(&mut self.game, &mut sc, &mut self.hash, *depth as i32);
|
|
|
-
|
|
|
- if let SearchResult::Finished(best_move, best_val) = search_result {
|
|
|
- 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 receiver = &mut self.r;
|
|
|
- let messages = &mut self.messages;
|
|
|
- let mut check_fn = || -> bool {
|
|
|
- if let Ok(msg) = receiver.try_recv() {
|
|
|
- if let EngineMsg::Stop = msg {
|
|
|
- return true;
|
|
|
- }
|
|
|
- else {
|
|
|
- messages.push_back(msg);
|
|
|
- }
|
|
|
- }
|
|
|
- if before.elapsed() > Duration::from_millis(*millis as _) {
|
|
|
+ if let Ok(msg) = receiver.try_recv() {
|
|
|
+ if let EngineMsg::Stop = msg {
|
|
|
+ messages.push_back(msg);
|
|
|
return true;
|
|
|
}
|
|
|
- return false;
|
|
|
- };
|
|
|
+ else {
|
|
|
+ messages.push_back(msg);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- let mut sc = SearchControl{ nodes: 0, check: &mut check_fn };
|
|
|
- let mut best_move = Move::default();
|
|
|
- let mut best_val: PosValue = i32::max_value() as _;
|
|
|
- loop {
|
|
|
- let search_result = search(&mut self.game, &mut sc, &mut self.hash, depth as i32);
|
|
|
- if let SearchResult::Finished(bm, bv) = search_result {
|
|
|
- info!("depth: {} bm: {}, bv: {}", depth, bm.to_string(), bv);
|
|
|
- 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;
|
|
|
- if let Some(turns) = crate::evaluate::is_mate_in_p1(bv) {
|
|
|
- println!("info depth {} score mate {} time {} nodes {} nps {} pv {}", depth, turns, elapsed.as_millis(), nodes, nps, best_move.to_string());
|
|
|
- info!("info depth {} score mate {} time {} nodes {} nps {} pv {}", depth, turns, elapsed.as_millis(), nodes, nps, best_move.to_string());
|
|
|
- break;
|
|
|
- }
|
|
|
- else {
|
|
|
- println!("info depth {} score cp {} time {} nodes {} nps {} pv {}", depth, cp, elapsed.as_millis(), nodes, nps, best_move.to_string());
|
|
|
- info!("info depth {} score cp {} time {} nodes {} nps {} pv {}", depth, cp, elapsed.as_millis(), nodes, nps, best_move.to_string());
|
|
|
+ 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 / 35) as _) {
|
|
|
+ return true;
|
|
|
}
|
|
|
- depth += 1;
|
|
|
}
|
|
|
- else {
|
|
|
- break;
|
|
|
+ }
|
|
|
+ else if side == BLACK {
|
|
|
+ if let Some(btime) = si.btime {
|
|
|
+ if before.elapsed() > Duration::from_millis((btime / 35) as _) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- info!("bestmove {}", best_move.to_string());
|
|
|
+ return false;
|
|
|
+ };
|
|
|
|
|
|
- println!("bestmove {}", best_move.to_string());
|
|
|
- },
|
|
|
- _ => {}
|
|
|
+ let mut sc = SearchControl{ nodes: 0, check: &mut check_fn, move_history: &mut self.move_history, initial_depth: 0 };
|
|
|
+ let mut best_move = Move::default();
|
|
|
+ let mut best_val: PosValue;
|
|
|
+ loop {
|
|
|
+
|
|
|
+ sc.initial_depth = depth;
|
|
|
+ let search_result = search(&mut self.game, &mut sc, &mut self.hash, depth as i32);
|
|
|
+
|
|
|
+ if let SearchResult::Finished(bm, bv) = search_result {
|
|
|
+ info!("depth: {} bm: {}, bv: {}", depth, bm.to_string(), bv);
|
|
|
+ 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;
|
|
|
+ if let Some(plies) = crate::evaluate::is_mate_in_p1(bv) {
|
|
|
+ let turns = plies / 2;
|
|
|
+ println!("info depth {} score mate {} time {} nodes {} nps {} pv {}", depth, turns, elapsed.as_millis(), nodes, nps, best_move.to_string());
|
|
|
+ info!("info depth {} score mate {} time {} nodes {} nps {} pv {}", depth, turns, elapsed.as_millis(), nodes, nps, best_move.to_string());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ println!("info depth {} score cp {} time {} nodes {} nps {} pv {}", depth, cp, elapsed.as_millis(), nodes, nps, best_move.to_string());
|
|
|
+ info!("info depth {} score cp {} time {} nodes {} nps {} pv {}", depth, cp, elapsed.as_millis(), nodes, nps, best_move.to_string());
|
|
|
+ }
|
|
|
+ depth += 1;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if let Some(d) = si.depth {
|
|
|
+ if depth > d {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ info!("bestmove {}", best_move.to_string());
|
|
|
+ println!("bestmove {}", best_move.to_string());
|
|
|
}
|
|
|
}
|
|
|
|