mod pick;
use std::mem;
use std::ptr;
use tanton::board::movegen::{MoveGen, PseudoLegal};
use tanton::core::mono_traits::*;
use tanton::helper::prelude::piecetype_value;
#[allow(unused_imports)]
use tanton::{BitMove, Board, MoveList, PieceType, ScoringMove, ScoringMoveList, SQ};
use self::pick::*;
use tables::prelude::*;
pub struct MovePicker {
pick: Pick,
board: *const Board,
moves: ScoringMoveList,
depth: i16,
ttm: BitMove,
killers: [BitMove; 2],
cm: BitMove,
recapture_sq: SQ,
threshold: i32,
main_hist: *const ButterflyHistory,
capture_hist: *const CapturePieceToHistory,
cont_hist: *const [*const PieceToHistory; 4],
cur_ptr: *mut ScoringMove,
end_ptr: *mut ScoringMove,
end_bad_captures: *mut ScoringMove,
}
impl MovePicker {
pub fn main_search(
board: &Board,
depth: i16,
main_hist: &ButterflyHistory,
cap_hist: &CapturePieceToHistory,
cont_hist: *const [*const PieceToHistory; 4],
mut ttm: BitMove,
killers: [BitMove; 2],
counter_move: BitMove,
) -> Self {
assert!(depth > 0);
let mut pick = if board.in_check() {
Pick::EvasionSearch
} else {
Pick::MainSearch
};
if ttm == BitMove::null() || !board.pseudo_legal_move(ttm) {
ttm = BitMove::null();
pick.incr();
}
let mut moves = ScoringMoveList::default();
let first: *mut ScoringMove = moves.as_mut_ptr();
MovePicker {
pick,
board: &*board,
moves,
depth,
ttm,
killers,
cm: counter_move,
recapture_sq: unsafe { mem::MaybeUninit::uninit().assume_init() },
threshold: unsafe { mem::MaybeUninit::uninit().assume_init() },
main_hist,
capture_hist: cap_hist,
cont_hist,
cur_ptr: first,
end_ptr: first,
end_bad_captures: first,
}
}
pub fn qsearch(
board: &Board,
depth: i16,
ttm: BitMove,
main_hist: &ButterflyHistory,
cap_hist: &CapturePieceToHistory,
recapture_sq: SQ,
) -> Self {
assert!(depth <= 0);
let mut moves = ScoringMoveList::default();
let first: *mut ScoringMove = moves.as_mut_ptr();
let p = mem::MaybeUninit::uninit();
let mut mp_qs = MovePicker {
pick: unsafe { p.assume_init() },
board: &*board,
moves,
depth,
ttm,
killers: unsafe { mem::MaybeUninit::uninit().assume_init() },
cm: unsafe { mem::MaybeUninit::uninit().assume_init() },
recapture_sq: unsafe { mem::MaybeUninit::uninit().assume_init() },
threshold: unsafe { mem::MaybeUninit::uninit().assume_init() },
main_hist,
capture_hist: cap_hist,
cont_hist: unsafe { mem::MaybeUninit::uninit().assume_init() },
cur_ptr: first,
end_ptr: first,
end_bad_captures: first,
};
if board.in_check() {
mp_qs.pick = Pick::EvasionSearch;
} else if depth > -5 {
mp_qs.pick = Pick::QSearch;
} else {
mp_qs.pick = Pick::QSearchRecaptures;
mp_qs.recapture_sq = recapture_sq;
return mp_qs;
}
if ttm == BitMove::null() || !board.pseudo_legal_move(ttm) {
mp_qs.ttm = BitMove::null();
mp_qs.pick.incr()
}
mp_qs
}
pub fn probcut_search(board: &Board, threshold: i32, mut ttm: BitMove) -> Self {
assert!(!board.in_check());
let mut moves = ScoringMoveList::default();
let first: *mut ScoringMove = moves.as_mut_ptr();
let mut pick = Pick::ProbCutSearch;
ttm = if ttm != BitMove::null()
&& board.pseudo_legal_move(ttm)
&& board.is_capture(ttm)
&& board.see_ge(ttm, threshold)
{
ttm
} else {
pick.incr();
BitMove::null()
};
MovePicker {
pick,
board: &*board,
moves,
depth: unsafe { mem::MaybeUninit::uninit().assume_init() },
ttm,
killers: unsafe { mem::MaybeUninit::uninit().assume_init() },
cm: unsafe { mem::MaybeUninit::uninit().assume_init() },
recapture_sq: unsafe { mem::MaybeUninit::uninit().assume_init() },
threshold,
main_hist: unsafe { mem::MaybeUninit::uninit().assume_init() },
capture_hist: unsafe { mem::MaybeUninit::uninit().assume_init() },
cont_hist: unsafe { mem::MaybeUninit::uninit().assume_init() },
cur_ptr: first,
end_ptr: first,
end_bad_captures: first,
}
}
fn main_hist(&self) -> &ButterflyHistory {
unsafe { &*self.main_hist }
}
fn capture_hist(&self) -> &CapturePieceToHistory {
unsafe { &*self.capture_hist }
}
fn cont_hist(&self, idx: usize) -> &PieceToHistory {
unsafe { &*((&*self.cont_hist)[idx]) }
}
pub fn drain(mut self, list: &mut MoveList, skip_quiets: bool) {
let mut mov = self.next_mov(skip_quiets);
while mov != BitMove::null() {
list.push(mov);
mov = self.next_mov(skip_quiets);
}
}
fn score_captures(&mut self) {
let mut ptr = self.cur_ptr;
unsafe {
while ptr < self.end_ptr {
let mov: BitMove = (*ptr).bit_move;
let piece_moved = self.board().moved_piece(mov);
let piece_cap = self.board().captured_piece(mov);
(*ptr).score = piecetype_value(piece_cap, false) as i16
+ self.capture_hist()[(piece_moved, mov.get_dest(), piece_cap)];
ptr = ptr.add(1);
}
}
}
fn score_evasions(&mut self) {
let mut ptr = self.cur_ptr;
unsafe {
while ptr < self.end_ptr {
let mov: BitMove = (*ptr).bit_move;
if self.board().is_capture(mov) {
let piece_moved = self.board().moved_piece(mov);
let piece_cap = self.board().captured_piece(mov);
(*ptr).score =
piecetype_value(piece_cap, false) as i16 - piece_moved.type_of() as i16;
} else {
(*ptr).score = self.main_hist()[(self.board().turn(), mov)];
}
ptr = ptr.add(1);
}
}
}
fn score_quiets(&mut self) {
let mut ptr = self.cur_ptr;
unsafe {
while ptr < self.end_ptr {
let mov: BitMove = (*ptr).bit_move;
let to_sq: SQ = mov.get_dest();
let moved_piece = self.board().moved_piece(mov);
(*ptr).score = self.main_hist()[(self.board().turn(), mov)]
+ self.cont_hist(0)[(moved_piece, to_sq)]
+ self.cont_hist(1)[(moved_piece, to_sq)]
+ self.cont_hist(3)[(moved_piece, to_sq)];
ptr = ptr.add(1);
}
}
}
fn pick_best(&self, begin: *mut ScoringMove, end: *mut ScoringMove) -> ScoringMove {
unsafe {
let mut best_score = begin;
let mut cur = begin.add(1);
while cur < end {
if (*cur).score > (*best_score).score {
best_score = cur;
}
cur = cur.add(1);
}
ptr::swap(begin, best_score);
*begin
}
}
pub fn state(&self) -> Pick {
self.pick
}
pub fn next(&mut self, skip_quiets: bool) -> Option<BitMove> {
let mov = self.next_mov(skip_quiets);
if mov != BitMove::null() {
Some(mov)
} else {
None
}
}
pub fn next_mov(&mut self, skip_quiets: bool) -> BitMove {
let mut mov: ScoringMove = ScoringMove::null();
match self.pick {
Pick::MainSearch | Pick::EvasionSearch | Pick::QSearch | Pick::ProbCutSearch => {
self.pick.incr();
return self.ttm;
}
Pick::CapturesInit
| Pick::ProbCutCapturesInit
| Pick::QSearchInit
| Pick::QSearchRecaptures => {
unsafe {
self.end_bad_captures = self.moves.as_mut_ptr();
self.cur_ptr = self.moves.as_mut_ptr();
self.end_ptr = MoveGen::extend_from_ptr::<
PseudoLegal,
CapturesGenType,
ScoringMoveList,
>(self.board(), self.cur_ptr);
}
self.score_captures();
self.pick.incr();
return self.next_mov(skip_quiets);
}
Pick::GoodCaptures => {
while self.cur_ptr < self.end_ptr {
mov = self.pick_best(self.cur_ptr, self.end_ptr);
unsafe {
self.cur_ptr = self.cur_ptr.add(1);
}
if mov.bit_move != self.ttm && mov.score > -128 {
let previous_val = unsafe { (*self.cur_ptr.sub(1)).score as i32 };
if self.board().see_ge(mov.bit_move, -55 * previous_val / 1024) {
return mov.bit_move;
}
}
unsafe {
*self.end_bad_captures = mov;
self.end_bad_captures = self.end_bad_captures.add(1);
}
}
self.pick.incr();
return self.next_mov(skip_quiets);
}
Pick::KillerOne | Pick::KillerTwo => {
mov.bit_move = self.killers[self.pick as usize - Pick::KillerOne as usize];
self.pick.incr();
if mov.bit_move != BitMove::null()
&& mov.bit_move != self.ttm
&& self.board().pseudo_legal_move(mov.bit_move)
&& !self.board().is_capture(mov.bit_move)
{
return mov.bit_move;
}
return self.next_mov(skip_quiets);
}
Pick::CounterMove => {
self.pick.incr();
if self.cm != BitMove::null()
&& self.cm != self.ttm
&& self.cm != self.killers[0]
&& self.cm != self.killers[1]
&& self.board().pseudo_legal_move(self.cm)
&& !self.board().is_capture(self.cm)
{
return self.cm;
}
return self.next_mov(skip_quiets);
}
Pick::QuietInit => {
unsafe {
self.cur_ptr = self.end_bad_captures;
self.end_ptr = MoveGen::extend_from_ptr::<
PseudoLegal,
QuietsGenType,
ScoringMoveList,
>(self.board(), self.cur_ptr);
}
self.pick.incr();
return self.next_mov(skip_quiets);
}
Pick::QuietMoves => {
if !skip_quiets {
while self.cur_ptr < self.end_ptr {
unsafe {
mov = *self.cur_ptr;
self.cur_ptr = self.cur_ptr.add(1);
if mov.bit_move != self.ttm
&& mov.bit_move != self.killers[0]
&& mov.bit_move != self.killers[1]
&& mov.bit_move != self.cm
{
return mov.bit_move;
}
}
}
}
self.pick.incr();
self.cur_ptr = self.moves.as_mut_ptr();
return self.next_mov(skip_quiets);
}
Pick::BadCaptures => {
if self.cur_ptr < self.end_bad_captures {
unsafe {
mov = *self.cur_ptr;
self.cur_ptr = self.cur_ptr.add(1);
}
return mov.bit_move;
}
}
Pick::EvasionsInit => {
unsafe {
self.cur_ptr = self.moves.as_mut_ptr();
self.end_ptr = MoveGen::extend_from_ptr::<
PseudoLegal,
EvasionsGenType,
ScoringMoveList,
>(self.board(), self.cur_ptr);
}
self.score_evasions();
self.pick.incr();
return self.next_mov(skip_quiets);
}
Pick::AllEvasions => {
while self.cur_ptr < self.end_ptr {
mov = self.pick_best(self.cur_ptr, self.end_ptr);
unsafe {
self.cur_ptr = self.cur_ptr.add(1);
}
if mov.bit_move != self.ttm {
return mov.bit_move;
}
}
}
Pick::ProbCutCaptures => {
while self.cur_ptr < self.end_ptr {
mov = self.pick_best(self.cur_ptr, self.end_ptr);
unsafe {
self.cur_ptr = self.cur_ptr.add(1);
}
if mov.bit_move != self.ttm && self.board().see_ge(mov.bit_move, self.threshold)
{
return mov.bit_move;
}
}
}
Pick::QCaptures => {
while self.cur_ptr < self.end_ptr {
mov = self.pick_best(self.cur_ptr, self.end_ptr);
unsafe {
self.cur_ptr = self.cur_ptr.add(1);
}
if mov.bit_move != self.ttm {
return mov.bit_move;
}
}
if self.depth > -1 {
unsafe {
self.cur_ptr = self.moves.as_mut_ptr();
self.end_ptr = MoveGen::extend_from_ptr::<
PseudoLegal,
QuietChecksGenType,
ScoringMoveList,
>(self.board(), self.cur_ptr);
}
self.pick.incr();
return self.next_mov(skip_quiets);
}
}
Pick::QChecks => {
while self.cur_ptr < self.end_ptr {
unsafe {
mov = *self.cur_ptr;
self.cur_ptr = self.cur_ptr.add(1);
}
if mov.bit_move != self.ttm {
return mov.bit_move;
}
}
}
Pick::QRecaptures => {
while self.cur_ptr < self.end_ptr {
unsafe {
mov = *self.cur_ptr;
self.cur_ptr = self.cur_ptr.add(1);
if mov.bit_move.get_dest() == self.recapture_sq {
return mov.bit_move;
}
}
}
}
}
BitMove::null()
}
fn board(&self) -> &Board {
unsafe { &*self.board }
}
}
fn partial_insertion_sort(begin: *mut ScoringMove, end: *mut ScoringMove, limit: i32) {
unsafe {
let mut sorted_end: *mut ScoringMove = begin;
let mut p: *mut ScoringMove = begin.add(1);
while p < end {
if (*p).score as i32 >= limit {
let tmp: ScoringMove = *p;
sorted_end = sorted_end.add(1);
*p = *sorted_end;
let mut q = sorted_end;
while q != begin && *(q.sub(1)) < tmp {
*q = *(q.sub(1));
q = q.sub(1);
}
*q = tmp;
}
p = p.add(1);
}
}
}
#[cfg(test)]
mod tests {
use std::i16::{MAX, MIN};
use std::mem;
use std::panic;
use super::*;
use rand;
use tanton::MoveList;
#[test]
fn mp_partial_insertion_sort() {
let mut moves = ScoringMoveList::default();
moves.push_score(BitMove::null(), 34);
moves.push_score(BitMove::null(), 20);
moves.push_score(BitMove::null(), -5);
moves.push_score(BitMove::null(), 50);
moves.push_score(BitMove::null(), 3);
moves.push_score(BitMove::null(), 0);
moves.push_score(BitMove::null(), 9);
moves.push_score(BitMove::null(), -1);
moves.push_score(BitMove::null(), 2);
let len = moves.len();
let begin = moves.get_mut(0).unwrap() as *mut ScoringMove;
let end = unsafe { begin.add(len) };
partial_insertion_sort(begin, end, 4);
}
#[test]
fn mp_partial_insertion_sort_rand() {
for _x in 0..10 {
partial_insertion_t();
}
}
fn partial_insertion_t() {
let min = 10;
let max = 200;
let num = (rand::random::<u16>() % (max - min)) + min;
let mut moves = ScoringMoveList::default();
for _x in 0..num {
let rand_score = rand::random::<i16>();
moves.push_score(BitMove::null(), rand_score);
}
let limit: i16 = rand::random::<i16>().max(MIN + 10).min(MAX - 10);
let len = moves.len();
let begin = moves.get_mut(0).unwrap() as *mut ScoringMove;
let end = unsafe { begin.add(len) };
partial_insertion_sort(begin, end, limit as i32);
let mut unsorted_idx = 0;
while unsorted_idx < len {
if moves[unsorted_idx].score < limit {
break;
}
unsorted_idx += 1;
}
for x in 0..unsorted_idx {
assert!(moves[x].score >= limit);
}
for x in unsorted_idx..len {
assert!(moves[x].score < limit);
}
}
#[test]
fn movepick_startpos_blank() {
movepick_main_search(
Board::start_pos(),
BitMove::null(),
&[BitMove::null(); 2],
BitMove::null(),
5,
);
}
#[test]
fn movepick_startpos_rand_op() {
let b = Board::start_pos();
for _x in 0..25 {
movepick_rand_one(b.clone());
}
}
#[test]
fn movepick_rand_mainsearch() {
for _x in 0..15 {
let b = Board::random().one();
movepick_rand_one(b);
println!("pass movepick rand! {} ", _x);
}
}
#[test]
fn movepick_incorrect_move_1() {
let b = Board::from_fen("rnb1k2r/pppp1ppp/7n/2b1P3/4P3/2P5/PP3PPP/RN1QKBNR w KQkq - 1 6")
.unwrap();
let ttm = BitMove::new(54048);
let killers = [BitMove::new(5443), BitMove::new(65354)];
let depth = 4232;
let cm = BitMove::new(62443);
movepick_main_search(b, ttm, &killers, cm, depth);
}
#[test]
fn movepick_incorrect_move_2() {
let b =
Board::from_fen("r4r2/1n1k1pp1/p1p4p/5n1P/1PpPB3/6PR/P3NP2/3RK3 w - - 4 28").unwrap();
let ttm = BitMove::new(31709);
let killers = [BitMove::new(3442), BitMove::new(29836)];
let depth = 15927;
let cm = BitMove::new(836);
movepick_main_search(b, ttm, &killers, cm, depth);
}
#[test]
fn movepick_incorrect_move_3() {
let b = Board::from_fen("rn5r/p3nppp/2pk4/bpq5/3p1PN1/1Q2P1P1/PP2N2P/R1BK1B1R w - - 0 19")
.unwrap();
let ttm = BitMove::new(1999);
let killers = [BitMove::new(23004), BitMove::new(38167)];
let depth = 127;
let cm = BitMove::new(10191);
movepick_main_search(b, ttm, &killers, cm, depth);
}
#[test]
fn movepick_incorrect_move_4() {
let b = Board::from_fen("rnb1kbnr/pp3ppp/1q6/2pp4/1P1P4/Q7/P3PPPP/RNB1KBNR w KQkq - 0 7")
.unwrap();
let ttm = BitMove::new(21534);
let killers = [BitMove::new(17666), BitMove::new(20209)];
let depth = 19;
let cm = BitMove::new(12968);
movepick_main_search(b, ttm, &killers, cm, depth);
}
#[test]
fn movepick_incorrect_move_5() {
let b =
Board::from_fen("r5nr/2knb1Q1/p1pp4/1p2p1Pp/1P4B1/2P5/P2P1PP1/RNB1K1NR w KQ h6 0 15")
.unwrap();
let ttm = BitMove::new(17022);
let killers = [BitMove::new(16930), BitMove::new(39106)];
let depth = 88;
let cm = BitMove::new(45971);
movepick_main_search(b, ttm, &killers, cm, depth);
}
#[test]
fn movepick_incorrect_move_6() {
let b = Board::from_fen("3r4/p2p1kP1/n2B4/b1p2p2/2P2P2/PP6/6NK/3R1RN1 w - - 1 33").unwrap();
let ttm = BitMove::new(16289);
let killers = [BitMove::new(39150), BitMove::new(35998)];
let depth = 23;
let cm = BitMove::new(1836);
movepick_main_search(b, ttm, &killers, cm, depth);
}
fn movepick_rand_one(b: Board) {
let ttm = BitMove::new(rand::random());
let cm = BitMove::new(rand::random());
let killers = [BitMove::new(rand::random()), BitMove::new(rand::random())];
let depth = (rand::random::<i16>().abs() % 126).max(1);
movepick_main_search(b, ttm, &killers, cm, depth);
}
fn movepick_rand_one_q(b: Board) {
let ttm = BitMove::new(rand::random());
let cm = BitMove::new(rand::random());
let killers = [BitMove::new(rand::random()), BitMove::new(rand::random())];
let depth = ((rand::random::<i16>().abs() % 9) * -1).min(0);
movepick_main_search(b, ttm, &killers, cm, depth);
}
fn movepick_main_search(
b: Board,
ttm: BitMove,
killers: &[BitMove; 2],
cm: BitMove,
depth: i16,
) {
let real_moves = b.generate_pseudolegal_moves();
let result = panic::catch_unwind(|| {
let mut moves_mp = MoveList::default();
let main_hist: ButterflyHistory = unsafe { mem::zeroed() };
let cap_hist: CapturePieceToHistory = unsafe { mem::zeroed() };
let cont_hist1: PieceToHistory = unsafe { mem::zeroed() };
let cont_hist2: PieceToHistory = unsafe { mem::zeroed() };
let cont_hist3: PieceToHistory = unsafe { mem::zeroed() };
let cont_hist4: PieceToHistory = unsafe { mem::zeroed() };
let cont_hist = [
&cont_hist1 as *const _,
&cont_hist2 as *const _,
&cont_hist3 as *const _,
&cont_hist4 as *const _,
];
let mut mp = MovePicker::main_search(
&b,
depth,
&main_hist,
&cap_hist,
&cont_hist as *const _,
ttm,
*killers,
cm,
);
let mut mp_next = mp.next_mov(false);
while mp_next != BitMove::null() {
moves_mp.push(mp_next);
mp_next = mp.next_mov(false);
}
moves_mp
});
let moves_mp = result.expect(&format!(
"\n Unknown panic while using the movelist!\
\n depth: {}, fen: {}\
\n in check?: {}\
\n ttm: {} bits: {} \
\n killer1: {} bits: {}\
\n killer2: {} bits: {}\
\n counter: {} bits: {}\n",
depth,
b.fen(),
b.in_check(),
ttm,
ttm.get_raw(),
killers[0],
killers[0].get_raw(),
killers[1],
killers[1].get_raw(),
cm,
cm.get_raw()
));
for (i, mov) in real_moves.iter().enumerate() {
if !moves_mp.contains(mov) {
panic!(
"\nMovePicker is missing this move: {} at index {}, bits: {}\
\n Real Length: {}, MovePicker Length: {},
\n depth: {}, fen: {}\
\n in check?: {}\
\n ttm: {} bits: {} \
\n killer1: {} bits: {}\
\n killer2: {} bits: {}\
\n counter: {} bits: {}\n",
mov,
i,
mov.get_raw(),
real_moves.len(),
moves_mp.len(),
depth,
b.fen(),
b.in_check(),
ttm,
ttm.get_raw(),
killers[0],
killers[0].get_raw(),
killers[1],
killers[1].get_raw(),
cm,
cm.get_raw()
);
}
}
for (i, mov) in moves_mp.iter().enumerate() {
if !real_moves.contains(mov) {
panic!(
"\nMovePicker Returned an incorrect move: {} at index {}, bits: {}\
\n Real Length: {}, MovePicker Length: {},
\n depth: {}, fen: {}\
\n in check?: {}\
\n ttm: {} bits: {} \
\n killer1: {} bits: {}\
\n killer2: {} bits: {}\
\n counter: {} bits: {}\n",
mov,
i,
mov.get_raw(),
real_moves.len(),
moves_mp.len(),
depth,
b.fen(),
b.in_check(),
ttm,
ttm.get_raw(),
killers[0],
killers[0].get_raw(),
killers[1],
killers[1].get_raw(),
cm,
cm.get_raw()
);
}
}
if moves_mp.len() != real_moves.len() {
panic!(
"\nMovePicker did not return the correct number of moves: {}, Actual number: {}\
\n depth: {}, fen: {}\
\n in check?: {}\
\n ttm: {} bits: {} \
\n killer1: {} bits: {}\
\n killer2: {} bits: {}\
\n counter: {} bits: {}\n",
moves_mp.len(),
real_moves.len(),
depth,
b.fen(),
b.in_check(),
ttm,
ttm.get_raw(),
killers[0],
killers[0].get_raw(),
killers[1],
killers[1].get_raw(),
cm,
cm.get_raw()
);
}
}
}