Skip to content

Commit

Permalink
Merge pull request #63 from emilio-zuniga/uci-search-integration
Browse files Browse the repository at this point in the history
UCI & search integration
  • Loading branch information
emilio-zuniga authored Dec 1, 2024
2 parents 490bceb + c7c8c67 commit 2a809bc
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 51 deletions.
15 changes: 14 additions & 1 deletion src/enginemanager.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
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<MoveTable>,
pub move_history: Vec<UciMove>,
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(),
}
}
}
51 changes: 39 additions & 12 deletions src/gamemanager/legal_moves/search.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
};

use rayon::prelude::*;

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<MoveTable>) -> (Move, GameManager) {
pub fn root_negamax(
depth: u16,
gm: GameManager,
tbl: &NoArc<MoveTable>,
flag: Arc<AtomicBool>,
best_move: Arc<Mutex<(Square, Square, MoveType)>>,
) {
/* ************************************************************************************* */
/* 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 {
Expand All @@ -19,7 +36,7 @@ pub fn root_negamax(depth: u16, gm: GameManager, tbl: &NoArc<MoveTable>) -> (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),
)
})
Expand All @@ -30,20 +47,17 @@ pub fn root_negamax(depth: u16, gm: GameManager, tbl: &NoArc<MoveTable>) -> (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(
Expand All @@ -53,8 +67,13 @@ fn negamax(
movetype: MoveType,
gm: &GameManager,
tbl: &NoArc<MoveTable>,
flag: Arc<AtomicBool>,
) -> 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);
Expand All @@ -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;
Expand Down
25 changes: 17 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)]
Expand Down
87 changes: 57 additions & 30 deletions src/ucimanager.rs
Original file line number Diff line number Diff line change
@@ -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::<UciMove>::new(),
//set_new_game: false,
};

while !stop_engine {
pub fn communicate(
mut e: Engine,
search_flag: Arc<AtomicBool>,
best_move: Arc<Mutex<(Square, Square, MoveType)>>,
) {
loop {
let mut text = String::new();

io::stdin()
Expand All @@ -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 {
Expand All @@ -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<MoveTable>, m: UciMove) -> GameManager{
fn make_move(board: &GameManager, tbl: &NoArc<MoveTable>, 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| {
Expand Down

0 comments on commit 2a809bc

Please sign in to comment.