|
@@ -18,6 +18,9 @@ pub struct SearchControl<'a> {
|
|
|
|
|
|
pub killer_moves: Vec<[Move; 2]>,
|
|
pub killer_moves: Vec<[Move; 2]>,
|
|
|
|
|
|
|
|
+ pub last_move: Option<Move>,
|
|
|
|
+ pub countermoves: [[Move; 64]; 64],
|
|
|
|
+
|
|
/// current halfmove clock for discarding old hash entries
|
|
/// current halfmove clock for discarding old hash entries
|
|
pub halfmove_age: u16,
|
|
pub halfmove_age: u16,
|
|
|
|
|
|
@@ -43,6 +46,8 @@ impl<'a> SearchControl<'a> {
|
|
SearchControl {
|
|
SearchControl {
|
|
nodes: 0,
|
|
nodes: 0,
|
|
killer_moves: vec![[Move::nullmove(); 2]; depth as usize],
|
|
killer_moves: vec![[Move::nullmove(); 2]; depth as usize],
|
|
|
|
+ last_move: None,
|
|
|
|
+ countermoves: [[Move::nullmove(); 64]; 64],
|
|
halfmove_age: 0,
|
|
halfmove_age: 0,
|
|
check,
|
|
check,
|
|
stopping: false,
|
|
stopping: false,
|
|
@@ -71,6 +76,13 @@ impl<'a> SearchControl<'a> {
|
|
pub fn is_killer(&self, ply_depth: usize, killer: &Move) -> bool {
|
|
pub fn is_killer(&self, ply_depth: usize, killer: &Move) -> bool {
|
|
self.killer_moves[ply_depth].contains(killer)
|
|
self.killer_moves[ply_depth].contains(killer)
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ pub fn countermove_to(&self, last_move: Move) -> Move {
|
|
|
|
+ let from = last_move.to_simple().from;
|
|
|
|
+ let to = last_move.to_simple().to;
|
|
|
|
+ self.countermoves[from as usize][to as usize]
|
|
|
|
+ }
|
|
|
|
+
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -86,7 +98,7 @@ pub fn search(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alp
|
|
let cache_entry = hash.lookup(game);
|
|
let cache_entry = hash.lookup(game);
|
|
|
|
|
|
let ply_depth = (sc.initial_depth - depth) as usize;
|
|
let ply_depth = (sc.initial_depth - depth) as usize;
|
|
- let moves = generate_legal_sorted_moves(game, hash, &sc.killer_moves[ply_depth], cache_entry.map(CacheEntry::clone), game.turn);
|
|
|
|
|
|
+ let moves = generate_legal_sorted_moves(game, hash, &sc.killer_moves[ply_depth], cache_entry.map(CacheEntry::clone), false, game.turn);
|
|
//let mut moves = generate_legal_moves(game, game.turn);
|
|
//let mut moves = generate_legal_moves(game, game.turn);
|
|
//sort_moves(game, hash, &sc.killer_moves[ply_depth], &mut moves);
|
|
//sort_moves(game, hash, &sc.killer_moves[ply_depth], &mut moves);
|
|
|
|
|
|
@@ -142,6 +154,13 @@ pub fn search(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alp
|
|
|
|
|
|
pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alpha: PosValue, beta: PosValue, mut depth: i32) -> PosValue {
|
|
pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alpha: PosValue, beta: PosValue, mut depth: i32) -> PosValue {
|
|
|
|
|
|
|
|
+ let last_move = sc.last_move;
|
|
|
|
+
|
|
|
|
+ // we can't beat an alpha this good
|
|
|
|
+ if alpha >= mate_in_p1(1) {
|
|
|
|
+ return alpha;
|
|
|
|
+ }
|
|
|
|
+
|
|
let cache_entry = hash.lookup(game);
|
|
let cache_entry = hash.lookup(game);
|
|
|
|
|
|
if let Some(e) = &cache_entry {
|
|
if let Some(e) = &cache_entry {
|
|
@@ -179,10 +198,11 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
|
|
cache_entry,
|
|
cache_entry,
|
|
game.turn);*/
|
|
game.turn);*/
|
|
|
|
|
|
- let moves = MoveGenerator::generate_legal_moves(
|
|
|
|
|
|
+ let mut moves = MoveGenerator::generate_legal_moves(
|
|
game,
|
|
game,
|
|
cache_entry,
|
|
cache_entry,
|
|
&sc.killer_moves[ply_depth],
|
|
&sc.killer_moves[ply_depth],
|
|
|
|
+ last_move.map(|lm| sc.countermove_to(lm)),
|
|
game.turn
|
|
game.turn
|
|
);
|
|
);
|
|
|
|
|
|
@@ -192,7 +212,7 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
|
|
if moves.is_empty() {
|
|
if moves.is_empty() {
|
|
if check {
|
|
if check {
|
|
// mate
|
|
// mate
|
|
- return -mate();
|
|
|
|
|
|
+ return checkmated();
|
|
}
|
|
}
|
|
else {
|
|
else {
|
|
// stalemate
|
|
// stalemate
|
|
@@ -201,7 +221,7 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
|
|
}
|
|
}
|
|
|
|
|
|
// Nullmove
|
|
// Nullmove
|
|
- if sc.nullmoves_enabled && !check && depth >= 4 && game_lateness(game) < 60 {
|
|
|
|
|
|
+ if sc.nullmoves_enabled && !check && depth >= 4 && game.get_all_side(game.turn).count_ones() > 5 {
|
|
|
|
|
|
let reduce = if depth > 5 { if depth > 7 { 5 } else { 4 }} else { 3 };
|
|
let reduce = if depth > 5 { if depth > 7 { 5 } else { 4 }} else { 3 };
|
|
|
|
|
|
@@ -213,10 +233,6 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
|
|
sc.nullmoves_enabled = true;
|
|
sc.nullmoves_enabled = true;
|
|
game.undo_move(undo);
|
|
game.undo_move(undo);
|
|
|
|
|
|
- if sc.stopping {
|
|
|
|
- return alpha;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
if is_mate_in_p1(val).is_none() {
|
|
if is_mate_in_p1(val).is_none() {
|
|
if val >= beta {
|
|
if val >= beta {
|
|
depth -= reduce - 1;
|
|
depth -= reduce - 1;
|
|
@@ -227,14 +243,10 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
|
|
|
|
|
|
let mut best_move: Option<Move> = None;
|
|
let mut best_move: Option<Move> = None;
|
|
|
|
|
|
- // we can't beat an alpha this good
|
|
|
|
- if alpha >= mate() {
|
|
|
|
- return alpha;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- for mov in moves {
|
|
|
|
|
|
+ while let Some(mov) = moves.next() {
|
|
//println!("mov: {}", mov.to_string());
|
|
//println!("mov: {}", mov.to_string());
|
|
let undo = game.apply(mov);
|
|
let undo = game.apply(mov);
|
|
|
|
+ sc.last_move = Some(mov);
|
|
|
|
|
|
let val = increase_mate_in(
|
|
let val = increase_mate_in(
|
|
-negamax(game, sc, hash, -decrease_mate_in(beta), -decrease_mate_in(alpha), depth - 1)
|
|
-negamax(game, sc, hash, -decrease_mate_in(beta), -decrease_mate_in(alpha), depth - 1)
|
|
@@ -250,12 +262,25 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
|
|
hash.cache(game, CacheEntry::new_lower(depth as _, sc.halfmove_age as _, mov.to_simple(), val));
|
|
hash.cache(game, CacheEntry::new_lower(depth as _, sc.halfmove_age as _, mov.to_simple(), val));
|
|
if !mov.is_capture() {
|
|
if !mov.is_capture() {
|
|
sc.insert_killer(ply_depth, mov);
|
|
sc.insert_killer(ply_depth, mov);
|
|
|
|
+ if depth >= 2 {
|
|
|
|
+ sc.countermoves[mov.to_simple().from as usize][mov.to_simple().to as usize] = mov;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
return val;
|
|
return val;
|
|
}
|
|
}
|
|
if val > alpha {
|
|
if val > alpha {
|
|
alpha = val;
|
|
alpha = val;
|
|
best_move = Some(mov);
|
|
best_move = Some(mov);
|
|
|
|
+ if depth >= 3 {
|
|
|
|
+ sc.countermoves[mov.to_simple().from as usize][mov.to_simple().to as usize] = mov;
|
|
|
|
+ }
|
|
|
|
+ if alpha >= mate_in_p1(1) {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if let Some(lm) = last_move {
|
|
|
|
+ moves.update_counter(sc.countermove_to(lm));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -294,9 +319,10 @@ fn quiescence_search(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache,
|
|
return alpha;
|
|
return alpha;
|
|
}
|
|
}
|
|
|
|
|
|
- if game.get_piece(KING, game.turn) == 0 { return -mate(); }
|
|
|
|
|
|
+ if game.get_piece(KING, game.turn) == 0 { return checkmated(); }
|
|
|
|
|
|
- let mut moves = generate_attacking_moves(game, game.turn);
|
|
|
|
|
|
+ //let mut moves = generate_legal_sorted_moves(game, hash, &[], None, true, game.turn);
|
|
|
|
+ let mut moves = generate_legal_moves(game, game.turn, true);
|
|
|
|
|
|
//sort_moves_no_hash(game, &mut moves);
|
|
//sort_moves_no_hash(game, &mut moves);
|
|
sort_moves_least_valuable_attacker(game, &mut moves);
|
|
sort_moves_least_valuable_attacker(game, &mut moves);
|
|
@@ -334,7 +360,7 @@ pub fn perft(game: &mut Game, sc: &mut SearchControl, depth: i32) -> bool {
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
|
|
+
|
|
for mov in moves {
|
|
for mov in moves {
|
|
let nodes_before = sc.nodes;
|
|
let nodes_before = sc.nodes;
|
|
let undo = game.apply(mov);
|
|
let undo = game.apply(mov);
|