From a892e2493b5efddb5bebaeecf360d744b75cc2d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E8=88=AA=E5=AE=87?= <3364451258@qq.com> Date: Sun, 31 May 2026 15:45:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BD=AE=E6=8D=A2=E8=A1=A8=20=E2=80=94?= =?UTF-8?q?=20Zobrist=20=E7=B4=A2=E5=BC=95=20+=20depth=20=E4=BC=98?= =?UTF-8?q?=E5=85=88=E6=9B=BF=E6=8D=A2=20+=205=20=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- core/src/ai/mod.rs | 2 + core/src/ai/trans_table.rs | 120 +++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 core/src/ai/trans_table.rs diff --git a/core/src/ai/mod.rs b/core/src/ai/mod.rs index 2bcd170..f622fed 100644 --- a/core/src/ai/mod.rs +++ b/core/src/ai/mod.rs @@ -10,4 +10,6 @@ pub trait AiEngine: Send + Sync { pub mod evaluate; pub mod killer; pub mod search; +pub mod trans_table; pub mod vcf; +pub mod opening; diff --git a/core/src/ai/trans_table.rs b/core/src/ai/trans_table.rs new file mode 100644 index 0000000..b961a6e --- /dev/null +++ b/core/src/ai/trans_table.rs @@ -0,0 +1,120 @@ +use crate::types::{Position, ZobristHash}; + +const TT_SIZE: usize = 1 << 20; // ~100 万条目 +const TT_MASK: usize = TT_SIZE - 1; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BoundType { + Exact, + LowerBound, + UpperBound, +} + +#[derive(Debug, Clone)] +pub struct TTEntry { + pub hash: ZobristHash, + pub depth: u8, + pub score: i32, + pub bound: BoundType, + pub best_move: Option, +} + +pub struct TransTable { + entries: Box<[Option]>, +} + +impl TransTable { + pub fn new() -> Self { + Self::default() + } + + pub fn probe(&self, hash: ZobristHash, depth: u8) -> Option<&TTEntry> { + let idx = (hash as usize) & TT_MASK; + self.entries[idx] + .as_ref() + .filter(|e| e.hash == hash && e.depth >= depth) + } + + pub fn store( + &mut self, + hash: ZobristHash, + depth: u8, + score: i32, + bound: BoundType, + best_move: Option, + ) { + let idx = (hash as usize) & TT_MASK; + let should_replace = match &self.entries[idx] { + None => true, + Some(old) => depth >= old.depth, + }; + if should_replace { + self.entries[idx] = Some(TTEntry { + hash, + depth, + score, + bound, + best_move, + }); + } + } + + pub fn clear(&mut self) { + for entry in self.entries.iter_mut() { + *entry = None; + } + } +} + +impl Default for TransTable { + fn default() -> Self { + Self { + entries: vec![None; TT_SIZE].into_boxed_slice(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_store_and_probe() { + let mut tt = TransTable::new(); + tt.store(12345, 3, 100, BoundType::Exact, Some(Position::new(7, 7))); + let entry = tt.probe(12345, 2).unwrap(); + assert_eq!(entry.score, 100); + assert_eq!(entry.best_move, Some(Position::new(7, 7))); + } + + #[test] + fn test_depth_requirement() { + let mut tt = TransTable::new(); + tt.store(42, 5, 200, BoundType::Exact, None); + assert!(tt.probe(42, 4).is_some()); + assert!(tt.probe(42, 6).is_none()); + } + + #[test] + fn test_hash_collision_prevention() { + let mut tt = TransTable::new(); + tt.store(100, 3, 50, BoundType::Exact, None); + assert!(tt.probe(200, 1).is_none()); + } + + #[test] + fn test_depth_priority_replacement() { + let mut tt = TransTable::new(); + tt.store(999, 2, 10, BoundType::Exact, None); + tt.store(999, 5, 99, BoundType::Exact, None); + assert_eq!(tt.probe(999, 3).unwrap().score, 99); + } + + #[test] + fn test_clear() { + let mut tt = TransTable::new(); + tt.store(1, 1, 1, BoundType::Exact, None); + tt.clear(); + assert!(tt.probe(1, 0).is_none()); + } +}