use bitboard::Bitboard; use board::Board; use search::*; use movegen::*; use std::io::BufRead; use std::process::exit; use std::sync::mpsc::{Sender, self}; use std::time::{Duration, Instant}; use zobrist::ZobristTable; use std::thread; use std::sync::{Arc, RwLock}; use crate::ttable::{RepetitionTable, Cache}; use crate::{uci, zobrist}; /// /// Message types that are sent from the uci interface to the engine thread. /// pub enum Command { Uci, Position{ pos: Board, moves: Vec }, SetPiece(Bitboard), Go(SearchInfo), SetOption{ name: String, val: String }, Print, IsReady, Ping, Stop, NewGame, GetState, GetInfo, Quit, } pub enum EngineOptionType { Spin{ min: i64, max: i64, default: i64 }, } /// /// 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 } #[derive(PartialEq)] pub enum SearchMessage { Stop } /// /// Stores the state of the chess engine. /// pub struct Engine { board: Board, move_history: Arc>, hash: Arc>, zobrist_table: Arc, search_thread: Option, } struct SearchThread { channel: Sender, join_handle: thread::JoinHandle, } 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() -> Self { Engine { board: Board::default(), move_history: Arc::new(RwLock::new(RepetitionTable::new())), hash: Arc::new(RwLock::new(Cache::new_in_megabytes(4))), zobrist_table: Arc::new(ZobristTable::new()), search_thread: None, } } pub fn run(&mut self) { for line_m in std::io::stdin().lock().lines() { let line = line_m.unwrap(); //println!("received line: {}", line); let command = uci::parse_command(&line); if let Ok(cmd) = command { self.run_command(cmd); } else if let Err(err) = command { println!("Error parsing command: {}", err); } } } fn run_command(&mut self, cmd: Command) { match cmd { Command::Uci => { println!("id name bishop 1.0"); println!("id author Geile Siech"); println!("option name Hash type spin default 4 min 0 max 100000000"); println!("uciok"); }, Command::IsReady => { println!("readyok"); }, Command::NewGame => { }, Command::Position { pos, moves } => { self.set_position(pos, &moves); }, Command::SetOption { name, val } => { self.set_option(&name, &val); }, Command::Go(si) => { self.stop_search(); self.start_search(&si); }, Command::Stop => { self.stop_search(); }, Command::Quit => { exit(0); } _ => { println!("not yet implemented!"); } } } fn set_position(&mut self, pos: Board, moves: &Vec) { generate_legal_moves_new(&mut pos.clone(), WHITE, false); self.board = pos; // ensure correct zobrist hash self.board.zobrist_hash = self.board.calculate_zobrist(zobrist::get_zobrist()); let mut move_history = self.move_history.write().unwrap(); move_history.increment(self.board.calculate_zobrist(&self.zobrist_table)); if !moves.is_empty() { move_history.clear(); } for mov in moves { let m = self.board.parse_move(&mov); if let Ok(mm) = m { self.board.apply(mm); if mm.is_capture() { move_history.clear(); } move_history.increment(self.board.calculate_zobrist(&self.zobrist_table)); } else { println!("{}", self.board.beautiful_print()); println!("error parsing move {}: {}", mov, m.err().unwrap_or(String::new())); break; } } } fn start_search(&mut self, si: &SearchInfo) { let (snd, rcv) = mpsc::channel::(); let join_handle = if si.perft { let depth = si.depth.unwrap_or(0); let mut pc = SearchControl::new_perft(&self.board, rcv, depth); let search_rtn = move || { let before = Instant::now(); let rv = pc.perft(depth); if !rv { let after = Instant::now(); let nodes = pc.nodes; let duration = after - before; let nps = (nodes * 1000).checked_div(duration.as_millis() as _); println!("finished in {} ms", duration.as_millis()); println!("nodes: {} nps: {}", nodes, nps.unwrap_or(0)); } rv }; thread::spawn(search_rtn) } else { let mut sc = SearchControl::new(&self.board, rcv, self.hash.clone(), self.move_history.clone()); sc.hash = self.hash.clone(); sc.movetime = si.movetime.map(|ms| Duration::from_millis(ms as _)); if self.board.turn == WHITE { sc.remaining_time = si.wtime.map(|ms| Duration::from_millis(ms as _)); } else if self.board.turn == BLACK { sc.remaining_time = si.btime.map(|ms| Duration::from_millis(ms as _)); } let depth = si.depth; let search_rtn = move || sc.iterative_deepening(depth); thread::spawn(search_rtn) }; self.search_thread = Some(SearchThread { channel: snd, join_handle }); //self.search_threads.push(st); } fn stop_search(&mut self) { if let Some(st) = std::mem::replace(&mut self.search_thread, None) { let _msg_res = st.channel.send(SearchMessage::Stop); let _res = st.join_handle.join().unwrap(); } } fn newgame(&mut self) { self.set_position(Board::default(), &Vec::new()); self.hash.write().unwrap().clear(); self.move_history.write().unwrap().clear(); } fn set_option(&mut self, name: &str, val: &str) { match name { "Hash" => { let size = val.parse::(); if let Ok(megabytes) = size { self.hash = Arc::new(RwLock::new(Cache::new_in_megabytes(megabytes))); } else { println!("invalid hash size"); } }, n => { println!("unknown option '{}'", n); } } } }