use std::mem;
use std::cmp::max;
use std::thread;
use std::cell::UnsafeCell;
use std::sync::{Arc, Mutex, Condvar};
use std::sync::mpsc::{channel, Sender, Receiver, TryRecvError, RecvError};
use std::time::Duration;
use std::marker::PhantomData;
use std::ops::Deref;
use uci::{SetOption, OptionDescription};
use value::*;
use depth::*;
use board::*;
use moves::*;
use hash_table::*;
use search_executor::{SearchParams, SearchReport, SearchExecutor};
use search_node::SearchNode;
use evaluator::Evaluator;
use qsearch::QsearchResult;
use utils::MoveStack;
pub struct StdSearchExecutor<T: HashTable, N: SearchNode> {
phantom: PhantomData<T>,
thread_join_handle: Option<thread::JoinHandle<()>>,
thread_commands: Sender<Command<N>>,
thread_reports: Receiver<SearchReport<()>>,
has_reports_condition: Arc<(Mutex<bool>, Condvar)>,
}
impl<T, N> SearchExecutor for StdSearchExecutor<T, N>
where T: HashTable + 'static,
N: SearchNode + 'static
{
type HashTable = T;
type SearchNode = N;
type ReportData = ();
fn new(tt: Arc<T>) -> StdSearchExecutor<T, N> {
let (commands_tx, commands_rx) = channel();
let (reports_tx, reports_rx) = channel();
let has_reports_condition = Arc::new((Mutex::new(false), Condvar::new()));
StdSearchExecutor {
phantom: PhantomData,
thread_commands: commands_tx,
thread_reports: reports_rx,
has_reports_condition: has_reports_condition.clone(),
thread_join_handle: Some(thread::spawn(move || {
serve_simple(tt, commands_rx, reports_tx, has_reports_condition);
})),
}
}
fn start_search(&mut self, params: SearchParams<N>) {
assert!(params.depth >= 0, "depth must be at least 0.");
debug_assert!(params.depth <= DEPTH_MAX);
debug_assert!(params.lower_bound < params.upper_bound);
debug_assert!(params.lower_bound != VALUE_UNKNOWN);
debug_assert!(params.searchmoves.is_empty() ||
contains_same_moves(¶ms.searchmoves, ¶ms.position.legal_moves()),
"StdSearchExecutor ignores searchmoves");
self.thread_commands.send(Command::Start(params)).unwrap();
}
fn try_recv_report(&mut self) -> Result<SearchReport<Self::ReportData>, TryRecvError> {
let mut has_reports = self.has_reports_condition.0.lock().unwrap();
let result = self.thread_reports.try_recv();
if result.is_err() {
*has_reports = false;
}
result
}
fn wait_report(&self, duration: Duration) {
let &(ref has_reports, ref condition) = &*self.has_reports_condition;
let has_reports = has_reports.lock().unwrap();
if !*has_reports {
condition.wait_timeout(has_reports, duration).unwrap();
}
}
fn terminate_search(&mut self) {
self.thread_commands.send(Command::Terminate).unwrap();
}
}
impl<T: HashTable, N: SearchNode> SetOption for StdSearchExecutor<T, N> {
fn options() -> Vec<(String, OptionDescription)> {
N::options()
}
fn set_option(name: &str, value: &str) {
N::set_option(name, value);
}
}
impl<T: HashTable, N: SearchNode> Drop for StdSearchExecutor<T, N> {
fn drop(&mut self) {
self.thread_commands.send(Command::Exit).unwrap();
self.thread_join_handle.take().unwrap().join().unwrap();
}
}
enum Command<N: SearchNode> {
Start(SearchParams<N>),
Terminate,
Exit,
}
fn serve_simple<T, N>(tt: Arc<T>,
commands: Receiver<Command<N>>,
reports: Sender<SearchReport<()>>,
has_reports_condition: Arc<(Mutex<bool>, Condvar)>)
where T: HashTable,
N: SearchNode
{
thread_local!(
static MOVE_STACK: UnsafeCell<MoveStack> = UnsafeCell::new(MoveStack::new())
);
MOVE_STACK.with(|s| {
let &(ref has_reports, ref condition) = &*has_reports_condition;
let mut move_stack = unsafe { &mut *s.get() };
let mut pending_command = None;
loop {
let command = match pending_command.take() {
Some(cmd) => cmd,
None => commands.recv().or::<RecvError>(Ok(Command::Exit)).unwrap(),
};
match command {
Command::Start(SearchParams { search_id,
position,
depth,
lower_bound,
upper_bound,
.. }) => {
debug_assert!(lower_bound < upper_bound);
let mut report = |searched_nodes| {
reports.send(SearchReport {
search_id: search_id,
searched_nodes: searched_nodes,
depth: 0,
value: VALUE_UNKNOWN,
data: (),
done: false,
})
.ok();
let mut has_reports = has_reports.lock().unwrap();
*has_reports = true;
condition.notify_one();
if let Ok(cmd) = commands.try_recv() {
pending_command = Some(cmd);
true
} else {
false
}
};
let mut search = Search::new(position, tt.deref(), move_stack, &mut report);
let (depth, value) = if let Ok(v) = search.run(lower_bound,
upper_bound,
depth,
Move::invalid()) {
(depth, v)
} else {
(0, VALUE_UNKNOWN)
};
reports.send(SearchReport {
search_id: search_id,
searched_nodes: search.node_count(),
depth: depth,
value: value,
data: (),
done: true,
})
.ok();
let mut has_reports = has_reports.lock().unwrap();
*has_reports = true;
condition.notify_one();
search.reset();
}
Command::Terminate => continue,
Command::Exit => break,
}
}
})
}
struct TerminatedSearch;
struct Search<'a, T, N>
where T: HashTable + 'a,
N: SearchNode
{
tt: &'a T,
killers: KillerTable,
position: N,
moves: &'a mut MoveStack,
moves_starting_ply: usize,
state_stack: Vec<NodeState>,
reported_nodes: u64,
unreported_nodes: u64,
report_function: &'a mut FnMut(u64) -> bool,
}
impl<'a, T, N> Search<'a, T, N>
where T: HashTable + 'a,
N: SearchNode
{
pub fn new(root: N,
tt: &'a T,
move_stack: &'a mut MoveStack,
report_function: &'a mut FnMut(u64) -> bool)
-> Search<'a, T, N> {
let moves_starting_ply = move_stack.ply();
Search {
tt: tt,
killers: KillerTable::new(),
position: root,
moves: move_stack,
moves_starting_ply: moves_starting_ply,
state_stack: Vec::with_capacity(32),
reported_nodes: 0,
unreported_nodes: 0,
report_function: report_function,
}
}
pub fn run(&mut self,
mut alpha: Value, beta: Value, depth: Depth,
last_move: Move)
-> Result<Value, TerminatedSearch> {
debug_assert!(alpha < beta);
let mut value = VALUE_UNKNOWN;
if let Some(v) = try!(self.node_begin(alpha, beta, depth, last_move)) {
value = v;
} else {
debug_assert!(depth > 0);
let mut bound = BOUND_EXACT;
let mut best_move = Move::invalid();
while let Some(m) = self.do_move() {
try!(self.report_progress(1));
let reduced_depth = if depth < 2 {
0
} else {
depth - 2
};
let mut v = if m.score() > REDUCTION_THRESHOLD {
-try!(self.run(-beta, -alpha, depth - 1, m))
} else {
match -try!(self.run(-alpha - 1, -alpha, reduced_depth, m)) {
v if v <= alpha => v,
_ => -try!(self.run(-beta, -alpha, depth - 1, m)),
}
};
self.undo_move();
debug_assert!(v > VALUE_UNKNOWN);
if v < VALUE_EVAL_MIN - 1 {
v += 1;
} else if v > VALUE_EVAL_MAX + 1 {
v -= 1;
}
if v >= beta {
best_move = m;
value = v;
bound = BOUND_LOWER;
self.register_killer_move(m);
break;
}
if v > value {
best_move = m;
value = v;
bound = if v > alpha {
alpha = v;
BOUND_EXACT
} else {
BOUND_UPPER
};
}
}
if value == VALUE_UNKNOWN {
value = self.position.evaluate_final();
debug_assert_eq!(bound, BOUND_EXACT);
}
self.store(value, bound, depth, best_move);
}
self.node_end();
Ok(value)
}
#[inline]
pub fn node_count(&self) -> u64 {
self.reported_nodes + self.unreported_nodes
}
#[inline]
pub fn reset(&mut self) {
while self.moves.ply() > self.moves_starting_ply {
self.moves.restore();
}
self.state_stack.clear();
self.reported_nodes = 0;
self.unreported_nodes = 0;
self.killers.forget_all();
}
fn node_begin(&mut self,
alpha: Value,
beta: Value,
depth: Depth,
last_move: Move)
-> Result<Option<Value>, TerminatedSearch> {
let hash = self.position.hash();
let (entry, static_eval) = if let Some(e) = self.tt.probe(hash) {
match e.static_eval() {
VALUE_UNKNOWN => {
(e,
self.position
.evaluator()
.evaluate(self.position.board()))
}
v => (e, v),
}
} else {
let v = self.position
.evaluator()
.evaluate(self.position.board());
(T::Entry::with_static_eval(0, BOUND_NONE, 0, MoveDigest::invalid(), v), v)
};
self.state_stack.push(NodeState {
phase: NodePhase::Pristine,
hash_move_digest: entry.move_digest(),
static_eval: static_eval,
is_check: unsafe { mem::uninitialized() }, killer: None,
});
if entry.depth() >= depth {
let value = entry.value();
let bound = entry.bound();
if (value >= beta && bound & BOUND_LOWER != 0) ||
(value <= alpha && bound & BOUND_UPPER != 0) ||
(bound == BOUND_EXACT) {
return Ok(Some(value));
};
};
if depth == 0 {
let result = self.position.evaluate_quiescence(alpha, beta, static_eval);
try!(self.report_progress(result.searched_nodes()));
let bound = if result.value() >= beta {
BOUND_LOWER
} else if result.value() <= alpha {
BOUND_UPPER
} else {
BOUND_EXACT
};
self.tt.store(hash,
T::Entry::with_static_eval(result.value(),
bound,
0,
MoveDigest::invalid(),
static_eval));
return Ok(Some(result.value()));
}
{
self.moves.save();
let state = self.state_stack.last_mut().unwrap();
state.phase = NodePhase::ConsideredNullMove;
state.is_check = self.position.is_check();
}
if !last_move.is_null() && static_eval >= beta &&
{
let p = &self.position;
!p.evaluator().is_zugzwangy(p.board())
} {
let reduced_depth = if depth > 7 {
depth - NULL_MOVE_REDUCTION - 1
} else {
depth - NULL_MOVE_REDUCTION
};
if entry.depth() >= max(0, reduced_depth) && entry.value() < beta &&
entry.bound() & BOUND_UPPER != 0 {
return Ok(None);
}
let m = self.position.null_move();
if self.position.do_move(m) {
let value = -try!(self.run(-beta, -alpha, max(0, reduced_depth - 1), m));
self.position.undo_last_move();
if value >= beta {
self.tt.store(hash,
T::Entry::with_static_eval(beta,
BOUND_LOWER,
depth,
MoveDigest::invalid(),
static_eval));
return Ok(Some(beta));
}
}
}
Ok(None)
}
#[inline]
fn node_end(&mut self) {
if let NodePhase::Pristine = self.state_stack.last().unwrap().phase {
} else {
self.moves.restore();
}
self.state_stack.pop();
let downgraded_ply = self.state_stack.len() + KILLERS_DOWNGRADE_DISTANCE;
if downgraded_ply < DEPTH_MAX as usize {
self.killers.downgrade(downgraded_ply);
}
}
#[inline]
fn do_move(&mut self) -> Option<Move> {
debug_assert!(self.state_stack.len() > 0);
let ply = self.state_stack.len() - 1;
let state = &mut self.state_stack[ply];
debug_assert!(if let NodePhase::Pristine = state.phase {
false
} else {
true
});
debug_assert!(ply < DEPTH_MAX as usize);
if let NodePhase::ConsideredNullMove = state.phase {
state.phase = NodePhase::TriedHashMove;
if let Some(mut m) = self.position.try_move_digest(state.hash_move_digest) {
if self.position.do_move(m) {
m.set_score(MOVE_SCORE_MAX);
return Some(m);
}
}
}
if let NodePhase::TriedHashMove = state.phase {
state.phase = NodePhase::GeneratedMoves;
self.position.generate_moves(self.moves);
if state.hash_move_digest != MoveDigest::invalid() {
self.moves.pull_move(state.hash_move_digest);
}
for m in self.moves.list_mut().iter_mut() {
let move_score = if m.move_type() == MOVE_PROMOTION {
if m.aux_data() == 0 {
MOVE_SCORE_MAX - 1
} else {
0
}
} else if m.captured_piece() < PIECE_NONE {
match self.position.evaluate_move(*m) {
see if see > 0 => MOVE_SCORE_MAX - 1,
see if see == 0 => MOVE_SCORE_MAX - 2,
_ => 0,
}
} else {
0
};
m.set_score(move_score);
}
}
while let Some(mut m) = if let NodePhase::TriedLosingCaptures = state.phase {
self.moves.pop()
} else {
self.moves.pull_best()
} {
if let NodePhase::GeneratedMoves = state.phase {
if m.score() > REDUCTION_THRESHOLD {
if self.position.do_move(m) {
return Some(m);
}
continue;
}
state.phase = NodePhase::TriedWinningMoves;
}
if let NodePhase::TriedWinningMoves = state.phase {
self.moves.add_move(m);
let killer = if let Some(k2) = state.killer {
state.phase = NodePhase::TriedKillerMoves;
k2
} else {
let (k1, k2) = self.killers.get(ply);
state.killer = Some(k2);
k1
};
if killer != MoveDigest::invalid() {
if let Some(mut m) = self.moves.pull_move(killer) {
if self.position.do_move(m) {
m.set_score(MOVE_SCORE_MAX);
return Some(m);
}
}
}
continue;
}
if let NodePhase::TriedKillerMoves = state.phase {
if m.captured_piece() < PIECE_NONE {
if self.position.do_move(m) {
m.set_score(MOVE_SCORE_MAX);
return Some(m);
}
continue;
}
state.phase = NodePhase::TriedLosingCaptures;
self.moves.add_move(m);
continue;
}
if self.position.do_move(m) {
if state.is_check || self.position.is_check() || m.move_type() == MOVE_PROMOTION {
m.set_score(MOVE_SCORE_MAX);
}
return Some(m);
}
}
None
}
#[inline]
fn undo_move(&mut self) {
self.position.undo_last_move();
}
#[inline]
fn store(&mut self, value: Value, bound: BoundType, depth: Depth, best_move: Move) {
self.tt.store(self.position.hash(),
T::Entry::with_static_eval(value,
bound,
depth,
best_move.digest(),
self.state_stack.last().unwrap().static_eval));
}
#[inline]
fn report_progress(&mut self, new_nodes: u64) -> Result<(), TerminatedSearch> {
let node_count_report_interval = if cfg!(debug_assertions) {
NODE_COUNT_REPORT_INTERVAL / 100
} else {
NODE_COUNT_REPORT_INTERVAL
};
self.unreported_nodes += new_nodes;
if self.unreported_nodes >= node_count_report_interval {
self.reported_nodes += self.unreported_nodes;
self.unreported_nodes = 0;
if (*self.report_function)(self.reported_nodes) {
return Err(TerminatedSearch);
}
}
Ok(())
}
#[inline]
fn register_killer_move(&mut self, m: Move) {
self.killers.register(self.state_stack.len() - 1, m);
}
}
const MOVE_SCORE_MAX: u32 = ::std::u32::MAX;
const NODE_COUNT_REPORT_INTERVAL: u64 = 15000;
const NULL_MOVE_REDUCTION: i8 = 3;
const REDUCTION_THRESHOLD: u32 = 0;
const KILLERS_DOWNGRADE_DISTANCE: usize = 3;
enum NodePhase {
Pristine,
ConsideredNullMove,
TriedHashMove,
GeneratedMoves,
TriedWinningMoves,
TriedKillerMoves,
TriedLosingCaptures,
}
struct NodeState {
phase: NodePhase,
hash_move_digest: MoveDigest,
static_eval: Value,
is_check: bool,
killer: Option<MoveDigest>,
}
struct KillerTable {
array: [KillerPair; DEPTH_MAX as usize],
}
impl KillerTable {
#[inline]
pub fn new() -> KillerTable {
KillerTable { array: [Default::default(); DEPTH_MAX as usize] }
}
#[inline]
pub fn register(&mut self, half_move: usize, m: Move) {
debug_assert!(half_move < self.array.len());
if m.captured_piece() < PIECE_NONE || m.move_type() == MOVE_PROMOTION {
return;
}
let pair = &mut self.array[half_move];
let minor = &mut pair.minor;
let major = &mut pair.major;
let digest = m.digest();
debug_assert!(digest != MoveDigest::invalid());
if major.digest == digest {
major.hits = major.hits.wrapping_add(1);
return;
} else if minor.digest == digest {
minor.hits = minor.hits.wrapping_add(1);
} else {
*minor = Killer {
digest: digest,
hits: 1,
};
}
if minor.hits >= major.hits {
mem::swap(minor, major);
}
}
#[inline]
pub fn get(&self, half_move: usize) -> (MoveDigest, MoveDigest) {
debug_assert!(half_move < self.array.len());
let pair = &self.array[half_move];
(pair.major.digest, pair.minor.digest)
}
#[inline]
pub fn downgrade(&mut self, half_move: usize) {
debug_assert!(half_move < self.array.len());
let pair = &mut self.array[half_move];
pair.minor.hits >>= 1;
pair.major.hits >>= 1;
}
#[inline]
pub fn forget_all(&mut self) {
for pair in self.array.iter_mut() {
*pair = Default::default();
}
}
}
#[derive(Clone, Copy)]
struct Killer {
pub digest: MoveDigest,
pub hits: u16,
}
#[derive(Clone, Copy)]
struct KillerPair {
pub minor: Killer,
pub major: Killer,
}
impl Default for KillerPair {
fn default() -> KillerPair {
KillerPair {
minor: Killer {
digest: MoveDigest::invalid(),
hits: 0,
},
major: Killer {
digest: MoveDigest::invalid(),
hits: 0,
},
}
}
}
fn contains_same_moves(list1: &Vec<Move>, list2: &Vec<Move>) -> bool {
let mut list1 = list1.clone();
let mut list2 = list2.clone();
list1.sort();
list2.sort();
list1 == list2
}
#[cfg(test)]
mod tests {
use super::{Search, KillerTable};
use value::*;
use board::*;
use search_node::*;
use moves::*;
use hash_table::*;
use stock::{StdHashTable, StdSearchNode, StdQsearch, StdMoveGenerator, SimpleEvaluator};
use utils::MoveStack;
type P = StdSearchNode<StdQsearch<StdMoveGenerator<SimpleEvaluator>>>;
#[test]
fn search() {
let p = P::from_history("8/8/8/8/3q3k/7n/6PP/2Q2R1K b - - 0 1",
&mut vec![].into_iter())
.ok()
.unwrap();
let tt = StdHashTable::new(None);
let mut moves = MoveStack::new();
let mut report = |_| false;
let mut search = Search::new(p, &tt, &mut moves, &mut report);
let value = search.run(VALUE_MIN, VALUE_MAX, 1, Move::invalid()).ok().unwrap();
assert!(value < -300);
search.reset();
let value = search.run(VALUE_MIN, VALUE_MAX, 8, Move::invalid()).ok().unwrap();
assert!(value > VALUE_EVAL_MAX);
}
#[test]
fn killers() {
let mut killers = KillerTable::new();
let mut p = P::from_history("5r2/8/8/4q1p1/3P4/k3P1P1/P2b1R1B/K4R2 w - - 0 1",
&mut vec![].into_iter())
.ok()
.unwrap();
let mut v = MoveStack::new();
p.generate_moves(&mut v);
let mut i = 1;
let mut previous_move_digest = MoveDigest::invalid();
while let Some(m) = v.pop() {
if m.captured_piece() == PIECE_NONE && p.do_move(m) {
for _ in 0..i {
killers.register(0, m);
}
i += 1;
p.undo_last_move();
let (killer1, killer2) = killers.get(0);
assert!(killer1 == m.digest());
assert!(killer2 == previous_move_digest);
previous_move_digest = m.digest();
}
}
assert!(killers.get(1) == (MoveDigest::invalid(), MoveDigest::invalid()));
}
}