Bläddra i källkod

fixing mate score bug and add Hash size option

Nicolas Winkler 2 år sedan
förälder
incheckning
10210d687b
6 ändrade filer med 171 tillägg och 76 borttagningar
  1. 31 1
      src/engine.rs
  2. 29 20
      src/evaluate.rs
  3. 3 1
      src/movegen.rs
  4. 56 52
      src/search.rs
  5. 11 2
      src/ttable.rs
  6. 41 0
      src/uci.rs

+ 31 - 1
src/engine.rs

@@ -21,6 +21,7 @@ pub enum Command {
     Position{ pos: Board, moves: Vec<String> },
     SetPiece(Bitboard),
     Go(SearchInfo),
+    SetOption{ name: String, val: String },
     Print,
     IsReady,
     Ping,
@@ -31,6 +32,10 @@ pub enum Command {
     Quit,
 }
 
+pub enum EngineOptionType {
+    Spin{ min: i64, max: i64, default: i64 },
+}
+
 ///
 /// Structure holding information for a search command.
 /// 
@@ -89,7 +94,7 @@ impl Engine {
         let mut eng = Engine {
             board: Board::default(),
             move_history: RepetitionTable::new(),
-            hash: Arc::new(RwLock::new(Cache::megabytes(4))),
+            hash: Arc::new(RwLock::new(Cache::new_in_megabytes(4))),
             zobrist_table: Arc::new(ZobristTable::new()),
             search_thread: None,
         };
@@ -119,14 +124,22 @@ impl Engine {
             Command::Uci => {
                 println!("id name bishop 1.0");
                 println!("id author Geile Siech");
+                println!("option name Hash type spin default 4 min 0 max 100000000");
                 println!("uciok");
             },
             Command::IsReady => {
                 println!("readyok");
             },
+            Command::NewGame => {
+                self.set_position(Board::default(), &Vec::new());
+                self.hash.write().unwrap().clear();
+            },
             Command::Position { pos, moves } => {
                 self.set_position(pos, &moves);
             },
+            Command::SetOption { name, val } => {
+                self.set_option(&name, &val);
+            },
             Command::Go(si) => {
                 self.stop_search();
                 self.start_search(&si);
@@ -202,4 +215,21 @@ impl Engine {
             let _res = st.join_handle.join().unwrap();
         }
     }
+
+    fn set_option(&mut self, name: &str, val: &str) {
+        match name {
+            "Hash" => {
+                let size = val.parse::<usize>();
+                if let Ok(megabytes) = size {
+                    self.hash = Arc::new(RwLock::new(Cache::new_in_megabytes(megabytes)));
+                }
+                else {
+                    println!("invalid hash size");
+                }
+            },
+            n => {
+                println!("unknown option '{}'", n);
+            }
+        }
+    }
 }

+ 29 - 20
src/evaluate.rs

@@ -12,26 +12,26 @@ use std::i32;
 /// 
 pub type PosValue = i32;
 
-pub const MIN_VALUE: PosValue = i32::MIN + 1;
-pub const MAX_VALUE: PosValue = i32::MAX;
+pub const MIN_VALUE: PosValue = -(1i32 << 22);
+pub const MAX_VALUE: PosValue = 1i32 << 22;
 
-const MATE_VALUE: PosValue = 1i32 << 22;
-const MATE_CUTOFF: PosValue = 1i32 << 21;
+const MATE_VALUE: PosValue = 1000000; //1i32 << 21;
+const MATE_CUTOFF: PosValue = 900000; // 1i32 << 20;
 
 ///
 /// evaluation score for a position in which the player to move is checkmated
 /// 
 pub fn checkmated() -> PosValue {
-    -mate_in_p1(0)
+    -mate_in_plies(0)
 }
 
 /// 
 /// constructs a PosValue that indicates that from a given position mate
 /// can be achieved in plies halfmoves
 /// 
-pub fn mate_in_p1(plies: i32) -> PosValue {
+pub fn mate_in_plies(plies: i32) -> PosValue {
     if plies < 0 {
-        return -mate_in_p1(- plies);
+        return -mate_in_plies(- plies);
     }
     else {
         return MATE_VALUE - plies;
@@ -45,7 +45,7 @@ pub fn mate_in_p1(plies: i32) -> PosValue {
 /// turns is negative if the moving side is getting mated
 /// in turns moves
 /// 
-pub fn is_mate_in_p1(pos_val: PosValue) -> Option<i32> {
+pub fn is_mate_in_plies(pos_val: PosValue) -> Option<i32> {
     if pos_val > MATE_CUTOFF && pos_val <= MATE_VALUE {
         Some(-pos_val + MATE_VALUE)
     }
@@ -58,7 +58,16 @@ pub fn is_mate_in_p1(pos_val: PosValue) -> Option<i32> {
 }
 
 pub fn increase_mate_in(mut val: PosValue) -> PosValue {
-    if let Some(plies) = is_mate_in_p1(val) {
+    if val > MATE_CUTOFF {
+        val - 1
+    }
+    else if val < -MATE_CUTOFF {
+        val + 1
+    }
+    else {
+        val
+    }
+    /*if let Some(plies) = is_mate_in_p1(val) {
         if plies < 0 {
             val = mate_in_p1(plies - 1);
         }
@@ -74,19 +83,19 @@ pub fn increase_mate_in(mut val: PosValue) -> PosValue {
             }
         }
     }
-    val
+    val*/
 }
 
 pub fn decrease_mate_in(mut val: PosValue) -> PosValue {
-    if let Some(plies) = is_mate_in_p1(val) {
-        if plies < 0 {
-            val = mate_in_p1(plies + 1);
-        }
-        if plies > 0 {
-            val = mate_in_p1(plies - 1);
-        }
+    if val > MATE_CUTOFF {
+        val + 1
+    }
+    else if val < -MATE_CUTOFF {
+        val - 1
+    }
+    else {
+        val
     }
-    val
 }
 
 
@@ -330,8 +339,8 @@ mod tests {
 
     #[test]
     fn test_mate_values() {
-        let mate_in_three = mate_in_p1(3);
-        let mate_in_four = mate_in_p1(4);
+        let mate_in_three = mate_in_plies(3);
+        let mate_in_four = mate_in_plies(4);
 
         assert!(mate_in_three > mate_in_four);
         assert!(mate_in_three > mate_in_four);

+ 3 - 1
src/movegen.rs

@@ -50,7 +50,9 @@ pub enum Move {
     Promotion { mov: SimpleMove, pc: PieceCaptureType },
 }
 
-const_assert_eq!(std::mem::size_of::<Move>(), 4);
+// not true on arm
+// also not necessary
+// const_assert_eq!(std::mem::size_of::<Move>(), 4);
 
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]

+ 56 - 52
src/search.rs

@@ -57,6 +57,8 @@ pub enum SearchResult {
     Finished(Move, PosValue),
     NoMove(PosValue),
     Cancelled(Option<(Move, PosValue)>),
+    FailHigh(PosValue),
+    FailLow(PosValue),
     PerftFinished,
     Invalid
 }
@@ -164,28 +166,32 @@ impl SearchControl {
         let mut result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
 
         loop {
-            if let SearchResult::Finished(_mov, val) = result {
-                if val > alpha && val < beta {
-                    // successful search within bounds
-                    break;
-                }
-                if val <= alpha {
+            match result {
+                SearchResult::FailHigh(_val) => {
+                    beta_coeff *= 3;
+                    beta = expected_value + initial_window_size * beta_coeff;
+                    if beta_coeff >= 9 {
+                        beta = MAX_VALUE;
+                    }
+                    result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
+                },
+                SearchResult::FailLow(_val) => {
                     alpha_coeff *= 3;
                     alpha = expected_value - initial_window_size * alpha_coeff;
+                    if alpha_coeff >= 9 {
+                        alpha = MIN_VALUE;
+                    }
                     result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
+                },
+                SearchResult::Finished(_mov, val) => {
+                    // successful search within bounds
+                    break;
                 }
-                if val >= beta {
-                    beta_coeff *= 3;
-                    beta = expected_value + initial_window_size * beta_coeff;
+                SearchResult::Invalid => {
+                    alpha = MIN_VALUE; beta = MAX_VALUE;
                     result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
                 }
-            }
-            else if let SearchResult::Invalid = result {
-                alpha = MIN_VALUE; beta = MAX_VALUE;
-                result = search(self.board.clone(), self, self.hash.clone(), alpha, beta, depth);
-            }
-            else {
-                break;
+                _ => break,
             }
         }
 
@@ -226,7 +232,7 @@ impl SearchControl {
                     let pv = &pv_array.iter().filter(|&m| !m.is_null()).map(|m| m.to_string()).fold(String::new(), |a, b| a + " " + &b)[0..];
 
 
-                    if let Some(plies) = crate::evaluate::is_mate_in_p1(val) {
+                    if let Some(plies) = crate::evaluate::is_mate_in_plies(val) {
                         //println!("plies = {}", plies);
                         let turns = (plies + if plies > 0 { 1 } else { -1 }) / 2;
                         let infostring = format!("info depth {} score mate {} time {} nodes {} nps {} hashfull {} pv{}", depth, turns, elapsed.as_millis(), nodes, nps, hashfull, pv);
@@ -294,11 +300,6 @@ pub fn search(mut board: Board, sc: &mut SearchControl, hashs: Arc<RwLock<Cache>
     
     info!("moves: {:?}", moves.iter().map(|mv| mv.to_string()).collect::<Vec<String>>());
 
-    // use a slight offset for the alpha value in the root node in order to
-    // determine possibly multiple good moves
-    const ALPHA_OFFSET: PosValue = 0 as PosValue;
-
-    let mut valued_moves: Vec<(Move, PosValue)> = Vec::with_capacity(moves.len());
     let mut cancelled = false;
 
     if moves.is_empty() {
@@ -310,13 +311,17 @@ pub fn search(mut board: Board, sc: &mut SearchControl, hashs: Arc<RwLock<Cache>
         }
     }
 
+    let mut best_move: Option<Move> = None;
+
     for mov in moves {
         let undo = board.apply(mov);
 
-        let val = -negamax(&mut board, sc, &mut hash, decrease_mate_in(-beta), decrease_mate_in(-alpha), depth - 1);
+        let val = increase_mate_in(
+            -negamax(&mut board, sc, &mut hash, decrease_mate_in(-beta), decrease_mate_in(-alpha), depth - 1)
+        );
 
         //info!("moveval {} -> {}\n", mov.to_string(), val);
-        
+
         board.undo_move(undo);
 
         if sc.stopping {
@@ -324,35 +329,30 @@ pub fn search(mut board: Board, sc: &mut SearchControl, hashs: Arc<RwLock<Cache>
             break;
         }
 
-        if increase_mate_in(val) > alpha {
-            alpha = increase_mate_in(val) - ALPHA_OFFSET;
-            valued_moves.push((mov, -alpha));
+        if val >= beta {
+            hash.cache(&board, CacheEntry::new_lower(depth as _, sc.halfmove_age as _, mov.to_simple(), val));
+            return SearchResult::FailHigh(val);
         }
-    }
 
-    valued_moves.sort_by_key(|mv| mv.1);
+        if val > alpha {
+            alpha = val;
+            best_move = Some(mov);
+        }
+    }
 
-    if valued_moves.len() > 0 {
-        let min_val = valued_moves[0].1;
-        let best_moves = valued_moves.iter().filter(|mv| mv.1 == min_val).collect::<Vec<&(Move, PosValue)>>();
+    if cancelled {
+        return SearchResult::Cancelled(None);
+    }
 
-        let mut rng = rand::thread_rng();
-        let chosen_mov = best_moves[(rng.next_u64() % best_moves.len() as u64) as usize];
-        if cancelled {
-            return SearchResult::Cancelled(Some((chosen_mov.0, -chosen_mov.1)));
-        }
-        else {
-            hash.cache(&board, CacheEntry::new_value(depth as _, sc.halfmove_age as _, chosen_mov.0.to_simple(), chosen_mov.1 as _));
-            return SearchResult::Finished(chosen_mov.0, -chosen_mov.1);
-        }
+    if let Some(mov) = best_move {
+        // alpha is exact
+        let ce = CacheEntry::new_value(depth as _, sc.halfmove_age as _, mov.to_simple(), alpha);
+        hash.cache(&board, ce);
+        return SearchResult::Finished(mov, alpha);
     }
     else {
-        if cancelled {
-            return SearchResult::Cancelled(None);
-        }
-        else {
-            return SearchResult::Invalid;
-        }
+        hash.cache(&board, CacheEntry::new_upper(depth as _, sc.halfmove_age as _, SimpleMove { from: 0, to: 0 }, alpha));
+        return SearchResult::FailLow(alpha);
     }
 }
 
@@ -362,11 +362,11 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
     let last_move = sc.last_move;
 
     // we can't beat an alpha this good
-    if alpha >= mate_in_p1(1) {
+    if alpha >= mate_in_plies(1) {
         return alpha;
     }
 
-    let cache_entry = hash.lookup(game);
+    let cache_entry = hash.lookup(game).map(CacheEntry::clone);
 
     if let Some(e) = &cache_entry {
         if e.depth() as i32 >= depth {
@@ -379,6 +379,7 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
                 EntryType::UpperBound => {
                     if e.value() < alpha { return alpha; }
                 },
+                EntryType::NoBound => {}
             }
         }
     }
@@ -413,7 +414,7 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
 
     let mut moves = MoveGenerator::generate_legal_moves(
         game,
-        cache_entry,
+        cache_entry.as_ref(),
         &sc.killer_moves[ply_depth],
         if depth >= 3 { last_move } else { None },
         sc.countermoves.clone(),
@@ -447,7 +448,7 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
         sc.reductions_allowed = true;
         game.undo_move(undo);
 
-        if is_mate_in_p1(val).is_none() {
+        if is_mate_in_plies(val).is_none() {
             if val >= beta {
                 depth -= reduce - 1;
                 //return beta;
@@ -519,12 +520,15 @@ pub fn negamax(game: &mut Board, sc: &mut SearchControl, hash: &mut Cache, mut a
         if val > alpha {
             alpha = val;
             best_move = Some(mov);
+            if sc.initial_depth >= 10 && cache_entry.is_some() && cache_entry.as_ref().unwrap().entry_type == EntryType::Value {
+                //println!("mov: {}", mov.to_string());
+            }
             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) {
+            if alpha >= mate_in_plies(1) {
                 break;
             }
         }

+ 11 - 2
src/ttable.rs

@@ -13,6 +13,7 @@ pub enum EntryType {
     Value,
     LowerBound,
     UpperBound,
+    NoBound
 }
 
 #[derive(Clone)]
@@ -82,7 +83,7 @@ pub struct RepetitionTable {
 }
 
 impl Cache {
-    pub fn megabytes(mbytes: usize) -> Self {
+    pub fn new_in_megabytes(mbytes: usize) -> Self {
         Self::new(mbytes * 1024 * 1024 / std::mem::size_of::<Bucket>())
     }
 
@@ -156,7 +157,9 @@ impl Cache {
 
         for i in 0..bucket.hashes.len() {
             if bucket.hashes[i] == hash {
-                bucket.entries[i] = ce;
+                if bucket.entries[i].depth() <= ce.depth() {
+                    bucket.entries[i] = ce;
+                }
                 return;
             }
         }
@@ -177,6 +180,12 @@ impl Cache {
         }
         permill
     }
+
+    pub fn clear(&mut self) {
+        for bucket in &mut self.table {
+            bucket.hashes = [0; 4];
+        }
+    }
 }
 
 

+ 41 - 0
src/uci.rs

@@ -12,6 +12,8 @@ pub fn parse_command(command: &str) -> Result<Command, String> {
         match command {
             "uci" => Ok(Command::Uci),
             "isready" => Ok(Command::IsReady),
+            "ucinewgame" => Ok(Command::NewGame),
+            "setoption" => parse_setoption(&args),
             "position" => parse_position(&args),
             "go" => parse_go(&args),
             "stop" => Ok(Command::Stop),
@@ -107,6 +109,45 @@ pub fn parse_go(args: &Vec<&str>) -> Result<Command, String> {
     Ok(Command::Go(si))
 }
 
+pub fn parse_setoption(args: &Vec<&str>) -> Result<Command, String> {
+    enum State {
+        None,
+        Name,
+        Value
+    }
+
+    let mut state = State::None;
+    let mut name: String = String::new();
+    let mut value: String = String::new();
+
+    let extend_string = |s: &mut String, ex: &str| {
+        if !s.is_empty() {
+            s.push_str(" ");
+        }
+        s.push_str(ex);
+    };
+
+    for arg in args {
+        match *arg {
+            "name" => {
+                state = State::Name;
+            },
+            "value" => {
+                state = State::Value;
+            },
+            any => {
+                match state {
+                    State::Name => extend_string(&mut name, any),
+                    State::Value => extend_string(&mut value, any),
+                    State::None => return Err("invalid option".to_owned())
+                }
+            }
+        }
+    }
+
+    Ok(Command::SetOption { name: name, val: value })
+}
+