use std::mem;
use std::cmp::max;
use std::thread;
use std::sync::Arc;
use std::sync::mpsc::{Sender, Receiver};
use std::marker::PhantomData;
use std::ops::Deref;
use uci::{SetOption, OptionDescription};
use value::*;
use depth::*;
use board::*;
use moves::*;
use ttable::*;
use search::*;
use search_node::SearchNode;
use evaluator::Evaluator;
use qsearch::QsearchResult;
use utils::MoveStack;
pub struct SimpleSearch<T: Ttable, N: SearchNode> {
phantom_t: PhantomData<T>,
phantom_n: PhantomData<N>,
}
impl<T, N> Search for SimpleSearch<T, N>
where T: Ttable,
N: SearchNode
{
type Ttable = T;
type SearchNode = N;
type ReportData = ();
fn spawn(params: SearchParams<Self::SearchNode>,
tt: Arc<Self::Ttable>,
reports_tx: Sender<SearchReport<Self::ReportData>>,
messages_rx: Receiver<String>)
-> thread::JoinHandle<Value> {
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()),
"SimpleSearch ignores searchmoves");
thread::spawn(move || {
let SearchParams {
search_id,
position,
depth,
lower_bound,
upper_bound,
..
} = params;
let report = SearchReport {
search_id: search_id,
searched_nodes: 0,
depth: 0,
value: VALUE_UNKNOWN,
data: (),
done: false,
};
let mut reporting = |searched_nodes| {
reports_tx
.send(SearchReport {
searched_nodes,
..report
})
.ok();
if let Ok(msg) = messages_rx.try_recv() {
msg == "TERMINATE"
} else {
false
}
};
let mut move_stack = MoveStack::new();
let mut search =
SearchRunner::new(position, tt.deref(), &mut move_stack, &mut reporting);
let (depth, value) = if let Ok(v) =
search.run(lower_bound, upper_bound, depth, Move::invalid()) {
(depth, v)
} else {
(0, VALUE_UNKNOWN)
};
reports_tx
.send(SearchReport {
searched_nodes: search.node_count(),
depth: depth,
value: value,
done: true,
..report
})
.ok();
value
})
}
}
impl<T: Ttable, N: SearchNode> SetOption for SimpleSearch<T, N> {
fn options() -> Vec<(&'static str, OptionDescription)> {
N::options()
}
fn set_option(name: &str, value: &str) {
N::set_option(name, value);
}
}
struct TerminatedSearch;
struct SearchRunner<'a, T, N>
where T: Ttable + 'a,
N: SearchNode
{
tt: &'a T,
killers: KillerTable,
position: N,
moves: &'a mut MoveStack,
state_stack: Vec<NodeState>,
reported_nodes: u64,
unreported_nodes: u64,
report_function: &'a mut FnMut(u64) -> bool,
}
impl<'a, T, N> SearchRunner<'a, T, N>
where T: Ttable + 'a,
N: SearchNode
{
pub fn new(root: N,
tt: &'a T,
move_stack: &'a mut MoveStack,
report_function: &'a mut FnMut(u64) -> bool)
-> SearchRunner<'a, T, N> {
SearchRunner {
tt: tt,
killers: KillerTable::new(),
position: root,
moves: move_stack,
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 mut v = if m.score() > REDUCTION_THRESHOLD {
-try!(self.run(-beta, -alpha, depth - 1, m))
} else {
match -try!(self.run(-alpha - 1, -alpha, depth - 2, 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
}
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::new(0, BOUND_NONE, 0).set_static_eval(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.qsearch(depth, 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::new(result.value(), bound, depth).set_static_eval(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::new(beta, BOUND_LOWER, depth)
.set_static_eval(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::new(value, bound, depth)
.set_move_digest(best_move.digest())
.set_static_eval(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;
}
}
#[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::{SearchRunner, KillerTable};
use value::*;
use board::*;
use search_node::*;
use moves::*;
use ttable::*;
use stock::{StdTtable, StdTtableEntry, StdSearchNode, StdQsearch, StdMoveGenerator,
SimpleEvaluator};
use utils::MoveStack;
type P = StdSearchNode<StdQsearch<StdMoveGenerator<SimpleEvaluator>>>;
#[test]
fn search() {
let tt = StdTtable::<StdTtableEntry>::new(None);
let p = P::from_history("8/8/8/8/3q3k/7n/6PP/2Q2R1K b - - 0 1",
&mut vec![].into_iter())
.ok()
.unwrap();
let mut moves = MoveStack::new();
let mut report = |_| false;
let mut search = SearchRunner::new(p, &tt, &mut moves, &mut report);
let value = search
.run(VALUE_MIN, VALUE_MAX, 1, Move::invalid())
.ok()
.unwrap();
assert!(value < -300);
let p = P::from_history("8/8/8/8/3q3k/7n/6PP/2Q2R1K b - - 0 1",
&mut vec![].into_iter())
.ok()
.unwrap();
let mut moves = MoveStack::new();
let mut report = |_| false;
let mut search = SearchRunner::new(p, &tt, &mut moves, &mut report);
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()));
}
}