Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix unsound search components. #200

Merged
merged 2 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/board/movegen/movepicker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub struct MovePicker<MovePickerMode> {
counter_move: Option<Move>,
pub skip_quiets: bool,
see_threshold: i32,
in_check: bool,
_mode: std::marker::PhantomData<MovePickerMode>,
}

Expand All @@ -60,6 +61,7 @@ impl<Mode: MovePickerMode> MovePicker<Mode> {
killers: [Option<Move>; 2],
counter_move: Option<Move>,
see_threshold: i32,
in_check: bool,
) -> Self {
debug_assert!(killers[0].is_none() || killers[0] != killers[1], "Killers are both {:?}", killers[0]);
Self {
Expand All @@ -71,6 +73,7 @@ impl<Mode: MovePickerMode> MovePicker<Mode> {
counter_move,
skip_quiets: false,
see_threshold,
in_check,
_mode: std::marker::PhantomData,
}
}
Expand All @@ -82,6 +85,7 @@ impl<Mode: MovePickerMode> MovePicker<Mode> {
}

/// Select the next move to try. Returns None if there are no more moves to try.
#[allow(clippy::cognitive_complexity)]
pub fn next(&mut self, position: &Board, t: &ThreadData) -> Option<MoveListEntry> {
if self.stage == Stage::Done {
return None;
Expand All @@ -97,7 +101,12 @@ impl<Mode: MovePickerMode> MovePicker<Mode> {
if self.stage == Stage::GenerateCaptures {
self.stage = Stage::YieldGoodCaptures;
debug_assert_eq!(self.movelist.len(), 0, "movelist not empty before capture generation");
position.generate_captures::<Mode>(&mut self.movelist);
// when we're in check, we want to generate enough moves to prove we're not mated.
if self.in_check {
position.generate_captures::<MainSearch>(&mut self.movelist);
} else {
position.generate_captures::<Mode>(&mut self.movelist);
}
Self::score_captures(t, position, &mut self.movelist);
}
if self.stage == Stage::YieldGoodCaptures {
Expand All @@ -110,7 +119,7 @@ impl<Mode: MovePickerMode> MovePicker<Mode> {
// the index so we can try this move again.
self.index -= 1;
}
self.stage = if Mode::CAPTURES_ONLY { Stage::Done } else { Stage::YieldKiller1 };
self.stage = if Mode::CAPTURES_ONLY && !self.in_check { Stage::Done } else { Stage::YieldKiller1 };
}
if self.stage == Stage::YieldKiller1 {
self.stage = Stage::YieldKiller2;
Expand Down
2 changes: 1 addition & 1 deletion src/perft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub fn movepicker_perft(pos: &mut Board, t: &mut ThreadData, depth: usize) -> u6
return 1;
}

let mut ml = MainMovePicker::new(None, [None, None], None, 0);
let mut ml = MainMovePicker::new(None, [None, None], None, 0, pos.in_check());

let mut count = 0;
while let Some(m) = ml.next(pos, t) {
Expand Down
29 changes: 20 additions & 9 deletions src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,13 @@ impl Board {
/// Give a legal default move in the case where we don't have enough time to search.
fn default_move(&mut self, t: &ThreadData) -> Move {
let tt_move = t.tt.probe_for_provisional_info(self.zobrist_key()).and_then(|e| e.0);
let mut mp = MovePicker::<MainSearch>::new(tt_move, self.get_killer_set(t), t.get_counter_move(self), 0);
let mut mp = MovePicker::<MainSearch>::new(
tt_move,
self.get_killer_set(t),
t.get_counter_move(self),
0,
self.in_check(),
);
let mut m = None;
while let Some(MoveListEntry { mov, .. }) = mp.next(self, t) {
if !self.make_move_simple(mov) {
Expand All @@ -418,7 +424,7 @@ impl Board {
}

/// Perform a tactical resolution search, searching only captures and promotions.
#[allow(clippy::too_many_lines)]
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn quiescence<NT: NodeType>(
&mut self,
pv: &mut PVariation,
Expand Down Expand Up @@ -489,7 +495,7 @@ impl Board {

if in_check {
// could be being mated!
raw_eval = -INFINITY;
raw_eval = VALUE_NONE;
stand_pat = -INFINITY;
} else if let Some(TTHit { eval: tt_eval, .. }) = &tt_hit {
// if we have a TT hit, check the cached TT eval.
Expand Down Expand Up @@ -537,7 +543,8 @@ impl Board {
let mut best_score = stand_pat;

let mut moves_made = 0;
let mut move_picker = CapturePicker::new(tt_hit.and_then(|e| e.mov), [None; 2], None, info.conf.qs_see_bound);
let mut move_picker =
CapturePicker::new(tt_hit.and_then(|e| e.mov), [None; 2], None, info.conf.qs_see_bound, in_check);
if !in_check {
move_picker.skip_quiets = true;
}
Expand All @@ -555,6 +562,8 @@ impl Board {
if !self.make_move(m, t) {
continue;
}
// move found, we can start skipping quiets again:
move_picker.skip_quiets = true;
info.nodes.increment();
moves_made += 1;

Expand All @@ -579,7 +588,7 @@ impl Board {
}

if moves_made == 0 && in_check {
return -5000; // weird but works
return mated_in(height);
}

let flag = if best_score >= beta {
Expand Down Expand Up @@ -753,8 +762,8 @@ impl Board {

if in_check {
// when we're in check, it could be checkmate, so it's unsound to use evaluate().
raw_eval = -INFINITY;
static_eval = -INFINITY;
raw_eval = VALUE_NONE;
static_eval = VALUE_NONE;
} else if excluded.is_some() {
// if we're in a singular-verification search, we already have the static eval.
// we can set raw_eval to whatever we like, because we're not going to be saving it.
Expand Down Expand Up @@ -915,7 +924,7 @@ impl Board {
// don't probcut if we have a tthit with value < pcbeta and depth >= depth - 3:
&& !matches!(tt_hit, Some(TTHit { value: v, depth: d, .. }) if v < pc_beta && d >= depth - 3)
{
let mut move_picker = CapturePicker::new(tt_move, [None; 2], None, 0);
let mut move_picker = CapturePicker::new(tt_move, [None; 2], None, 0, in_check);
while let Some(MoveListEntry { mov: m, score: ordering_score }) = move_picker.next(self, t) {
if ordering_score < WINNING_CAPTURE_SCORE {
break;
Expand Down Expand Up @@ -960,7 +969,7 @@ impl Board {

let killers = self.get_killer_set(t);
let counter_move = t.get_counter_move(self);
let mut move_picker = MainMovePicker::new(tt_move, killers, counter_move, info.conf.main_see_bound);
let mut move_picker = MainMovePicker::new(tt_move, killers, counter_move, info.conf.main_see_bound, in_check);

let mut quiets_tried = ArrayVec::<_, MAX_POSITION_MOVES>::new();
let mut tacticals_tried = ArrayVec::<_, MAX_POSITION_MOVES>::new();
Expand Down Expand Up @@ -1228,6 +1237,8 @@ impl Board {

// this heuristic is on the whole unmotivated, beyond mere empiricism.
// perhaps it's really important to know which quiet moves are good in "bad" positions?
// note: if in check, static_eval will be VALUE_NONE, but this probably doesn't cause
// any issues.
let history_depth_boost = i32::from(static_eval <= alpha);
self.update_quiet_history(t, quiets_tried.as_slice(), best_move, depth + history_depth_boost);
}
Expand Down
Loading