ソースを参照

reworking transposition table

Nicolas Winkler 2 年 前
コミット
b2fb74199e
9 ファイル変更420 行追加351 行削除
  1. 44 51
      Cargo.lock
  2. 1 4
      Cargo.toml
  3. 19 11
      src/engine.rs
  4. 5 2
      src/evaluate.rs
  5. 195 190
      src/game.rs
  6. 8 2
      src/main.rs
  7. 61 25
      src/movegen.rs
  8. 17 23
      src/search.rs
  9. 70 43
      src/ttable.rs

+ 44 - 51
Cargo.lock

@@ -1,10 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-[[package]]
-name = "autocfg"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+version = 3
 
 [[package]]
 name = "bishop"
@@ -13,6 +9,7 @@ dependencies = [
  "log",
  "rand",
  "simplelog",
+ "static_assertions",
 ]
 
 [[package]]
@@ -22,23 +19,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
-name = "chrono"
-version = "0.4.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
-dependencies = [
- "libc",
- "num-integer",
- "num-traits",
- "time",
- "winapi",
-]
-
-[[package]]
 name = "getrandom"
-version = "0.2.5"
+version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
+checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
 dependencies = [
  "cfg-if",
  "libc",
@@ -46,37 +30,33 @@ dependencies = [
 ]
 
 [[package]]
-name = "libc"
-version = "0.2.119"
+name = "itoa"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
+checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
 
 [[package]]
-name = "log"
-version = "0.4.14"
+name = "libc"
+version = "0.2.135"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
-dependencies = [
- "cfg-if",
-]
+checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
 
 [[package]]
-name = "num-integer"
-version = "0.1.44"
+name = "log"
+version = "0.4.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
 dependencies = [
- "autocfg",
- "num-traits",
+ "cfg-if",
 ]
 
 [[package]]
-name = "num-traits"
-version = "0.2.14"
+name = "num_threads"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
 dependencies = [
- "autocfg",
+ "libc",
 ]
 
 [[package]]
@@ -108,49 +88,62 @@ dependencies = [
 
 [[package]]
 name = "rand_core"
-version = "0.6.3"
+version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 dependencies = [
  "getrandom",
 ]
 
 [[package]]
 name = "simplelog"
-version = "0.11.2"
+version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1348164456f72ca0116e4538bdaabb0ddb622c7d9f16387c725af3e96d6001c"
+checksum = "48dfff04aade74dd495b007c831cd6f4e0cee19c344dd9dc0884c0289b70a786"
 dependencies = [
- "chrono",
  "log",
  "termcolor",
+ "time",
 ]
 
 [[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
 name = "termcolor"
-version = "1.1.2"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
 dependencies = [
  "winapi-util",
 ]
 
 [[package]]
 name = "time"
-version = "0.1.44"
+version = "0.3.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c"
 dependencies = [
+ "itoa",
  "libc",
- "wasi",
- "winapi",
+ "num_threads",
+ "time-macros",
 ]
 
 [[package]]
+name = "time-macros"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
+
+[[package]]
 name = "wasi"
-version = "0.10.0+wasi-snapshot-preview1"
+version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "winapi"

+ 1 - 4
Cargo.toml

@@ -8,17 +8,14 @@ authors = ["Nicolas Winkler <nicolas.winkler@gmx.ch>"]
 log = "*"
 simplelog = "*"
 rand = "*"
+static_assertions = "1.1.0"
 
 [build-dependencies]
 rand = "*"
 
-[build]
-rustflags = ["-C","target-cpu=native"]
-
 [profile.release]
 opt-level = 3
 lto = true
-strip = "debuginfo"
 
 # Optimize the build script;
 # As we generate magic tables in it, it needs to be optimized

+ 19 - 11
src/engine.rs

@@ -88,7 +88,7 @@ impl Engine {
             move_history: RepetitionTable::new(),
             messages: VecDeque::new(),
             r, s,
-            hash: Cache::new(),
+            hash: Cache::megabytes(4),
             zobrist_table: Arc::new(ZobristTable::new())
         };
         eng.game.zobrist = Some((eng.zobrist_table.clone(), eng.game.calculate_zobrist(&eng.zobrist_table)));
@@ -173,11 +173,18 @@ impl Engine {
         for _ in 0..100 {
             let mce = hash.lookup(&g);
             if let Some(ce) = mce {
-                if ce.mov == Move::Nullmove {
+                if ce.mov == (SimpleMove{ from: 0, to: 0 }) {
+                    break;
+                }
+                let movs = generate_moves(game, game.turn);
+                let bm = movs.iter().find(|m| m.to_simple() == ce.mov);
+                if let Some(found) = bm {
+                    g.apply(*found);
+                    pv.push(*found);
+                }
+                else {
                     break;
                 }
-                pv.push(ce.mov);
-                g.apply(ce.mov);
             }
             else {
                 break;
@@ -239,7 +246,7 @@ impl Engine {
             };
 
         let mut sc = SearchControl::new(&mut check_fn, &mut self.move_history, 0);
-        let mut best_move = Move::default();
+        let mut best_move: Option<Move> = None;
         let mut best_val: PosValue;
 
         sc.halfmove_age = (self.game.turn_number * 2 + if self.game.turn == BLACK { 1 } else { 0 }) as _;
@@ -296,7 +303,7 @@ impl Engine {
                     beta = bv + window_size;
                 }
 
-                best_move = bm;
+                best_move = Some(bm);
                 best_val = bv;
 
                 let elapsed = before.elapsed();
@@ -325,8 +332,8 @@ impl Engine {
                 depth += 1;
             }
             else if let SearchResult::Cancelled(Some((bm, _bv))) = search_result {
-                if best_move == Move::Nullmove {
-                    best_move = bm;
+                if best_move.is_none() {
+                    best_move = Some(bm);
                     //best_val = bv;
                 }
                 break;
@@ -335,7 +342,6 @@ impl Engine {
                 break;
             }
 
-
             if let Some(d) = si.depth {
                 if depth > d {
                     break;
@@ -343,8 +349,10 @@ impl Engine {
             }
         }
 
-        info!("bestmove {}", best_move.to_string());
-        println!("bestmove {}", best_move.to_string());
+        if let Some(best) = best_move {
+            info!("bestmove {}", best.to_string());
+            println!("bestmove {}", best.to_string());
+        }
     }
 }
 

+ 5 - 2
src/evaluate.rs

@@ -7,13 +7,16 @@ use std::i32;
 ///
 /// type that is returned by the evaluate funcion
 /// 
+/// the score value must not exceed 2^23-1 (only 3 bytes), as it
+/// will be compressed to 3 bytes in the transposition table
+/// 
 pub type PosValue = i32;
 
 pub const MIN_VALUE: PosValue = i32::MIN + 1;
 pub const MAX_VALUE: PosValue = i32::MAX;
 
-const MATE_VALUE: PosValue = 1i32 << 26;
-const MATE_CUTOFF: PosValue = 1i32 << 24;
+const MATE_VALUE: PosValue = 1i32 << 22;
+const MATE_CUTOFF: PosValue = 1i32 << 20;
 
 pub fn mate() -> PosValue {
     mate_in_p1(1)

+ 195 - 190
src/game.rs

@@ -223,7 +223,7 @@ impl Game {
                     if piece_type == KING && (from as i32 - to as i32).abs() == 2 {
                         let left = from < to;
                         //println!("OMG castling!");
-                        return Ok(Move::Castling{ side, left });
+                        return Ok(Move::Castling{ mov: SimpleMove { from, to }, side, left });
                     }
 
                     // pawn capture
@@ -613,147 +613,149 @@ impl Game {
         let side = self.turn;
         let others = self.get_all_side(!side);
 
-        match mov {
-            Move::Default{ mov, pc } => {
-                let pt = pc.piece_type();
+        if !mov.is_null() {
+            match mov {
+                Move::Default{ mov, pc } => {
+                    let pt = pc.piece_type();
 
-                if pt == KING {
-                    // invalidate castling rights
-                    self.castling_rights[if side == BLACK { 2 } else { 0 }] = false;
-                    self.castling_rights[if side == BLACK { 3 } else { 1 }] = false;
-                }
+                    if pt == KING {
+                        // invalidate castling rights
+                        self.castling_rights[if side == BLACK { 2 } else { 0 }] = false;
+                        self.castling_rights[if side == BLACK { 3 } else { 1 }] = false;
+                    }
 
-                // invalidate castling rights
-                if mov.from == square_from_indices(7, 0) || mov.to == square_from_indices(7, 0) {
-                    self.castling_rights[0] = false;
-                }
-                if mov.from == square_from_indices(0, 0) || mov.to == square_from_indices(0, 0) {
-                    self.castling_rights[1] = false;
-                }
-                if mov.from == square_from_indices(7, 7) || mov.to == square_from_indices(7, 7) {
-                    self.castling_rights[2] = false;
-                }
-                if mov.from == square_from_indices(0, 7) || mov.to == square_from_indices(0, 7) {
-                    self.castling_rights[3] = false;
-                }
+                    // invalidate castling rights
+                    if mov.from == square_from_indices(7, 0) || mov.to == square_from_indices(7, 0) {
+                        self.castling_rights[0] = false;
+                    }
+                    if mov.from == square_from_indices(0, 0) || mov.to == square_from_indices(0, 0) {
+                        self.castling_rights[1] = false;
+                    }
+                    if mov.from == square_from_indices(7, 7) || mov.to == square_from_indices(7, 7) {
+                        self.castling_rights[2] = false;
+                    }
+                    if mov.from == square_from_indices(0, 7) || mov.to == square_from_indices(0, 7) {
+                        self.castling_rights[3] = false;
+                    }
 
-                // if it is a capture
-                if from_square(mov.to) & others != 0 {
-                    let (other_piece, other_side) = self.get_square(mov.to);
+                    // if it is a capture
+                    if from_square(mov.to) & others != 0 {
+                        let (other_piece, other_side) = self.get_square(mov.to);
 
-                    // update zobrist
-                    if let Some((ref zt, ref mut zv)) = self.zobrist {
-                        let hup = zt.piece_hash(other_piece, other_side, mov.to);
-                        *zv ^= hup;
+                        // update zobrist
+                        if let Some((ref zt, ref mut zv)) = self.zobrist {
+                            let hup = zt.piece_hash(other_piece, other_side, mov.to);
+                            *zv ^= hup;
+                        }
+                        *self.get_piece_mut(other_piece, other_side) &= !from_square(mov.to);
                     }
-                    *self.get_piece_mut(other_piece, other_side) &= !from_square(mov.to);
-                }
 
 
-                // reset en passant and then check if possible
-                self.en_passant = None;
-                if pt == PAWN {
-                    let row1 = indices_from_square(mov.from).1;
-                    let row2 = indices_from_square(mov.to).1;
-                    // check for double push
-                    if i32::abs(row1 as i32 - row2 as i32) >= 2 {
-                        let opponent_pawns = self.get_piece(PAWN, !side);
-                        let target_bb = from_square(mov.to);
-                        if (west_one(target_bb) | east_one(target_bb)) & opponent_pawns != 0 {
-                            self.en_passant = Some(indices_from_square(mov.to).0);
+                    // reset en passant and then check if possible
+                    self.en_passant = None;
+                    if pt == PAWN {
+                        let row1 = indices_from_square(mov.from).1;
+                        let row2 = indices_from_square(mov.to).1;
+                        // check for double push
+                        if i32::abs(row1 as i32 - row2 as i32) >= 2 {
+                            let opponent_pawns = self.get_piece(PAWN, !side);
+                            let target_bb = from_square(mov.to);
+                            if (west_one(target_bb) | east_one(target_bb)) & opponent_pawns != 0 {
+                                self.en_passant = Some(indices_from_square(mov.to).0);
+                            }
                         }
                     }
-                }
 
-                let moved_piece = mov.apply_to(self.get_piece(pt, side));
-                self.set_piece(pt, side, moved_piece);
-                if let Some((ref zt, ref mut zv)) = self.zobrist {
-                    let hup = zt.piece_hash(pt, side, mov.from) ^ zt.piece_hash(pt, side, mov.to);
-                    *zv ^= hup;
-                }
-            },
-            Move::Castling { side, left } => {
-                // invalidate future castling rights
-                self.castling_rights[if side == BLACK { 2 } else { 0 }] = false;
-                self.castling_rights[if side == BLACK { 3 } else { 1 }] = false;
-
-                let king = self.get_piece(KING, side);
-                let rook = if left {
-                    self.get_piece(ROOK, side) & (king << 4)
-                }
-                else {
-                    self.get_piece(ROOK, side) & (king >> 3)
-                };
-
-                let new_king = if left { king << 2 } else { king >> 2 };
-                let new_rook = if left { rook >> 3 } else { rook << 2 };
-
-                self.set_piece(KING, side, new_king);
-                *self.get_piece_mut(ROOK, side) &= !rook;
-                *self.get_piece_mut(ROOK, side) |= new_rook;
-                if let Some((ref zt, ref mut zv)) = self.zobrist {
-                    let hupk = zt.piece_hash(KING, side, square(king)) ^ zt.piece_hash(KING, side, square(new_king));
-                    let hup = zt.piece_hash(ROOK, side, square(rook)) ^ zt.piece_hash(ROOK, side, square(new_rook));
-                    *zv ^= hup ^ hupk;
-                }
+                    let moved_piece = mov.apply_to(self.get_piece(pt, side));
+                    self.set_piece(pt, side, moved_piece);
+                    if let Some((ref zt, ref mut zv)) = self.zobrist {
+                        let hup = zt.piece_hash(pt, side, mov.from) ^ zt.piece_hash(pt, side, mov.to);
+                        *zv ^= hup;
+                    }
+                },
+                Move::Castling { mov:_, side, left } => {
+                    // invalidate future castling rights
+                    self.castling_rights[if side == BLACK { 2 } else { 0 }] = false;
+                    self.castling_rights[if side == BLACK { 3 } else { 1 }] = false;
 
-                self.en_passant = None;
-            },
-            Move::EnPassant { mov, beaten } => {
-                if let Some(_ep) = self.en_passant {
-                    *self.get_piece_mut(PAWN, side) &= !from_square(mov.from);
-                    *self.get_piece_mut(PAWN, side) |= from_square(mov.to);
-                    *self.get_piece_mut(PAWN, !side) &= !from_square(beaten);
+                    let king = self.get_piece(KING, side);
+                    let rook = if left {
+                        self.get_piece(ROOK, side) & (king << 4)
+                    }
+                    else {
+                        self.get_piece(ROOK, side) & (king >> 3)
+                    };
+
+                    let new_king = if left { king << 2 } else { king >> 2 };
+                    let new_rook = if left { rook >> 3 } else { rook << 2 };
 
+                    self.set_piece(KING, side, new_king);
+                    *self.get_piece_mut(ROOK, side) &= !rook;
+                    *self.get_piece_mut(ROOK, side) |= new_rook;
                     if let Some((ref zt, ref mut zv)) = self.zobrist {
-                        let hup = zt.piece_hash(PAWN, side, mov.from) ^ zt.piece_hash(PAWN, side, mov.to);
-                        let hupo = zt.piece_hash(PAWN, !side, beaten);
-                        *zv ^= hup ^ hupo;
+                        let hupk = zt.piece_hash(KING, side, square(king)) ^ zt.piece_hash(KING, side, square(new_king));
+                        let hup = zt.piece_hash(ROOK, side, square(rook)) ^ zt.piece_hash(ROOK, side, square(new_rook));
+                        *zv ^= hup ^ hupk;
                     }
 
                     self.en_passant = None;
-                }
-                else {
-                    info!("internal en passant error");
-                    panic!("internal en passant error");
-                }
-            },
-            Move::Promotion { mov, pc } => {
-                let promote_to = pc.piece_type();
-                let captured = pc.capture_type();
-                //if from_square(mov.to) & others != 0 {
-                if let Some(pt) = captured {
-                    *self.get_piece_mut(pt, !side) &= !from_square(mov.to);
+                },
+                Move::EnPassant { mov, beaten } => {
+                    if let Some(_ep) = self.en_passant {
+                        *self.get_piece_mut(PAWN, side) &= !from_square(mov.from);
+                        *self.get_piece_mut(PAWN, side) |= from_square(mov.to);
+                        *self.get_piece_mut(PAWN, !side) &= !from_square(beaten);
+
+                        if let Some((ref zt, ref mut zv)) = self.zobrist {
+                            let hup = zt.piece_hash(PAWN, side, mov.from) ^ zt.piece_hash(PAWN, side, mov.to);
+                            let hupo = zt.piece_hash(PAWN, !side, beaten);
+                            *zv ^= hup ^ hupo;
+                        }
 
+                        self.en_passant = None;
+                    }
+                    else {
+                        info!("internal en passant error");
+                        panic!("internal en passant error");
+                    }
+                },
+                Move::Promotion { mov, pc } => {
+                    let promote_to = pc.piece_type();
+                    let captured = pc.capture_type();
+                    //if from_square(mov.to) & others != 0 {
+                    if let Some(pt) = captured {
+                        *self.get_piece_mut(pt, !side) &= !from_square(mov.to);
+
+                        if let Some((ref zt, ref mut zv)) = self.zobrist {
+                            let hup = zt.piece_hash(pt, !side, mov.to);
+                            *zv ^= hup;
+                        }
+                    }
+                    *self.get_piece_mut(PAWN, side) &= !from_square(mov.from);
                     if let Some((ref zt, ref mut zv)) = self.zobrist {
-                        let hup = zt.piece_hash(pt, !side, mov.to);
+                        let hup = zt.piece_hash(PAWN, side, mov.from);
                         *zv ^= hup;
                     }
-                }
-                *self.get_piece_mut(PAWN, side) &= !from_square(mov.from);
-                if let Some((ref zt, ref mut zv)) = self.zobrist {
-                    let hup = zt.piece_hash(PAWN, side, mov.from);
-                    *zv ^= hup;
-                }
 
-                match promote_to {
-                    QUEEN => { *self.queens_mut(side) |= from_square(mov.to); }
-                    ROOK => { *self.rooks_mut(side) |= from_square(mov.to); }
-                    BISHOP => { *self.bishops_mut(side) |= from_square(mov.to); }
-                    KNIGHT => { *self.knights_mut(side) |= from_square(mov.to); }
-                    _ => {
-                        info!("internal error");
+                    match promote_to {
+                        QUEEN => { *self.queens_mut(side) |= from_square(mov.to); }
+                        ROOK => { *self.rooks_mut(side) |= from_square(mov.to); }
+                        BISHOP => { *self.bishops_mut(side) |= from_square(mov.to); }
+                        KNIGHT => { *self.knights_mut(side) |= from_square(mov.to); }
+                        _ => {
+                            info!("internal error");
+                        }
                     }
-                }
-                if let Some((ref zt, ref mut zv)) = self.zobrist {
-                    let hup = zt.piece_hash(promote_to, side, mov.to);
-                    *zv ^= hup;
-                }
-                self.en_passant = None;
-            },
-            Move::Nullmove => {}
+                    if let Some((ref zt, ref mut zv)) = self.zobrist {
+                        let hup = zt.piece_hash(promote_to, side, mov.to);
+                        *zv ^= hup;
+                    }
+                    self.en_passant = None;
+                },
+            }
         }
 
+
         if self.turn == BLACK {
             self.turn_number += 1;
         }
@@ -787,6 +789,7 @@ impl Game {
         }
     }
 
+
     pub fn undo_move(&mut self, umov: MoveUndo) {
 
         /*if !self.is_zobrist_correct() {
@@ -810,96 +813,98 @@ impl Game {
 
         // the side that played the turn that is to be reverted
         let side = !self.turn;
-        match mov {
-            Move::Default{ mov, pc } => {
-                let piece_type = pc.piece_type();
-                let captured = pc.capture_type();
 
-                let moved_piece_bb = self.get_piece_mut(piece_type, side);
-                *moved_piece_bb &= !from_square(mov.to);
-                *moved_piece_bb |= from_square(mov.from);
+        if !mov.is_null() {
+            match mov {
+                Move::Default{ mov, pc } => {
+                    let piece_type = pc.piece_type();
+                    let captured = pc.capture_type();
 
-                if let Some((ref zt, ref mut zv)) = self.zobrist {
-                    let hup = zt.piece_hash(piece_type, side, mov.from) ^ zt.piece_hash(piece_type, side, mov.to);
-                    *zv ^= hup;
-                }
-
-                if let Some(pt) = captured {
-                    *self.get_piece_mut(pt, !side) |= from_square(mov.to);
+                    let moved_piece_bb = self.get_piece_mut(piece_type, side);
+                    *moved_piece_bb &= !from_square(mov.to);
+                    *moved_piece_bb |= from_square(mov.from);
 
                     if let Some((ref zt, ref mut zv)) = self.zobrist {
-                        let hup = zt.piece_hash(pt, !side, mov.to);
+                        let hup = zt.piece_hash(piece_type, side, mov.from) ^ zt.piece_hash(piece_type, side, mov.to);
                         *zv ^= hup;
                     }
-                }
-            },
-            Move::Castling { side, left } => {
-                let king = self.get_piece(KING, side);
 
-                let rook = if left {
-                    self.get_piece(ROOK, side) & (king >> 1)
-                }
-                else {
-                    self.get_piece(ROOK, side) & (king << 1)
-                };
+                    if let Some(pt) = captured {
+                        *self.get_piece_mut(pt, !side) |= from_square(mov.to);
 
-                let old_king = if left { king >> 2 } else { king << 2 };
-                let old_rook = if left { rook << 3 } else { rook >> 2 };
+                        if let Some((ref zt, ref mut zv)) = self.zobrist {
+                            let hup = zt.piece_hash(pt, !side, mov.to);
+                            *zv ^= hup;
+                        }
+                    }
+                },
+                Move::Castling { mov: _, side, left } => {
+                    let king = self.get_piece(KING, side);
 
-                self.set_piece(KING, side, old_king);
-                *self.get_piece_mut(ROOK, side) &= !rook;
-                *self.get_piece_mut(ROOK, side) |= old_rook;
+                    let rook = if left {
+                        self.get_piece(ROOK, side) & (king >> 1)
+                    }
+                    else {
+                        self.get_piece(ROOK, side) & (king << 1)
+                    };
 
-                if let Some((ref zt, ref mut zv)) = self.zobrist {
-                    let khup = zt.piece_hash(KING, side, square(old_king)) ^ zt.piece_hash(KING, side, square(king));
-                    let rhup = zt.piece_hash(ROOK, side, square(rook)) ^ zt.piece_hash(ROOK, side, square(old_rook));
-                    *zv ^= khup ^ rhup;
-                }
-            },
-            Move::EnPassant { mov, beaten } => {
-                *self.get_piece_mut(PAWN, side) |= from_square(mov.from);
-                *self.get_piece_mut(PAWN, side) &= !from_square(mov.to);
-                *self.get_piece_mut(PAWN, !side) |= from_square(beaten);
-
-                if let Some((ref zt, ref mut zv)) = self.zobrist {
-                    let phup = zt.piece_hash(PAWN, side, mov.from) ^ zt.piece_hash(PAWN, side, mov.to);
-                    let chup = zt.piece_hash(PAWN, !side, beaten);
-                    *zv ^= phup ^ chup;
-                }
+                    let old_king = if left { king >> 2 } else { king << 2 };
+                    let old_rook = if left { rook << 3 } else { rook >> 2 };
+
+                    self.set_piece(KING, side, old_king);
+                    *self.get_piece_mut(ROOK, side) &= !rook;
+                    *self.get_piece_mut(ROOK, side) |= old_rook;
 
-                // should already be reverted
-                //self.en_passant = Some(indices_from_square(beaten).0);
-            },
-            Move::Promotion { mov, pc } => {
-                let promote_to = pc.piece_type();
-                let captured = pc.capture_type();
-
-                match promote_to {
-                    QUEEN => { *self.queens_mut(side) &= !from_square(mov.to); }
-                    ROOK => { *self.rooks_mut(side) &= !from_square(mov.to); }
-                    BISHOP => { *self.bishops_mut(side) &= !from_square(mov.to); }
-                    KNIGHT => { *self.knights_mut(side) &= !from_square(mov.to); }
-                    _ => {
-                        info!("internal error");
+                    if let Some((ref zt, ref mut zv)) = self.zobrist {
+                        let khup = zt.piece_hash(KING, side, square(old_king)) ^ zt.piece_hash(KING, side, square(king));
+                        let rhup = zt.piece_hash(ROOK, side, square(rook)) ^ zt.piece_hash(ROOK, side, square(old_rook));
+                        *zv ^= khup ^ rhup;
                     }
-                }
-                *self.pawns_mut(side) |= from_square(mov.from);
+                },
+                Move::EnPassant { mov, beaten } => {
+                    *self.get_piece_mut(PAWN, side) |= from_square(mov.from);
+                    *self.get_piece_mut(PAWN, side) &= !from_square(mov.to);
+                    *self.get_piece_mut(PAWN, !side) |= from_square(beaten);
 
-                if let Some((ref zt, ref mut zv)) = self.zobrist {
-                    let hup = zt.piece_hash(PAWN, side, mov.from) ^ zt.piece_hash(promote_to, side, mov.to);
-                    *zv ^= hup;
-                }
+                    if let Some((ref zt, ref mut zv)) = self.zobrist {
+                        let phup = zt.piece_hash(PAWN, side, mov.from) ^ zt.piece_hash(PAWN, side, mov.to);
+                        let chup = zt.piece_hash(PAWN, !side, beaten);
+                        *zv ^= phup ^ chup;
+                    }
 
-                if let Some(pt) = captured {
-                    *self.get_piece_mut(pt, !side) |= from_square(mov.to);
+                    // should already be reverted
+                    //self.en_passant = Some(indices_from_square(beaten).0);
+                },
+                Move::Promotion { mov, pc } => {
+                    let promote_to = pc.piece_type();
+                    let captured = pc.capture_type();
+
+                    match promote_to {
+                        QUEEN => { *self.queens_mut(side) &= !from_square(mov.to); }
+                        ROOK => { *self.rooks_mut(side) &= !from_square(mov.to); }
+                        BISHOP => { *self.bishops_mut(side) &= !from_square(mov.to); }
+                        KNIGHT => { *self.knights_mut(side) &= !from_square(mov.to); }
+                        _ => {
+                            info!("internal error");
+                        }
+                    }
+                    *self.pawns_mut(side) |= from_square(mov.from);
 
                     if let Some((ref zt, ref mut zv)) = self.zobrist {
-                        let hup = zt.piece_hash(pt, !side, mov.to);
+                        let hup = zt.piece_hash(PAWN, side, mov.from) ^ zt.piece_hash(promote_to, side, mov.to);
                         *zv ^= hup;
                     }
-                }
-            },
-            Move::Nullmove => {}
+
+                    if let Some(pt) = captured {
+                        *self.get_piece_mut(pt, !side) |= from_square(mov.to);
+
+                        if let Some((ref zt, ref mut zv)) = self.zobrist {
+                            let hup = zt.piece_hash(pt, !side, mov.to);
+                            *zv ^= hup;
+                        }
+                    }
+                },
+            }
         }
 
         self.turn = side;

+ 8 - 2
src/main.rs

@@ -1,3 +1,4 @@
+#![feature(pointer_is_aligned)]
 pub mod interface;
 pub mod bitboard;
 pub mod movegen;
@@ -13,12 +14,16 @@ pub mod magic;
 extern crate log;
 extern crate simplelog;
 extern crate rand;
+extern crate static_assertions;
 
 use std::sync::{mpsc};
 use std::{thread, fs::File};
 use engine::Engine;
 use log::*;
 use simplelog::*;
+use static_assertions::const_assert_eq;
+
+use crate::movegen::Move;
 
 
 fn main() {
@@ -30,8 +35,9 @@ fn main() {
         .init();
         */
 
-    let logfile = File::create("C:\\Users\\Nicolas\\debug.log").unwrap();
-    simplelog::WriteLogger::init(LevelFilter::Info, Config::default(), logfile).unwrap();
+    //let logfile = File::create("C:\\Users\\Nicolas\\debug.log").unwrap();
+    //simplelog::WriteLogger::init(LevelFilter::Info, Config::default(), logfile).unwrap();
+    const_assert_eq!(std::mem::size_of::<Move>(), 4);
 
     let (esend, erecv) = mpsc::channel();
     let (isend, irecv) = mpsc::channel();

+ 61 - 25
src/movegen.rs

@@ -2,9 +2,11 @@ use bitboard::Bitboard;
 use bitboard::Square;
 use bitboard::*;
 use game::Game;
+use static_assertions::const_assert_eq;
 use ttable::{Cache, EntryType};
 use std::iter::Iterator;
 
+use crate::magic::magic_bishop;
 use crate::ttable::CacheEntry;
 
 pub type Side = bool;
@@ -39,12 +41,13 @@ pub struct PieceCaptureType(u8);
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub enum Move {
     Default { mov: SimpleMove, pc: PieceCaptureType },
-    Castling { side: Side, left: bool },
+    Castling { mov: SimpleMove, side: Side, left: bool },
     EnPassant { mov: SimpleMove, beaten: Square },
     Promotion { mov: SimpleMove, pc: PieceCaptureType },
-    Nullmove
 }
 
+const_assert_eq!(std::mem::size_of::<Move>(), 4);
+
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub struct MoveUndo {
@@ -86,14 +89,14 @@ impl PieceCaptureType {
     }
 }
 
-impl Default for Move {
-    fn default() -> Move {
-        Move::Nullmove
-    }
-}
 
 
 impl Move {
+
+    pub fn nullmove() -> Self {
+        Move::Default { mov: SimpleMove{ from: 0, to: 0 }, pc: PieceCaptureType(0) }
+    }
+
     pub fn parse_square(sq: &str) -> Option<Square> {
         let col = sq.chars().nth(0)?;
         let row = sq.chars().nth(1)?;
@@ -115,7 +118,7 @@ impl Move {
     }
 
     pub fn is_null(&self) -> bool {
-        *self == Move::Nullmove
+        self.to_simple() == SimpleMove{ from: 0, to: 0 }
     }
 
     pub fn to_string(&self) -> String {
@@ -123,7 +126,7 @@ impl Move {
             Move::Default{ mov, pc: _} => {
                 Self::square_to_string(mov.from) + &Self::square_to_string(mov.to)
             },
-            Move::Castling{ side, left } => {
+            Move::Castling{ mov: _mov, side, left } => {
                 match (side, left) {
                     (&WHITE, false) => {
                         Self::square_to_string(3) + &Self::square_to_string(1)
@@ -150,9 +153,6 @@ impl Move {
                 else if promote_to == KNIGHT { "n" }
                 else if promote_to == BISHOP { "b" }
                 else { "" }
-            },
-            Move::Nullmove => {
-                String::from("0000")
             }
         }
     }
@@ -162,8 +162,18 @@ impl Move {
             Move::Default{ mov: _, pc } => pc.capture_type().is_some(),
             Move::Promotion{ mov: _, pc } => pc.capture_type().is_some(),
             Move::EnPassant{ mov: _, beaten: _ } => true,
-            Move::Castling{ side: _, left: _ } => false,
-            Move::Nullmove => false
+            Move::Castling{ mov: _, side: _, left: _ } => false,
+        }
+    }
+
+    pub fn to_simple(&self) -> SimpleMove {
+        match self {
+            Move::Default{ mov, pc: _ } |
+            Move::Promotion{ mov, pc: _ } |
+            Move::Castling{ mov, side: _, left: _ } |
+            Move::EnPassant{ mov, beaten: _ } => {
+                *mov
+            },
         }
     }
 }
@@ -216,7 +226,7 @@ pub struct MoveGenerator {
 
     state: SortingState,
 
-    pv_move: Option<Move>,
+    pv_move: Option<SimpleMove>,
     killers: Vec<Move>
 }
 
@@ -246,9 +256,10 @@ impl Iterator for MoveGenerator {
                 SortingState::PvMove => {
                     self.state = SortingState::Captures;
                     if let Some(pv) = self.pv_move {
-                        if self.moves.contains(&pv) {
-                            self.moves.retain(|m| *m != pv);
-                            return Some(pv);
+                        let found = self.moves.iter().find(|m| m.to_simple() == pv).map(Move::clone);
+                        if let Some(mov) = found {
+                            self.moves.retain(|m| *m != mov);
+                            return Some(mov);
                         }
                     }
                 },
@@ -448,7 +459,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.to_simple() { 
                 return -2000;
             }
         };
@@ -483,13 +494,13 @@ pub fn sort_moves(game: &mut Game, hash: &mut Cache, killers: &[Move], move_list
         if let Some(e) = hash.lookup(game) {
             game.undo_move(undo);
             if let EntryType::Value = e.entry_type {
-                return e.value;
+                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 };
@@ -548,6 +559,29 @@ pub fn sort_moves_no_hash(game: &mut Game, move_list: &mut Vec<Move>) {
 }
 
 
+fn generate_legal_pawn_pushes<const SIDE: Side>(game: &Game, move_list: &mut Vec<Move>) {
+    let pawns = game.pawns(SIDE);
+    let adv_pieces= game.get_all_side(!SIDE);
+    let own_pieces = game.get_all_side(SIDE);
+
+    let moved_pawns =
+        match SIDE {
+            WHITE => north_one(pawns),
+            BLACK => south_one(pawns)
+        } & !(ROW_1 | ROW_8) & !(adv_pieces | own_pieces);
+    
+    let adv_bishops = game.get_piece(BISHOP, !SIDE);
+    let adv_queens = game.get_piece(QUEEN, !SIDE);
+    let adv_rooks= game.get_piece(ROOK, !SIDE);
+
+    let king = game.get_piece(KING, SIDE);
+
+    let diagonal_pinline = magic_bishop(square(king), adv_bishops | adv_queens);
+    
+    let iter = BitboardIterator(moved_pawns & !adv_pieces);
+}
+
+
 fn generate_pawn_pushes(game: &Game, side: Side, move_list: &mut Vec<Move>) {
     let pawns = game.pawns(side);
     let others = game.get_all_side(!side);
@@ -712,7 +746,7 @@ fn generate_knight_moves(game: &Game, side: Side, move_list: &mut Vec<Move>, cap
 }
 
 pub fn get_knight_targets(square: Square) -> Bitboard {
-    /// @brief array containing the possible targets for a knight on each square.
+    /// array containing the possible targets for a knight on each square.
     /// entry at index i is a bitboard with all bits set where a knight on square i can jump
     const TARGETS: [Bitboard; 64] = [132096, 329728, 659712, 1319424, 2638848, 5277696, 10489856,
     4202496, 33816580, 84410376, 168886289, 337772578, 675545156, 1351090312, 2685403152,
@@ -848,8 +882,10 @@ fn generate_king_moves(game: &Game, side: Side, move_list: &mut Vec<Move>, captu
 
 
 fn generate_castling_moves(game: &Game, side: Side, move_list: &mut Vec<Move>) {
-    let c1 = Move::Castling{ side: side, left: false };
-    let c2 = Move::Castling{ side: side, left: true };
+    let king = game.kings(side);
+    let king_sq = square(king);
+    let c1 = Move::Castling{ mov: SimpleMove { from: king_sq, to: king_sq - 2 }, side: side, left: false };
+    let c2 = Move::Castling{ mov: SimpleMove { from: king_sq, to: king_sq + 2 }, side: side, left: true };
 
     let legal_castlings: &[bool] = match side {
         WHITE => &game.castling_rights[0..2],

+ 17 - 23
src/search.rs

@@ -16,7 +16,6 @@ pub struct SearchControl<'a> {
     /// node counter
     pub nodes: usize,
 
-    pub pv: Vec<Move>,
     pub killer_moves: Vec<[Move; 2]>,
 
     /// current halfmove clock for discarding old hash entries
@@ -43,8 +42,7 @@ impl<'a> SearchControl<'a> {
     pub fn new(check: &'a mut dyn FnMut() -> bool, move_history: &'a mut RepetitionTable, depth: i32) -> Self {
         SearchControl {
             nodes: 0,
-            pv: Vec::with_capacity(depth as usize),
-            killer_moves: vec![[Move::Nullmove; 2]; depth as usize],
+            killer_moves: vec![[Move::nullmove(); 2]; depth as usize],
             halfmove_age: 0,
             check,
             stopping: false,
@@ -56,8 +54,7 @@ impl<'a> SearchControl<'a> {
 
     pub fn set_depth(&mut self, depth: i32) {
         self.initial_depth = depth;
-        self.killer_moves = vec![[Move::Nullmove; 2]; depth as usize];
-        self.pv = vec![Move::Nullmove; depth as _];
+        self.killer_moves = vec![[Move::nullmove(); 2]; depth as usize];
     }
 
     pub fn insert_killer(&mut self, ply_depth: usize, killer: Move) {
@@ -89,7 +86,7 @@ pub fn search(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alp
     let cache_entry = hash.lookup(game);
 
     let ply_depth = (sc.initial_depth - depth) as usize;
-    let moves = generate_legal_sorted_moves(game, hash, &sc.killer_moves[ply_depth], cache_entry, game.turn);
+    let moves = generate_legal_sorted_moves(game, hash, &sc.killer_moves[ply_depth], cache_entry.map(CacheEntry::clone), game.turn);
     //let mut moves = generate_legal_moves(game, game.turn);
     //sort_moves(game, hash, &sc.killer_moves[ply_depth], &mut moves);
     
@@ -134,8 +131,7 @@ pub fn search(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut alp
             return SearchResult::Cancelled(Some((chosen_mov.0, -chosen_mov.1)));
         }
         else {
-            hash.cache(game, CacheEntry::new_value(depth as _, sc.halfmove_age, chosen_mov.0, chosen_mov.1));
-            sc.pv[0] = chosen_mov.0;
+            hash.cache(game, 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);
         }
     }
@@ -149,15 +145,15 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
     let cache_entry = hash.lookup(game);
 
     if let Some(e) = &cache_entry {
-        if e.depth as i32 >= depth {
+        if e.depth() as i32 >= depth {
             //println!("TABLE HIT!");
             match e.entry_type {
-                EntryType::Value => { return e.value; },
+                EntryType::Value => { return e.value(); },
                 EntryType::LowerBound => {
-                    if e.value >= beta { return beta; }
+                    if e.value() >= beta { return beta; }
                 },
                 EntryType::UpperBound => {
-                    if e.value < alpha { return alpha; }
+                    if e.value() < alpha { return alpha; }
                 },
             }
         }
@@ -185,7 +181,7 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
 
     let moves = MoveGenerator::generate_legal_moves(
         game,
-        cache_entry.as_ref(),
+        cache_entry,
         &sc.killer_moves[ply_depth],
         game.turn
     );
@@ -209,7 +205,7 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
 
         let reduce = if depth > 5 { if depth > 7 { 5 } else { 4 }} else { 3 };
 
-        let nmov = Move::Nullmove;
+        let nmov = Move::nullmove();
         let undo = game.apply(nmov);
 
         sc.nullmoves_enabled = false;
@@ -229,8 +225,7 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
         }
     }
 
-    let mut alpha_is_exact = false;
-    let mut best_move = Move::Nullmove;
+    let mut best_move: Option<Move> = None;
 
     // we can't beat an alpha this good
     if alpha >= mate() {
@@ -250,7 +245,7 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
         }
 
         if val >= beta {
-            hash.cache(game, CacheEntry::new_lower(depth as _, sc.halfmove_age, mov, increase_mate_in(val)));
+            hash.cache(game, CacheEntry::new_lower(depth as _, sc.halfmove_age as _, mov.to_simple(), increase_mate_in(val)));
             if !mov.is_capture() {
                 sc.insert_killer(ply_depth, mov);
             }
@@ -258,19 +253,18 @@ pub fn negamax(game: &mut Game, sc: &mut SearchControl, hash: &mut Cache, mut al
         }
         if increase_mate_in(val) > alpha {
             alpha = increase_mate_in(val);
-            alpha_is_exact = true;
-            best_move = mov;
+            best_move = Some(mov);
         }
     }
 
 
-    if alpha_is_exact {
-        hash.cache(game, CacheEntry::new_value(depth as _, sc.halfmove_age, best_move, alpha));
+    if let Some(mov) = best_move {
+        // alpha is exact
+        hash.cache(game, CacheEntry::new_value(depth as _, sc.halfmove_age as _, mov.to_simple(), alpha));
         let cur_depth = (sc.initial_depth - depth) as usize;
-        sc.pv[cur_depth] = best_move;
     }
     else {
-        hash.cache(game, CacheEntry::new_upper(depth as _, sc.halfmove_age, Move::Nullmove, alpha));
+        hash.cache(game, CacheEntry::new_upper(depth as _, sc.halfmove_age as _, SimpleMove { from: 0, to: 0 }, alpha));
     }
     //info!("best alpha {}", alpha);
     return alpha;

+ 70 - 43
src/ttable.rs

@@ -1,10 +1,12 @@
 use game::Game;
 use evaluate::PosValue;
+use static_assertions::const_assert_eq;
 use std::collections::{HashMap};
 use log::info;
 use zobrist;
 
-use crate::{movegen::Move, zobrist::Hash};
+use crate::movegen::{Move, SimpleMove};
+use crate::zobrist::Hash;
 
 #[derive(Clone, PartialEq)]
 pub enum EntryType {
@@ -15,52 +17,63 @@ pub enum EntryType {
 
 #[derive(Clone)]
 pub struct CacheEntry {
-    pub entry_type: EntryType,
-    pub halfmove_age: u16,
-    pub mov: Move,
-    pub depth: i16,
-    pub value: PosValue,
+    pub entry_type: EntryType,  // 1 byte
+    pub halfmove_age: u8,       // 1 byte
+    pub mov: SimpleMove,        // 2 bytes
+    pub value_and_depth: i32,   // 4 bytes
 }
+const_assert_eq!(std::mem::size_of::<CacheEntry>(), 8);
 
 impl CacheEntry {
     pub fn null() -> Self {
-        CacheEntry { entry_type: EntryType::Value, halfmove_age: 0, mov: Move::Nullmove, depth: 0, value: 0 }
+        CacheEntry { entry_type: EntryType::Value, halfmove_age: 0, mov: SimpleMove { from: 0, to: 0 }, value_and_depth: 0 }
     }
 
-    pub fn new_value(depth: i16, halfmove_age: u16, mov: Move, value: PosValue) -> Self {
+    pub fn new_value(depth: u8, halfmove_age: u8, mov: SimpleMove, value: PosValue) -> Self {
         CacheEntry {
             entry_type: EntryType::Value,
             halfmove_age,
             mov,
-            depth,
-            value,
+            value_and_depth: (value << 8) | (depth as i32),
         }
     }
 
-    pub fn new_upper(depth: i16, halfmove_age: u16, mov: Move, value: PosValue) -> Self {
+    pub fn new_upper(depth: u8, halfmove_age: u8, mov: SimpleMove, value: PosValue) -> Self {
         CacheEntry {
             entry_type: EntryType::UpperBound,
             halfmove_age,
             mov,
-            depth,
-            value,
+            value_and_depth: (value << 8) | (depth as i32),
         }
     }
 
-    pub fn new_lower(depth: i16, halfmove_age: u16, mov: Move, value: PosValue) -> Self {
+    pub fn new_lower(depth: u8, halfmove_age: u8, mov: SimpleMove, value: PosValue) -> Self {
         CacheEntry {
             entry_type: EntryType::LowerBound,
             halfmove_age,
             mov,
-            depth,
-            value,
+            value_and_depth: (value << 8) | (depth as i32),
         }
     }
+    
+    pub fn value(&self) -> PosValue {
+        self.value_and_depth >> 8
+    }
+
+    pub fn depth(&self) -> u8 {
+        (self.value_and_depth & 0xFF) as u8
+    }
+}
+
+struct Bucket {
+    hashes: [Hash; 4],
+    entries: [CacheEntry; 4],
 }
 
-type TableEntry = (zobrist::Hash, CacheEntry);
+const_assert_eq!(std::mem::size_of::<Bucket>(), 64);
+
 pub struct Cache {
-    table: Vec<TableEntry>,
+    table: Vec<Bucket>,
 }
 
 #[derive(Clone)]
@@ -69,11 +82,19 @@ pub struct RepetitionTable {
 }
 
 impl Cache {
-    pub fn new() -> Self {
-        let mut c = Cache {
-            table: Vec::new(),
+    pub fn megabytes(mbytes: usize) -> Self {
+        Self::new(mbytes * 1024 * 1024 / std::mem::size_of::<Bucket>())
+    }
+
+    pub fn new(length: usize) -> Self {
+        let c = Cache {
+            table: unsafe {
+                let layout = std::alloc::Layout::array::<Bucket>(length).unwrap();
+                let allayout = layout.align_to(64).unwrap();
+                let ptr = std::alloc::alloc(allayout);
+                Vec::from_raw_parts(ptr.cast(), length, length)
+            },
         };
-        c.table.resize(4096 * 512, (0, CacheEntry::null()));
         c
     }
 
@@ -81,43 +102,41 @@ impl Cache {
         (hash % (self.table.len() as u64)) as usize
     }
 
-    pub fn lookup<'a>(&'a self, game_pos: &Game) -> Option<CacheEntry> {
+    pub fn lookup<'a>(&'a self, game_pos: &Game) -> Option<&'a CacheEntry> {
         if game_pos.zobrist.is_none() {
             info!("invalid zobrist");
         }
         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
+        let bucket = &self.table[index];
+        for i in 0..bucket.hashes.len() {
+            if bucket.hashes[i] == hash {
+                return Some(&bucket.entries[i]);
+            }
         }
+        return None;
     }
 
-    fn should_replace(old: &TableEntry, new_hash: Hash, new: &CacheEntry) -> bool {
-        let (old_hash, old_ce) = old;
-
-        if *old_hash == 0 {
+    fn should_replace(old_hash: Hash, old_ce: &CacheEntry, new_hash: Hash, new: &CacheEntry) -> bool {
+        if old_hash == 0 {
             return true;
         }
 
         // different positions
-        if *old_hash != new_hash {
+        if old_hash != new_hash {
             if old_ce.halfmove_age < new.halfmove_age {
                 return true;
             }
             if old_ce.entry_type == EntryType::Value {
                 return false;
             }
-            if old_ce.depth <= new.depth {
+            if old_ce.depth() <= new.depth() {
                 return true;
             }
         }
 
-        if new.depth >= old_ce.depth {
+        if new.depth() >= old_ce.depth() {
             return true;
         }
 
@@ -131,19 +150,27 @@ impl Cache {
 
         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);
+        let ptr: *const Bucket = &self.table[index];
+        if !ptr.is_aligned_to(64) {
+            println!("AAAH: {}", std::mem::size_of::<Bucket>());
+        }
+
+        let bucket = &mut self.table[index];
+
+        for i in 0..bucket.hashes.len() {
+            if Self::should_replace(bucket.hashes[i], &bucket.entries[i], hash, &ce) {
+                bucket.hashes[i] = hash;
+                bucket.entries[i] = ce;
+                break;
+            }
         }
     }
 
     pub fn fullness_permill(&self) -> usize{
         let mut permill = 0;
-        for i in 0..100 {
-            if self.table[i].0 != 0 {
-                permill += 10;
-            }
+        for i in 0..25 {
+            permill += 10 * self.table[i].hashes.iter().filter(|h| **h == 0).count();
         }
         permill
     }