diff --git a/pleco_engine/Cargo.toml b/pleco_engine/Cargo.toml index ae631e1..b6c6d29 100644 --- a/pleco_engine/Cargo.toml +++ b/pleco_engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pleco_engine" -version = "0.1.0" +version = "0.1.1" # Reminder to change in ./engine.rs authors = ["Stephen Fleischman "] description = "A blazingly-fast Chess AI." homepage = "https://github.com/sfleischman105/Pleco" diff --git a/pleco_engine/src/consts.rs b/pleco_engine/src/consts.rs index c61da2b..b877c3b 100644 --- a/pleco_engine/src/consts.rs +++ b/pleco_engine/src/consts.rs @@ -33,11 +33,10 @@ lazy_static! { pub fn init_globals() { INITALIZED.call_once(|| { - prelude::init_statics(); + prelude::init_statics(); // Initialize static tables compiler_fence(Ordering::SeqCst); - lazy_static::initialize(&TT_TABLE); - threadpool::init_threadpool(); - + lazy_static::initialize(&TT_TABLE); // Transposition Table + threadpool::init_threadpool(); // Make Threadpool }); } diff --git a/pleco_engine/src/engine.rs b/pleco_engine/src/engine.rs index 010b7e4..0818bd1 100644 --- a/pleco_engine/src/engine.rs +++ b/pleco_engine/src/engine.rs @@ -19,7 +19,7 @@ use num_cpus; pub static ID_NAME: &str = "Pleco"; pub static ID_AUTHORS: &str = "Stephen Fleischman"; -pub static VERSION: &str = "0.0.8"; +pub static VERSION: &str = "0.1.1"; #[derive(PartialEq)] enum SearchType { @@ -58,7 +58,7 @@ impl PlecoSearcher { "" => continue, "uci" => self.uci_startup(), "setoption" => self.apply_option(&full_command), - "options" | "alloptions" => {}, + "options" | "alloptions" => self.options.display_all(), "ucinewgame" => self.clear_search(), "isready" => println!("readyok"), "position" => { @@ -84,7 +84,6 @@ impl PlecoSearcher { _ => print!("Unknown Command: {}",full_command) } self.apply_all_options(); - } } @@ -103,11 +102,11 @@ impl PlecoSearcher { args.next().unwrap(); // setoption if let Some(non_name) = args.next() { if non_name != "name" { - println!("setoption `name`"); + println!("setoption [name]"); return; } } else { - println!("setoption `name`"); + println!("setoption name [name] "); return; } let mut name = String::new(); @@ -116,7 +115,7 @@ impl PlecoSearcher { if let Some(third_arg) = args.next() { //[should be name of the option] name += third_arg; } else { - println!("setoption needs a name!"); + println!("setoption name [name]"); return; } @@ -137,10 +136,8 @@ impl PlecoSearcher { } } - println!("name :{}: value :{}:",name,value); - if !self.options.apply_option(&name, &value) { - println!("unable to apply option: {}",full_command); + println!("unable to apply option: '{}'", full_command); } else { self.apply_all_options(); } @@ -170,7 +167,6 @@ impl PlecoSearcher { pub fn search(&mut self, board: &Board, limit: &PreLimits) { self.search_mode = SearchType::Search; threadpool().uci_search(board, &(limit.clone().create())); - } pub fn halt(&mut self) { diff --git a/pleco_engine/src/main.rs b/pleco_engine/src/main.rs index 29d3c3a..c4c23c2 100644 --- a/pleco_engine/src/main.rs +++ b/pleco_engine/src/main.rs @@ -1,12 +1,7 @@ extern crate pleco_engine; - - use pleco_engine::engine::PlecoSearcher; - - fn main() { let mut s = PlecoSearcher::init(true); - println!("Turtle"); s.uci(); } diff --git a/pleco_engine/src/search/mod.rs b/pleco_engine/src/search/mod.rs index c20dec8..e7628b9 100644 --- a/pleco_engine/src/search/mod.rs +++ b/pleco_engine/src/search/mod.rs @@ -291,7 +291,9 @@ impl Searcher { self.failed_low = false; } + // The depth to start searching at based on the thread ID. let start_ply: u16 = START_PLY[self.id % THREAD_DIST]; + // The number of plies to skip each iteration. let skip_size: u16 = SKIP_SIZE[self.id % THREAD_DIST]; let mut depth: u16 = start_ply + 1; @@ -332,6 +334,7 @@ impl Searcher { // search! best_value = self.search::(alpha, beta, stack.ply_zero(),depth); + // Sort root moves based on the scores self.root_moves().sort(); if self.stop() { @@ -423,8 +426,6 @@ impl Searcher { unstable_factor *= self.previous_time_reduction.powf(0.42) / time_reduction; // Stop the search if we have only one legal move, or if available time elapsed -// let new_time = (TIMER.ideal_time() as f64 * unstable_factor as f64 * improving_factor as f64 / 602.0) as i64; -// println!("new time: {}", new_time); if self.root_moves().len() == 1 || TIMER.elapsed() >= (TIMER.ideal_time() as f64 * unstable_factor as f64 * improving_factor as f64 / 609.0) as i64 { threadpool().set_stop(true); @@ -494,10 +495,12 @@ impl Searcher { // increment the next ply ss.incr().ply = ply + 1; + // Set the killer moves two plies in advance to be nothing. ss.offset(2).killers = [BitMove::null(); 2]; ss.current_move = BitMove::null(); ss.cont_history = &mut self.cont_history[(Piece::None, SQ(0))] as *mut _; + // square the previous piece moved. let prev_sq: SQ = ss.offset(-1).current_move.get_src(); // probe the transposition table @@ -544,11 +547,11 @@ impl Searcher { // Get and set the position eval if in_check { - // A checking position should never be evaluated + // A checking position should never be evaluated. We go directly to the moves loop + // now. pos_eval = NONE; } else { // No checks from here on until moves loop - if tt_hit { pos_eval = if tt_entry.eval as i32 == NONE { self.eval() @@ -588,18 +591,22 @@ impl Searcher { } } + // Continuation histories of the previous moved from 1, 2, and 4 moves ago. let cont_hists = [ss.offset(-1).cont_history, ss.offset(-2).cont_history, ptr::null(), ss.offset(-4).cont_history]; + let counter: BitMove = self.counter_moves[(self.board.piece_at_sq(prev_sq),prev_sq)]; + let mut move_picker = MovePicker::main_search(&self.board, depth as i16, &self.main_history, &self.capture_history, &cont_hists as *const _, tt_move, - &ss.killers, BitMove::null()); + &ss.killers, + counter); while let Some(mov) = move_picker.next(false) { if self.board.legal_move(mov) { @@ -617,8 +624,13 @@ impl Searcher { // prefetch the zobrist key self.tt.prefetch(self.board.zobrist()); - // At higher depths, only do a lower - let do_full_depth: bool = if depth >= 4 && moves_played > 1 && !mov.is_capture() && !mov.is_promo() { + // At higher depths, do a search of a lower ply to see if this move is + // worth searching. We don't do this for capturing or promotion moves. + let do_full_depth: bool = if depth >= 4 + && moves_played > 1 + && !mov.is_capture() + && !mov.is_promo() { + let new_depth = if in_check || gives_check {depth - 2} else {depth - 3}; value = -self.search::(-(alpha+1), -alpha, ss.incr(), new_depth); value > alpha @@ -626,6 +638,7 @@ impl Searcher { !is_pv || moves_played > 1 }; + // If the value is potentially better, do a full depth search. if do_full_depth { value = if depth <= 1 { if gives_check { -self.qsearch::(-(alpha+1), -alpha, ss.incr(), 0) @@ -635,6 +648,8 @@ impl Searcher { }; } + // If on the PV node and the node might be a continuation, search for a full depth + // with a PV value. if is_pv && (moves_played == 1 || (value > alpha && (at_root || value < beta))) { value = if depth <= 1 { if gives_check { -self.qsearch::(-(alpha+1), -alpha, ss.incr(), 0)} @@ -648,6 +663,7 @@ impl Searcher { assert!(value > NEG_INFINITE); assert!(value < INFINITE ); + // Unsafe to return any other value here when the threads are stopped. if self.stop() { return ZERO; } @@ -670,6 +686,7 @@ impl Searcher { rm.score = NEG_INFINITE; } } + // If we have a new best move at root, update the nmber of best_move changes. if incr_bmc { self.best_move_changes += 1.0; } @@ -693,6 +710,7 @@ impl Searcher { } } + // If best_move wasnt found, add it to the list of quiets / captures that failed if mov != best_move { if capture_or_promotion && captures_count < 32 { captures_searched[captures_count] = mov; @@ -705,13 +723,15 @@ impl Searcher { } } + // check for checkmate or draw. if moves_played == 0 { - if self.board.in_check() { + if in_check { return -MATE as i32 + (ply as i32); } else { return DRAW as i32; } } else if best_move != BitMove::null() { + // If the best move is quiet, update move heuristics if !self.board.is_capture_or_promotion(best_move) { self.update_quiet_stats(best_move, ss, &quiets_searched[0..quiets_count], @@ -722,6 +742,7 @@ impl Searcher { stat_bonus(depth)); } + // penalize quiet TT move that was refuted. if ss.offset(-1).move_count == 1 && self.board.piece_captured_last_turn().is_none() { let piece_at_sq = self.board.piece_at_sq(prev_sq); self.update_continuation_histories(ss.offset(-1), @@ -733,6 +754,7 @@ impl Searcher { && self.board.piece_captured_last_turn().is_none() && ss.offset(-1).current_move.is_okay() { let piece_at_sq = self.board.piece_at_sq(prev_sq); + // bonus for counter move that failed low self.update_continuation_histories(ss.offset(-1), piece_at_sq, prev_sq, @@ -777,11 +799,11 @@ impl Searcher { let futility_base: Value; let mut futility_value: Value; let mut evasion_prunable: bool; - #[allow(unused_variables)] let mut moves_played: u32 = 0; let old_alpha = alpha; - let tt_depth: i16 = if in_check || rev_depth >= 0 {0} else {-1}; + // Determine whether or not to include checking moves. + let tt_depth: i16 = if in_check || rev_depth >= 0 {0} else {-1}; if ply >= MAX_PLY { if !in_check { @@ -805,6 +827,7 @@ impl Searcher { return tt_value; } + // Evaluate the position statically. if in_check { pos_eval = NONE; best_value = NEG_INFINITE; @@ -852,8 +875,8 @@ impl Searcher { while let Some(mov) = move_picker.next(false) { let gives_check: bool = self.board.gives_check(mov); - moves_played += 1; + // futility pruning if !in_check && !gives_check @@ -878,6 +901,7 @@ impl Searcher { && best_value > MATED_IN_MAX_PLY && !self.board.is_capture(mov); + // Don't search moves that lead to a negative static exhange if (!in_check || evasion_prunable) && !self.board.see_ge(mov, 0) { continue; } @@ -903,6 +927,7 @@ impl Searcher { assert!(value > NEG_INFINITE); assert!(value < INFINITE ); + // Check for new best value if value > best_value { best_value = value; @@ -923,6 +948,7 @@ impl Searcher { } } + // If in checkmate, return so if in_check && best_value == NEG_INFINITE { return -MATE + ss.ply as i32; } @@ -941,6 +967,7 @@ impl Searcher { best_value } + /// If a new capturing best move is found, updating sorting heuristics. fn update_capture_stats(&mut self, mov: BitMove, captures: &[BitMove], bonus: i32) { let cap_hist: &mut CapturePieceToHistory = &mut self.capture_history; let mut moved_piece: Piece = self.board.moved_piece(mov); @@ -955,6 +982,7 @@ impl Searcher { } + /// If a new quiet best move is found, updating sorting heuristics. fn update_quiet_stats(&mut self, mov: BitMove, ss: &mut Stack, quiets: &[BitMove], bonus: i32) { if ss.killers[0] != mov { @@ -985,6 +1013,8 @@ impl Searcher { } } + // updates histories of the move pairs formed by the current move of one, two, and four + // moves ago fn update_continuation_histories(&mut self, ss: &mut Stack, piece: Piece, to: SQ, bonus: i32) { for i in [1,2,4].iter() { let i_ss: &mut Stack = ss.offset(-i as isize); @@ -1119,5 +1149,5 @@ fn stat_bonus(depth: u16) -> i32 { #[test] fn how_big() { let x = mem::size_of::() / 1000; - println!("size {} KB",x); -} \ No newline at end of file + println!("size of searcher: {} KB",x); +} diff --git a/pleco_engine/src/threadpool/mod.rs b/pleco_engine/src/threadpool/mod.rs index 841a129..b77e4bb 100644 --- a/pleco_engine/src/threadpool/mod.rs +++ b/pleco_engine/src/threadpool/mod.rs @@ -22,22 +22,23 @@ use search::Searcher; use consts::*; +const KILOBYTE: usize = 1000; +const THREAD_STACK_SIZE: usize = 18000 * KILOBYTE; + +// The Global threadpool! pub static mut THREADPOOL: NonNull = unsafe {NonNull::new_unchecked(ptr::null_mut())}; static THREADPOOL_INIT: Once = ONCE_INIT; -const KILOBYTE: usize = 1000; -const THREAD_STACK_SIZE: usize = 18000 * KILOBYTE; - pub fn init_threadpool() { THREADPOOL_INIT.call_once(|| { - unsafe { + // We have a spawned thread create all structures, as a stack overflow can + // occur otherwise let builder = thread::Builder::new() .name("Starter".to_string()) .stack_size(THREAD_STACK_SIZE); - let handle = scoped::builder_spawn_unsafe(builder, - move || { + let handle = scoped::builder_spawn_unsafe(builder, move || { let layout = Layout::new::(); let result = Heap.alloc_zeroed(layout); let new_ptr: *mut ThreadPool = match result { @@ -52,7 +53,7 @@ pub fn init_threadpool() { }); } -/// Returns access to the global thread pool +/// Returns access to the global thread pool. pub fn threadpool() -> &'static mut ThreadPool { unsafe { THREADPOOL.as_mut() @@ -64,6 +65,7 @@ lazy_static! { pub static ref TIMER: TimeManager = TimeManager::uninitialized(); } +// Dummy struct to allow us to pass a pointer into a spawned thread. struct SearcherPtr { ptr: UnsafeCell<*mut Searcher> } @@ -71,7 +73,7 @@ struct SearcherPtr { unsafe impl Sync for SearcherPtr {} unsafe impl Send for SearcherPtr {} -/// The thread-pool for the chess engine +/// The thread-pool for the chess engine. pub struct ThreadPool { /// Access to each thread's Structure pub threads: Vec>, @@ -91,7 +93,7 @@ pub struct ThreadPool { // and lastly determining the "best move" from all the threads. /// // While we spawn all the other threads, We mostly communicate with the -// MainThread to do anything useful. We let the mainthread handle anything fun. +// MainThread to do anything useful. The mainthread handles anything fun. // The goal of the ThreadPool is to be NON BLOCKING, unless we want to await a // result. impl ThreadPool { @@ -132,21 +134,11 @@ impl ThreadPool { }; } - /// Returns a pointer to the main thread. - fn main(&mut self) -> &mut Searcher { - unsafe { - let main_thread: *mut Searcher = *self.threads.get_unchecked(0).get(); - return &mut *main_thread; - } - } - - /// Returns the number of threads - #[inline(always)] - fn size(&self) -> usize { - self.threads.len() - } - /// Allocates a thread structure and pushes it to the threadstack. + /// + /// This does not spawn a thread, just creates the structure in the heap for the thread. + /// + /// Only to be called by `attach_thread()`. fn create_thread(&mut self) -> SearcherPtr { let len: usize = self.threads.len(); let layout = Layout::new::(); @@ -163,6 +155,19 @@ impl ThreadPool { } } + /// Returns the number of threads + #[inline(always)] + pub fn size(&self) -> usize { + self.threads.len() + } + + /// Returns a pointer to the main thread. + fn main(&mut self) -> &mut Searcher { + unsafe { + let main_thread: *mut Searcher = *self.threads.get_unchecked(0).get(); + return &mut *main_thread; + } + } /// Sets the use of standard out. This can be changed mid search as well. #[inline(always)]