瀏覽代碼

reworked transposition table

Nicolas Winkler 2 年之前
父節點
當前提交
0c6e416243
共有 7 個文件被更改,包括 129 次插入166 次删除
  1. 1 2
      build.rs
  2. 1 1
      src/engine.rs
  3. 14 0
      src/game.rs
  4. 2 66
      src/main.rs
  5. 37 59
      src/movegen.rs
  6. 7 9
      src/search.rs
  7. 67 29
      src/ttable.rs

+ 1 - 2
build.rs

@@ -204,6 +204,7 @@ fn create_magic_slider(s: Square, diagonal: bool) -> Option<(u64, u64, Vec<Bitbo
 
 
 fn create_tables(file: &mut File) {
+    writeln!(file, "use crate::bitboard::*;").unwrap();
     create(file, false, "ROOK");
     create(file, true, "BISHOP");
 }
@@ -225,8 +226,6 @@ fn create(file: &mut File, diagonal: bool, prefix: &str) {
         }
     }
 
-    writeln!(file, "use crate::bitboard::*;").unwrap();
-
     write!(file, "pub const {}_MASKS_AND_MAGICS: [(Bitboard, Bitboard); 64] = [", prefix).unwrap();
     for square in 0..64 {
         write!(file, "({}, {}), ", masks[square], magics[square]).unwrap();

+ 1 - 1
src/engine.rs

@@ -7,7 +7,7 @@ use log::{info};
 use std::time::{Duration, Instant};
 use std::collections::{VecDeque};
 use zobrist::ZobristTable;
-use hash::{Cache, RepetitionTable};
+use ttable::{Cache, RepetitionTable};
 
 use std::sync::Arc;
 use std::sync::mpsc::{Receiver, Sender};

+ 14 - 0
src/game.rs

@@ -574,6 +574,20 @@ impl Game {
     /// TODO: improve
     /// 
     pub fn is_legal(&self, mov: Move) -> bool {
+        /*match mov {
+            Move::Default { mov, pc } => {
+                let piece_type = pc.piece_type();
+                let capture = pc.capture_type();
+
+                let legal_from = self.get_piece(piece_type, self.turn);
+
+                match piece_type {
+                    PAWN => {
+                        if pawn
+                    }
+                }
+            }
+        }*/
         movegen::generate_legal_moves(&mut self.clone(), self.turn, false).contains(&mov)
     }
 

+ 2 - 66
src/main.rs

@@ -6,7 +6,7 @@ pub mod game;
 pub mod evaluate;
 pub mod search;
 pub mod zobrist;
-pub mod hash;
+pub mod ttable;
 pub mod settings;
 pub mod magic;
 
@@ -15,74 +15,10 @@ extern crate simplelog;
 extern crate rand;
 
 use std::sync::{mpsc};
-use bitboard::{Bitboard};
-use rand::RngCore;
-use crate::bitboard::{Square, print_board, ROW_4};
 use std::{thread, fs::File};
-use bitboard::{from_square};
+use engine::Engine;
 use log::*;
-use movegen::generate_sliding_destinations;
 use simplelog::*;
-use engine::Engine;
-
-use crate::bitboard::{FILE_A, FILE_H, ROW_1, ROW_8, ROW_3};
-
-
-fn extract(mut mask: Bitboard, index: u32) -> Bitboard {
-    let required_bits = mask.count_ones();
-    let mut result: Bitboard = 0;
-    for bit in 0..required_bits {
-        let idx = mask.trailing_zeros();
-        if (1 << bit) & index != 0 {
-            result |= 1 << idx;
-        }
-        mask = mask & !(1 << idx);
-    }
-    return result;
-}
-
-pub fn low_one_random() -> u64 {
-    let mut rng = rand::thread_rng();
-    rng.next_u64() & rng.next_u64() & rng.next_u64()
-}
-
-fn create_magic_bishop(s: Square) -> Option<(u64, Vec<Bitboard>)> {
-    let border_mask = FILE_A | FILE_H | ROW_1 | ROW_8;
-    let bitboard = from_square(s);
-    let patt = generate_sliding_destinations(border_mask, 0, bitboard, false, true);
-
-    let required_bits = patt.count_ones();
-    
-    for _ in 0..1000000 {
-        let mut table: [Bitboard; 128] = [0; 128];
-        let magic = low_one_random();
-        let mut failed = false;
-        for i in 0..(1 << required_bits) {
-            let b = extract(patt, i);
-            let correct_result = generate_sliding_destinations(0, b, bitboard, false, true);
-
-            let index = magic.wrapping_mul(b).wrapping_shr(64 - required_bits) as usize;
-            if table[index] == 0 {
-                table[index] = correct_result;
-            }
-            else if table[index] != correct_result {
-                failed = true;
-                break;
-            }
-
-            //println!("{}", print_board(b));
-        }
-
-        if !failed {
-            println!("OMG: {:16x}", magic);
-            return Some((magic, table.to_vec()));
-        }
-
-    }
-
-    println!("att\n{}", print_board(patt));
-    return None;
-}
 
 
 fn main() {

+ 37 - 59
src/movegen.rs

@@ -2,12 +2,10 @@ use bitboard::Bitboard;
 use bitboard::Square;
 use bitboard::*;
 use game::Game;
-use hash::{Cache, EntryType};
-use std::f32::consts::PI;
+use ttable::{Cache, EntryType};
 use std::iter::Iterator;
 
-use crate::evaluate::evaluate;
-use crate::hash::CacheEntry;
+use crate::ttable::CacheEntry;
 
 pub type Side = bool;
 
@@ -30,6 +28,11 @@ pub struct SimpleMove {
     pub to: Square,
 }
 
+///
+/// stores a PieceType and an additional Option<PieceType> in one single byte
+/// (under the assumption that there are only 6 pieces). This allows the Mov
+/// structure to be 4 bytes only
+/// 
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub struct PieceCaptureType(u8);
 
@@ -206,8 +209,6 @@ enum SortingState {
 /// generates and sorts moves which can then be extracted via an Iterator interface
 /// 
 pub struct MoveGenerator {
-
-    game: Game,
     moves: Vec<Move>,
 
     captures: Vec<Move>,
@@ -222,10 +223,9 @@ pub struct MoveGenerator {
 impl MoveGenerator {
     pub fn generate_legal_moves(game: &mut Game, ce: Option<&CacheEntry>, killers: &[Move], side: Side) -> Self {
         MoveGenerator {
-            game: game.clone(),
             moves: generate_legal_moves(game, side, false),
-            captures: Vec::new(),
-            quiets: Vec::new(),
+            captures: 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()
@@ -254,8 +254,14 @@ impl Iterator for MoveGenerator {
                 },
                 SortingState::Captures => {
                     if self.captures.is_empty() {
-                        self.captures = self.moves.iter().filter(|m| m.is_capture()).map(|m| m.clone()).collect();
-                        self.quiets = self.moves.iter().filter(|m| !m.is_capture()).map(|m| m.clone()).collect();
+                        for m in &self.moves {
+                            if m.is_capture() {
+                                self.captures.push(*m);
+                            }
+                            else {
+                                self.quiets.push(*m);
+                            }
+                        }
                         self.moves.clear();
                         if self.captures.is_empty() {
                             self.state = SortingState::Killers;
@@ -442,7 +448,7 @@ pub fn generate_legal_sorted_moves(game: &mut Game, _hash: &mut Cache, killers:
     let mov_val= |mov: &Move| {
         // if its a pv move from previously
         if let Some(c) = &ce {
-            if &c.mov == mov { 
+            if c.mov == *mov { 
                 return -2000;
             }
         };
@@ -480,10 +486,10 @@ pub fn sort_moves(game: &mut Game, hash: &mut Cache, killers: &[Move], move_list
                 return e.value;
             }
             else if let EntryType::LowerBound = e.entry_type {
-                return e.value - 1000;
+                return e.value + 1000;
             }
             else if let EntryType::UpperBound = e.entry_type {
-                return e.value + 1000;
+                return e.value - 1000;
             }
             else {
                 return crate::evaluate::evaluate(game) - if killers.contains(mov) { 200 } else { 0 };
@@ -593,11 +599,11 @@ fn generate_pawn_captures(game: &Game, side: Side, move_list: &mut Vec<Move>) {
     let pawn_iterator = BitboardIterator(pawns);
 
     for pawn in pawn_iterator {
-        let left_cap = match side {
+        let right_cap = match side {
             WHITE => northeast_one(pawn),
             BLACK => southeast_one(pawn)
         };
-        let right_cap = match side {
+        let left_cap = match side {
             WHITE => northwest_one(pawn),
             BLACK => southwest_one(pawn)
         };
@@ -766,13 +772,7 @@ fn generate_sliding_moves(game: &Game, friends: Bitboard, others: Bitboard, piec
     let mask = if captures_only { others } else { !(0 as Bitboard) };
 
     for piece in pieces_iter {
-        let mut destinations: Bitboard = 0;
-        if diagonal {
-            destinations |= crate::magic::magic_bishop(square(piece), friends | others) & !friends;
-        }
-        if straight {
-            destinations |= crate::magic::magic_rook(square(piece), friends | others) & !friends;
-        }
+        let destinations = generate_sliding_destinations(friends, others, piece, straight, diagonal);
         let dest_iter = BitboardIterator(destinations & mask);
         for dest in dest_iter {
             let smove = SimpleMove{ from: square(piece), to: square(dest) };
@@ -791,22 +791,16 @@ pub fn generate_sliding_destinations(friends: Bitboard, others: Bitboard,
                                diagonal: bool) -> Bitboard {
     let occupied = friends | others;
 
-    let straights = [north_one, south_one, east_one, west_one];
-    let diagonals = [northeast_one, southeast_one, northwest_one, southwest_one];
 
-    let mut result: Bitboard = 0;
-    if straight {
-        for direction in straights.iter() {
-            result |= generate_direction(piece, *direction, occupied);
-        }
-    }
+    let mut destinations: Bitboard = 0;
     if diagonal {
-        for direction in diagonals.iter() {
-            result |= generate_direction(piece, *direction, occupied);
-        }
+        destinations |= crate::magic::magic_bishop(square(piece), friends | others) & !friends;
     }
-
-    return result & !friends;
+    if straight {
+        destinations |= crate::magic::magic_rook(square(piece), friends | others) & !friends;
+    }
+    
+    return destinations & !friends;
 }
 
 fn generate_direction(piece: Bitboard, direction: fn(Bitboard) -> Bitboard, occupied: Bitboard) -> Bitboard {
@@ -841,15 +835,8 @@ fn generate_king_moves(game: &Game, side: Side, move_list: &mut Vec<Move>, captu
             !friends
         };
 
-    let area = (north_one(king)
-              | south_one(king)
-              | east_one(king)
-              | west_one(king)
-              | northeast_one(king)
-              | northwest_one(king)
-              | southwest_one(king)
-              | southeast_one(king))
-            & mask;
+    let file = north_one(king) | king | south_one(king);
+    let area = (west_one(file) | file | east_one(file)) & mask;
 
     let area_iter = BitboardIterator(area);
     for destination in area_iter {
@@ -876,11 +863,8 @@ fn generate_castling_moves(game: &Game, side: Side, move_list: &mut Vec<Move>) {
         //info!("possible castling? {} {} {}", game.get_piece(KING, side), game.get_piece(ROOK, side), ((game.get_piece(KING, side) >> 3) & game.get_piece(ROOK, side)));
         if ((game.get_piece(KING, side) >> 3) & game.get_piece(ROOK, side)) != 0 &&
             ((game.get_piece(KING, side) >> 1) | (game.get_piece(KING, side) >> 2)) & all_pieces == 0 {
-                let mut tg1 = game.clone();
-
-                *tg1.get_piece_mut(KING, side) = game.get_piece(KING, side) >> 1;
-
-                if !is_check(game, side) && !is_check(&tg1, side) {
+                let king = game.get_piece(KING, side);
+                if !is_check(game, side) && !is_attacked(game, square(king) - 1, !side) {
                     move_list.push(c1);
                 }
         }
@@ -890,14 +874,8 @@ fn generate_castling_moves(game: &Game, side: Side, move_list: &mut Vec<Move>) {
         //info!("possible castling? {} {} {}", game.get_piece(KING, side), game.get_piece(ROOK, side), ((game.get_piece(KING, side) >> 3) & game.get_piece(ROOK, side)));
         if ((game.get_piece(KING, side) << 4) & game.get_piece(ROOK, side)) != 0 &&
             ((game.get_piece(KING, side) << 1) | (game.get_piece(KING, side) << 2) | (game.get_piece(KING, side) << 3)) & all_pieces == 0 {
-
-                let mut tg1 = game.clone();
-                let mut tg2 = game.clone();
-
-                *tg1.get_piece_mut(KING, side) = game.get_piece(KING, side) << 1;
-                *tg2.get_piece_mut(KING, side) = game.get_piece(KING, side) << 2;
-
-                if !is_check(game, side) && !is_check(&tg1, side) && !is_check(&tg2, side) {
+                let king = game.get_piece(KING, side);
+                if !is_check(game, side) && !is_attacked(game, square(king) + 1, !side) && !is_attacked(game, square(king) + 2, !side)  {
                     move_list.push(c2);
                 }
         }
@@ -1013,7 +991,7 @@ pub fn is_attacked(game: &Game, sq: Square, side: Side) -> bool {
 mod tests {
     use movegen::*;
 
-    use crate::{search::{perft, SearchControl}, hash::RepetitionTable};
+    use crate::{search::{perft, SearchControl}, ttable::RepetitionTable};
 
     ///
     /// tests the move generation from some positions to depth 4 and checks wether the correct

+ 7 - 9
src/search.rs

@@ -1,9 +1,11 @@
+use std::clone;
+
 use movegen::*;
 use game::Game;
 use evaluate::*;
 use log::info;
 use rand::prelude::*;
-use hash::*;
+use ttable::*;
 
 
 
@@ -144,15 +146,11 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
             //println!("TABLE HIT!");
             match e.entry_type {
                 EntryType::Value => { return e.value; },
-                //EntryType::Value => { if e.value >= alpha { return (e.value, false); } else { return (alpha, false); } },
-                //EntryType::LowerBound => { if e.value > alpha { return (e.value, false) } },
                 EntryType::LowerBound => {
-                    if e.value < alpha { return alpha; }
-                    //if e.value >= beta { return (beta, false); }
+                    if e.value >= beta { return beta; }
                 },
-                //EntryType::UpperBound => { if e.value >= beta { return (beta, false); } },
                 EntryType::UpperBound => {
-                    if e.value >= beta { return beta; }
+                    if e.value < alpha { return alpha; }
                 },
             }
         }
@@ -239,7 +237,7 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
         }
 
         if val >= beta {
-            hash.cache(game, CacheEntry::new_upper(depth, mov, val));
+            hash.cache(game, CacheEntry::new_lower(depth, mov, val));
             if !mov.is_capture() {
                 sc.insert_killer(ply_depth, mov);
             }
@@ -259,7 +257,7 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
         sc.pv[cur_depth] = best_move;
     }
     else {
-        hash.cache(game, CacheEntry::new_lower(depth, Move::Nullmove, alpha));
+        hash.cache(game, CacheEntry::new_upper(depth, Move::Nullmove, alpha));
     }
     //info!("best alpha {}", alpha);
     return alpha;

+ 67 - 29
src/hash.rs → src/ttable.rs

@@ -4,9 +4,9 @@ use std::collections::{HashMap};
 use log::info;
 use zobrist;
 
-use crate::{movegen::Move};
+use crate::{movegen::Move, zobrist::Hash};
 
-#[derive(Clone)]
+#[derive(Clone, PartialEq)]
 pub enum EntryType {
     Value,
     LowerBound,
@@ -22,6 +22,10 @@ pub struct CacheEntry {
 }
 
 impl CacheEntry {
+    pub fn null() -> Self {
+        CacheEntry { entry_type: EntryType::Value, mov: Move::Nullmove, depth: 0, value: 0 }
+    }
+
     pub fn new_value(depth: i32, mov: Move, value: PosValue) -> Self {
         CacheEntry {
             entry_type: EntryType::Value,
@@ -31,28 +35,28 @@ impl CacheEntry {
         }
     }
 
-    pub fn new_upper(depth: i32, mov: Move, beta: PosValue) -> Self {
+    pub fn new_upper(depth: i32, mov: Move, value: PosValue) -> Self {
         CacheEntry {
             entry_type: EntryType::UpperBound,
             mov,
             depth,
-            value: beta,
+            value,
         }
     }
 
-    pub fn new_lower(depth: i32, mov: Move, alpha: PosValue) -> Self {
+    pub fn new_lower(depth: i32, mov: Move, value: PosValue) -> Self {
         CacheEntry {
             entry_type: EntryType::LowerBound,
             mov,
             depth,
-            value: alpha,
+            value,
         }
     }
 }
 
+type TableEntry = (zobrist::Hash, CacheEntry);
 pub struct Cache {
-    hashmap: HashMap<zobrist::Hash, CacheEntry>,
-    max_capacity: usize
+    table: Vec<TableEntry>,
 }
 
 #[derive(Clone)]
@@ -62,42 +66,76 @@ pub struct RepetitionTable {
 
 impl Cache {
     pub fn new() -> Self {
-        Cache {
-            hashmap: HashMap::new(),
-            max_capacity: 40000000
-        }
+        let mut c = Cache {
+            table: Vec::new(),
+        };
+        c.table.resize(4096 * 4096, (0, CacheEntry::null()));
+        c
+    }
+
+    fn get_index(&self, hash: zobrist::Hash) -> usize {
+        (hash % (self.table.len() as u64)) as usize
     }
 
-    pub fn lookup(&self, game_pos: &Game) -> Option<CacheEntry> {
+    pub fn lookup<'a>(&'a self, game_pos: &Game) -> Option<CacheEntry> {
         if game_pos.zobrist.is_none() {
             info!("invalid zobrist");
         }
-        self.hashmap.get(&game_pos.zobrist.as_ref().unwrap().1).map(|x| x.clone())
+        let hash = game_pos.zobrist.as_ref().unwrap().1;
+        let index = self.get_index(hash);
+        let table_entry = &self.table[index];
+
+        if table_entry.0 == hash {
+            Some(table_entry.1.clone())
+        }
+        else {
+            None
+        }
+    }
+
+    fn should_replace(old: &TableEntry, new_hash: Hash, new: &CacheEntry) -> bool {
+        let (old_hash, old_ce) = old;
+
+        if *old_hash == 0 {
+            return true;
+        }
+
+        // different positions
+        if *old_hash != new_hash {
+            if old_ce.entry_type == EntryType::Value {
+                return false;
+            }
+        }
+
+        if new.depth >= old_ce.depth {
+            return true;
+        }
+
+        return false;
     }
     
     pub fn cache(&mut self, game_pos: &Game, ce: CacheEntry) {
         if game_pos.zobrist.is_none() {
             info!("invalid zobrist");
         }
-        if let Some(c) = self.lookup(game_pos) {
-            if c.depth > ce.depth {
-                return;
-            }
-        }
-        if self.hashmap.len() > self.max_capacity {
-            //let first_key = self.hashmap.keys().next().unwrap().clone();
-            //self.hashmap.remove(&first_key);
-            self.hashmap.clear();
-        }
-        let key = game_pos.zobrist.as_ref().unwrap().1;
-        self.hashmap.insert(key, ce);
-        if self.hashmap.len() % (1024 * 32) == 0 {
-            info!("hash contains {} items", self.hashmap.len());
+
+        let hash = game_pos.zobrist.as_ref().unwrap().1;
+        let index = self.get_index(hash);
+        let existing_entry = &self.table[index];
+
+        if Self::should_replace(existing_entry, hash, &ce) {
+            self.table[index] = (hash, ce);
         }
     }
 
     pub fn fullness_permill(&self) -> usize{
-        return (1000 * self.hashmap.len()) / self.max_capacity;
+        let mut permill = 0;
+        for i in 0..100 {
+            if self.table[i].0 != 0 {
+                permill += 10;
+            }
+        }
+        permill
     }
 }