Ver Fonte

basic LMR introduced

Nicolas Winkler há 2 anos atrás
pai
commit
a08dd83fb9
5 ficheiros alterados com 144 adições e 39 exclusões
  1. 3 0
      Cargo.toml
  2. 1 1
      src/evaluate.rs
  3. 0 1
      src/main.rs
  4. 59 12
      src/movegen.rs
  5. 81 25
      src/search.rs

+ 3 - 0
Cargo.toml

@@ -13,6 +13,9 @@ static_assertions = "1.1.0"
 [build-dependencies]
 rand = "*"
 
+[profile.dev]
+overflow-checks = false
+
 [profile.release]
 opt-level = 3
 lto = true

+ 1 - 1
src/evaluate.rs

@@ -16,7 +16,7 @@ pub const MIN_VALUE: PosValue = i32::MIN + 1;
 pub const MAX_VALUE: PosValue = i32::MAX;
 
 const MATE_VALUE: PosValue = 1i32 << 22;
-const MATE_CUTOFF: PosValue = 1i32 << 20;
+const MATE_CUTOFF: PosValue = 1i32 << 21;
 
 ///
 /// evaluation score for a position in which the player to move is checkmated

+ 0 - 1
src/main.rs

@@ -1,4 +1,3 @@
-#![feature(stdsimd)]
 pub mod uci;
 pub mod bitboard;
 pub mod movegen;

+ 59 - 12
src/movegen.rs

@@ -5,8 +5,11 @@ use board::Board;
 use static_assertions::const_assert_eq;
 use ttable::{Cache, EntryType};
 use std::iter::Iterator;
+use std::sync::Arc;
+use std::sync::Mutex;
 
 use crate::magic::magic_bishop;
+use crate::search::CountermoveTable;
 use crate::ttable::CacheEntry;
 
 pub type Side = bool;
@@ -187,6 +190,14 @@ impl Move {
         }
     }
 
+    pub fn from(&self) -> Square {
+        self.to_simple().from
+    }
+
+    pub fn to(&self) -> Square {
+        self.to_simple().to
+    }
+
     pub fn piece_type(&self) -> PieceType {
         match self {
             Move::Default{ mov: _, pc } |
@@ -247,36 +258,42 @@ enum SortingState {
 /// 
 pub struct MoveGenerator {
     moves: Vec<Move>,
+    side: Side,
 
     captures: Vec<Move>,
+    counters: Vec<(Move, i16)>,
     quiets: Vec<Move>,
 
     state: SortingState,
 
     pv_move: Option<SimpleMove>,
     killers: Vec<Move>,
-    counter: Option<Move>
+    last_move: Option<Move>,
+    counters_table: Arc<Mutex<CountermoveTable>>,
 }
 
 impl MoveGenerator {
-    pub fn generate_legal_moves(game: &mut Board, ce: Option<&CacheEntry>, killers: &[Move], counter: Option<Move>, side: Side) -> Self {
+    pub fn generate_legal_moves(game: &mut Board, ce: Option<&CacheEntry>, killers: &[Move], last_move: Option<Move>, counters_table: Arc<Mutex<CountermoveTable>>, side: Side) -> Self {
         MoveGenerator {
             moves: generate_legal_moves(game, side, false),
+            side,
             captures: Vec::with_capacity(32),
+            counters: Vec::with_capacity(32),
             quiets: Vec::with_capacity(32),
             state: if ce.is_some() { SortingState::PvMove } else { SortingState::Captures },
             pv_move: ce.map(|e| e.mov),
             killers: killers.to_vec(),
-            counter
+            last_move,
+            counters_table
         }
     }
 
     pub fn is_empty(&self) -> bool {
-        return self.moves.is_empty() && self.captures.is_empty() && self.quiets.is_empty();
+        return self.moves.is_empty() && self.captures.is_empty() && self.counters.is_empty() && self.quiets.is_empty();
     }
 
-    pub fn update_counter(&mut self, counter: Move) {
-        self.counter = Some(counter);
+    pub fn is_late(&self) -> bool {
+        return self.state == SortingState::Quiets;
     }
 }
 
@@ -342,16 +359,46 @@ impl Iterator for MoveGenerator {
                 },
                 SortingState::Counter => {
                     self.state = SortingState::Quiets;
-                    if let Some(c) = self.counter {
-                        if self.quiets.contains(&c) {
-                            self.quiets.retain(|m| *m != c);
-                            //println!("counter!");
-                            return Some(c);
+                    //continue;
+                    if let Some(lm) = self.last_move {
+                        if let Ok(countermoves) = self.counters_table.lock() {
+                            let side = self.side;
+                            for q in &mut self.quiets {
+                                let score = countermoves.get_score(side, lm, *q);
+                                if score != 0 {
+                                    self.counters.push((*q, score));
+                                    *q = Move::nullmove();
+                                }
+                            }
+                            self.quiets.retain(|m| !m.is_null());
+                            //self.counters.sort_unstable_by_key(|(_m, s)| *s);
+                            continue;
                         }
                     }
-                    continue;
                 },
                 SortingState::Quiets => {
+                    if !self.counters.is_empty() {
+                        let mut best = self.counters[0];
+                        for c in &self.counters {
+                            if c.1 > best.1 {
+                                best = *c;
+                            }
+                        }
+                        self.counters.retain(|c| *c != best);
+                        return Some(best.0);
+                    }
+
+                    let mut promo: Option<Move> = None;
+                    for q in &self.quiets {
+                        if q.is_promotion() {
+                            promo = Some(*q);
+                            break;
+                        }
+                    }
+                    if let Some(p) = promo {
+                        self.quiets.retain(|m| *m != p);
+                        return Some(p);
+                    }
                     return self.quiets.pop();
                 },
             }

+ 81 - 25
src/search.rs

@@ -1,4 +1,4 @@
-use std::{clone, sync::{Arc, RwLock, mpsc}, cell::RefCell, time::{Instant, Duration}, ops::Add};
+use std::{clone, sync::{Arc, RwLock, mpsc, Mutex}, cell::{RefCell, UnsafeCell}, time::{Instant, Duration}, ops::Add, borrow::BorrowMut};
 
 use movegen::*;
 use board::Board;
@@ -30,7 +30,7 @@ pub struct SearchControl {
     pub killer_moves: Vec<[Move; 2]>,
 
     pub last_move: Option<Move>,
-    pub countermoves: [[Move; 64]; 64],
+    pub countermoves: Arc<Mutex<CountermoveTable>>,
 
     /// current halfmove clock for discarding old hash entries
     pub halfmove_age: u16,
@@ -46,7 +46,7 @@ pub struct SearchControl {
     pub movetime: Option<Duration>,
     pub remaining_time: Option<Duration>,
 
-    nullmoves_enabled: bool,
+    reductions_allowed: bool,
 }
 
 
@@ -68,7 +68,7 @@ impl SearchControl {
             hash,
             killer_moves: Vec::new(),
             last_move: None,
-            countermoves: [[Move::nullmove(); 64]; 64],
+            countermoves: Arc::new(Mutex::new(CountermoveTable::new())),
             halfmove_age: 0,
             stop_channel,
             stopping: false,
@@ -76,7 +76,7 @@ impl SearchControl {
             movetime: None,
             remaining_time: None,
             initial_depth: 0,
-            nullmoves_enabled: true
+            reductions_allowed: true
         }
     }
 
@@ -100,12 +100,6 @@ impl SearchControl {
         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]
-    }
-
     fn reconstruct_pv(&self) -> Vec<Move> {
         let mut pv: Vec<Move> = Vec::new();
         let mut g = self.board.clone();
@@ -210,7 +204,7 @@ impl SearchControl {
 
             let result =
                 if let Some(bv) = best_val {
-                    const WINDOW_RADIUS: PosValue = 10;
+                    const WINDOW_RADIUS: PosValue = 20;
                     self.windowed_search(depth, bv, WINDOW_RADIUS)
                 } else {
                     search(self.board.clone(), self, self.hash.clone(), MIN_VALUE, MAX_VALUE, depth)
@@ -241,6 +235,10 @@ impl SearchControl {
                         }
                     }
                     else {
+                        if val.abs() > 10000 {
+                            let neg = is_mate_in_p1(-val);
+                            println!("neg of: {:?}", neg);
+                        }
                         let infostring = format!("info depth {} score cp {} time {} nodes {} nps {} hashfull {} pv{}", depth, val, elapsed.as_millis(), nodes, nps, hashfull, pv);
                         println!("{}", infostring);
                         info!("{}", infostring);
@@ -410,7 +408,8 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
         game,
         cache_entry,
         &sc.killer_moves[ply_depth],
-        last_move.map(|lm| sc.countermove_to(lm)),
+        if depth >= 3 { last_move } else { None },
+        sc.countermoves.clone(),
         game.turn
     );
 
@@ -429,16 +428,16 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
     }
 
     // Nullmove
-    if sc.nullmoves_enabled && !check && depth >= 4 && game.get_all_side(game.turn).count_ones() > 5 {
+    if sc.reductions_allowed && !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 { 4 } else { 3 }} else { 2 };
 
         let nmov = Move::nullmove();
         let undo = game.apply(nmov);
 
-        sc.nullmoves_enabled = false;
+        sc.reductions_allowed = false;
         let val = -negamax(game, sc, hash, -beta, -alpha, depth - reduce);
-        sc.nullmoves_enabled = true;
+        sc.reductions_allowed = true;
         game.undo_move(undo);
 
         if is_mate_in_p1(val).is_none() {
@@ -461,14 +460,37 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
     let mut best_move: Option<Move> = None;
 
     while let Some(mov) = moves.next() {
+
+        let mut reduce = 0;
+        let reductions_allowed_before = sc.reductions_allowed;
+        if depth > 4 && moves.is_late() && !mov.is_promotion() && reductions_allowed_before {
+            reduce = 1;
+            if depth >= 8 {
+                reduce = 2;
+            }
+            sc.reductions_allowed = false;
+        }
+
         //println!("mov: {}", mov.to_string());
         let undo = game.apply(mov);
         sc.last_move = Some(mov);
 
-        let val = increase_mate_in(
-            -negamax(game, sc, hash, -decrease_mate_in(beta), -decrease_mate_in(alpha), depth - 1)
+        let mut val = increase_mate_in(
+            -negamax(game, sc, hash, -decrease_mate_in(beta), -decrease_mate_in(alpha), depth - 1 - reduce)
         );
         game.undo_move(undo);
+        sc.reductions_allowed = reductions_allowed_before;
+
+        if reduce != 0 && (val >= beta || val > alpha) {
+            reduce = 0;
+            let undo = game.apply(mov);
+            sc.last_move = Some(mov);
+
+            val = increase_mate_in(
+                -negamax(game, sc, hash, -decrease_mate_in(beta), -decrease_mate_in(alpha), depth - 1 - reduce)
+            );
+            game.undo_move(undo);
+        }
 
         // return if the search has been cancelled
         if sc.stopping {
@@ -480,7 +502,9 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
             if !mov.is_capture() {
                 sc.insert_killer(ply_depth, mov);
                 if depth >= 2 {
-                    sc.countermoves[mov.to_simple().from as usize][mov.to_simple().to as usize] = mov;
+                    if let Some(lm) = last_move {
+                        sc.countermoves.as_ref().lock().unwrap().update_score(game.turn, lm, mov, (depth * depth) as i16);
+                    }
                 }
             }
             return val;
@@ -488,8 +512,10 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
         if val > alpha {
             alpha = val;
             best_move = Some(mov);
-            if depth >= 3 {
-                sc.countermoves[mov.to_simple().from as usize][mov.to_simple().to as usize] = mov;
+            if depth >= 2 {
+                if let Some(lm) = last_move {
+                    //sc.countermoves.as_ref().lock().unwrap().update_score(lm, mov, (depth * depth) as i16);
+                }
             }
             if alpha >= mate_in_p1(1) {
                 break;
@@ -497,7 +523,7 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
         }
 
         if let Some(lm) = last_move {
-            moves.update_counter(sc.countermove_to(lm));
+            //moves.update_counter(sc.countermove_to(lm));
         }
     }
 
@@ -593,14 +619,14 @@ impl SearchControl {
             initial_depth: depth,
             killer_moves: Vec::new(),
             last_move: None,
-            countermoves: [[Move::nullmove(); 64]; 64],
+            countermoves: Arc::new(Mutex::new(CountermoveTable::new())),
             halfmove_age: board.ply_number() as _,
             stop_channel,
             stopping: false,
             search_started: Instant::now(),
             movetime: None,
             remaining_time: None,
-            nullmoves_enabled: false
+            reductions_allowed: false
         }
     }
 
@@ -640,8 +666,38 @@ impl SearchControl {
 }
 
 
+pub struct CountermoveTable {
+    table: [[[[i16; 12]; 64]; 12]; 64],
+}
+
+unsafe impl Send for CountermoveTable { }
+unsafe impl Sync for CountermoveTable { }
+
+impl CountermoveTable {
 
+    pub fn new() -> Self {
+        CountermoveTable { table: [[[[0; 12]; 64]; 12]; 64] }
+    }
+
+    pub fn get_score(&self, side: Side, last_move: Move, this_move: Move) -> i16 {
+        let side_offs = if side == BLACK { 6 } else { 0 };
+        self.table
+            [last_move.to() as usize]
+            [last_move.piece_type() as usize + side_offs]
+            [this_move.to() as usize]
+            [this_move.piece_type() as usize + side_offs]
+    }
 
+    pub fn update_score(&mut self, side: Side, last_move: Move, this_move: Move, increase: i16) {
+        let side_offs = if side == BLACK { 6 } else { 0 };
+        let entry = &mut self.table
+            [last_move.to() as usize]
+            [last_move.piece_type() as usize + side_offs]
+            [this_move.to() as usize]
+            [this_move.piece_type() as usize + side_offs];
+        *entry = entry.saturating_add(increase);
+    }
+}
 
 
 #[cfg(test)]