diff --git a/src/enginemanager.rs b/src/enginemanager.rs index aff4771..04ef8be 100644 --- a/src/enginemanager.rs +++ b/src/enginemanager.rs @@ -1,6 +1,9 @@ use vampirc_uci::UciMove; -use crate::{gamemanager::GameManager, movetable::{noarc::NoArc, MoveTable}}; +use crate::{ + gamemanager::GameManager, + movetable::{noarc::NoArc, MoveTable}, +}; pub struct Engine { pub tbl: NoArc, @@ -8,3 +11,13 @@ pub struct Engine { pub board: GameManager, //pub set_new_game: bool, } + +impl Default for Engine { + fn default() -> Self { + Self { + tbl: NoArc::new(MoveTable::default()), + move_history: Vec::new(), + board: GameManager::default(), + } + } +} diff --git a/src/gamemanager/legal_moves/search.rs b/src/gamemanager/legal_moves/search.rs index 6202572..c307ff4 100644 --- a/src/gamemanager/legal_moves/search.rs +++ b/src/gamemanager/legal_moves/search.rs @@ -1,3 +1,8 @@ +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, +}; + use rayon::prelude::*; use crate::types::{Move, MoveType, PieceType, Square}; @@ -5,7 +10,19 @@ use crate::types::{Move, MoveType, PieceType, Square}; use super::{GameManager, MoveTable, NoArc}; /// A Negamax search routine that runs in parallel. -pub fn root_negamax(depth: u16, gm: GameManager, tbl: &NoArc) -> (Move, GameManager) { +pub fn root_negamax( + depth: u16, + gm: GameManager, + tbl: &NoArc, + flag: Arc, + best_move: Arc>, +) { + /* ************************************************************************************* */ + /* NOTE: This acquires the lock around best_move. If the lock is freed before the best */ + /* move has been written, the UCI thread will print out an invalid move. DO NOT */ + /* FREE THIS LOCK EARLY! */ + /* ************************************************************************************* */ + let mut state = best_move.lock().unwrap(); let moves = gm.legal_moves(tbl); if moves.len() == 0 { @@ -19,7 +36,7 @@ pub fn root_negamax(depth: u16, gm: GameManager, tbl: &NoArc) -> (Mov .into_par_iter() .map(|mv| { ( - -negamax(depth, -beta, -alpha, mv.3, &mv.4, tbl), + -negamax(depth, -beta, -alpha, mv.3, &mv.4, tbl, flag.clone()), ((mv.0, mv.1, mv.2, mv.3), mv.4), ) }) @@ -30,20 +47,17 @@ pub fn root_negamax(depth: u16, gm: GameManager, tbl: &NoArc) -> (Mov .map(|(s, movetuple)| (s, (movetuple.0, movetuple.1))) .collect(); - if depth % 2 == 0 { - scored_moves.sort_by(|a, b| a.0.cmp(&b.0)); - } else { - scored_moves.sort_by(|a, b| a.0.cmp(&b.0)); - } + scored_moves.sort_by(|a, b| a.0.cmp(&b.0)); let best = scored_moves .into_iter() - .inspect(|m| println!("{}: {}{}", m.0, m.1 .0 .1.to_str(), m.1 .0 .2.to_str())) + .inspect(|m| eprintln!("{}: {}{}", m.0, m.1 .0 .1.to_str(), m.1 .0 .2.to_str())) .last() .expect("Should be a move here!"); - println!("Best score: {}", best.0); - best.1 + state.0 = best.1 .0 .1; // from + state.1 = best.1 .0 .2; // to + state.2 = best.1 .0 .3; // movetype } fn negamax( @@ -53,8 +67,13 @@ fn negamax( movetype: MoveType, gm: &GameManager, tbl: &NoArc, + flag: Arc, ) -> i32 { - if depth == 0 { + if flag.load(Ordering::Relaxed) == false || depth == 0 { + // NOTE: Call quiesence search on the current position regardless of + // depth if the flag "continue searching" is false. We can't stop + // immediately without throwing out the work at this depth entirely, + // and I'm not that good at concurrent programs to make that work. capture_search(alpha, beta, movetype, gm, tbl) } else { let moves = gm.legal_moves(tbl); @@ -66,7 +85,15 @@ fn negamax( let mut score = i32::MIN + 1; for mv in moves { // Call negamax and negate it's return value. Enemy's alpha is our -beta & v.v. - score = score.max(-negamax(depth - 1, -beta, -alpha, mv.3, &mv.4, tbl)); + score = score.max(-negamax( + depth - 1, + -beta, + -alpha, + mv.3, + &mv.4, + tbl, + flag.clone(), + )); alpha = alpha.max(score); if alpha >= beta { break; diff --git a/src/main.rs b/src/main.rs index 732d2cb..3f53844 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,13 @@ #![allow(dead_code)] -use gamemanager::{legal_moves::search::root_negamax, GameManager}; -use movetable::{noarc, MoveTable}; +use std::{ + sync::{atomic::AtomicBool, Arc, Mutex}, + thread, +}; + +use enginemanager::Engine; +use types::{MoveType, Square}; + mod bitboard; mod enginemanager; mod gamemanager; @@ -10,14 +16,17 @@ mod types; mod ucimanager; fn main() { - let tbl = noarc::NoArc::new(MoveTable::default()); + let e = Engine::default(); + let search_flag = Arc::new(AtomicBool::new(false)); // Continue searching? Default to no. + let best_move = Arc::new(Mutex::new((Square::A1, Square::A1, MoveType::QuietMove))); - let gm = - GameManager::from_fen_str("rn1b1k1r/p4ppp/1pp5/8/2B5/3n4/PPP1N1PP/RNBQ1K1R w - - 0 11"); - let bestmove = root_negamax(4, gm, &tbl).0; - println!("Best move: {}{}", bestmove.1.to_str(), bestmove.2.to_str()); + let uci_handle = thread::spawn(move || { + ucimanager::communicate(e, search_flag, best_move); + }); - ucimanager::communicate(); + uci_handle + .join() + .expect("Joining thread uci_handle failed; the engine probably crashed."); } #[cfg(test)] diff --git a/src/ucimanager.rs b/src/ucimanager.rs index 74fea06..9b7c940 100644 --- a/src/ucimanager.rs +++ b/src/ucimanager.rs @@ -1,26 +1,23 @@ +use crate::gamemanager::legal_moves::search::root_negamax; use crate::types::{MoveType, Square}; use crate::{ enginemanager::Engine, gamemanager::GameManager, movetable::{noarc::NoArc, MoveTable}, }; -use std::io; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::thread; +use std::sync::{Arc, Mutex}; +use std::thread::{self, sleep}; +use std::time::Duration; +use std::{io, u16}; use vampirc_uci::{UciMessage, UciMove, UciPiece}; -pub fn communicate() { - let mut stop_engine = false; - let start_search_flag = Arc::new(AtomicBool::new(false)); - let mut e: Engine = Engine { - tbl: NoArc::new(MoveTable::default()), - board: GameManager::default(), - move_history: Vec::::new(), - //set_new_game: false, - }; - - while !stop_engine { +pub fn communicate( + mut e: Engine, + search_flag: Arc, + best_move: Arc>, +) { + loop { let mut text = String::new(); io::stdin() @@ -45,9 +42,9 @@ pub fn communicate() { fen, moves, } => { - //For now, we'll reinitalize the engine's data - //(minus movetable) each time we receive a - //'position' command. + // For now, we'll reinitalize the engine's data + // (minus movetable) each time we receive a + // 'position' command. if startpos { e.board = GameManager::default(); } else { @@ -62,34 +59,64 @@ pub fn communicate() { //e.set_new_game = false; } UciMessage::Go { - time_control: _, - search_control: _, + time_control, + search_control, } => { - start_search_flag.store(true, Ordering::Relaxed); - let clone_flag = start_search_flag.clone(); + search_flag.store(true, Ordering::Relaxed); + { + let mut lock = best_move.lock().unwrap(); + *lock = (Square::A1, Square::A1, MoveType::QuietMove); // Reinitialize best_move. + } // Lock dropped here. + let flag = search_flag.clone(); + let best_move = best_move.clone(); + let table = NoArc::new(MoveTable::default()); // TODO: Really hurts to create a whole new table... + let gm = e.board.clone(); - thread::spawn(move || { - //search() - pass in clone_flag to check whether search termination was ordered - }); + if let Some(timectl) = time_control { + match timectl { + vampirc_uci::UciTimeControl::Infinite => { + thread::spawn(move || { + root_negamax(20, gm, &table, flag, best_move); + }); + } + vampirc_uci::UciTimeControl::MoveTime(_) => unimplemented!(), + vampirc_uci::UciTimeControl::Ponder => unimplemented!(), + vampirc_uci::UciTimeControl::TimeLeft { .. } => unimplemented!(), + } + } } UciMessage::Stop => { - start_search_flag.store(false, Ordering::Relaxed); + search_flag.store(false, Ordering::Relaxed); + { + let lock = best_move.lock().unwrap(); + use MoveType::*; + let promo = match lock.2 { + QPromotion | QPromoCapture => "q", + RPromotion | RPromoCapture => "r", + BPromotion | BPromoCapture => "b", + NPromotion | NPromoCapture => "n", + _ => "", + }; + let outstr = + format!("bestmove {}{}{}", lock.0.to_str(), lock.1.to_str(), promo); + println!("{}", outstr); + } } UciMessage::Quit => { - start_search_flag.store(false, Ordering::Relaxed); - stop_engine = true; + search_flag.store(false, Ordering::Relaxed); + break; } _ => { println!("Some other message was received."); } } - } + } // End of the input loop. UCI terminates. } -fn make_move(board: &GameManager, tbl: &NoArc, m: UciMove) -> GameManager{ +fn make_move(board: &GameManager, tbl: &NoArc, m: UciMove) -> GameManager { let h_from = Square::from_str(&m.from.to_string()).unwrap(); let h_to = Square::from_str(&m.to.to_string()).unwrap(); - let legal_moves = board.legal_moves(tbl); + let legal_moves = board.legal_moves(&tbl); let updated_data = legal_moves .iter() .find(|data| {