fathom_syzygy/
lib.rs

1use std::{
2    ffi::CString,
3    path::Path,
4    sync::atomic::{AtomicBool, Ordering},
5};
6
7use fathom_syzygy_sys as fathom;
8use thiserror::Error;
9
10pub const CASTLE_WHITE_KINGSIDE: u32 = fathom::TB_CASTLING_K;
11pub const CASTLE_WHITE_QUEENSIDE: u32 = fathom::TB_CASTLING_Q;
12pub const CASTLE_BLACK_KINGSIDE: u32 = fathom::TB_CASTLING_k;
13pub const CASTLE_BLACK_QUEENSIDE: u32 = fathom::TB_CASTLING_q;
14
15#[derive(Clone, Debug, Error)]
16pub enum Error {
17    #[error("Invalid path")]
18    InvalidPath,
19    #[error("Library already initialized")]
20    AlreadyInitialized,
21}
22
23static mut INITIALIZED: AtomicBool = AtomicBool::new(false);
24
25#[derive(Debug)]
26pub struct Fathom {
27    _phantom: std::marker::PhantomData<()>,
28}
29
30impl Fathom {
31    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
32        let initialized_before = unsafe { INITIALIZED.swap(true, Ordering::SeqCst) };
33        if initialized_before {
34            return Err(Error::AlreadyInitialized);
35        }
36
37        let fathom = Fathom {
38            _phantom: Default::default(),
39        };
40
41        fathom.reload(path)
42    }
43
44    pub fn reload<P: AsRef<Path>>(self, path: P) -> Result<Self, Error> {
45        let pathref = path.as_ref();
46        let pathstr = pathref.to_str().ok_or(Error::InvalidPath)?;
47        let c_string = CString::new(pathstr).map_err(|_| Error::InvalidPath)?;
48
49        unsafe { fathom::tb_init(c_string.as_ptr()) };
50
51        Ok(self)
52    }
53
54    pub fn get_probers(&mut self) -> (RootProber<'_>, Prober<'_>) {
55        (RootProber::new(), Prober::new())
56    }
57
58    pub fn max_pieces(&self) -> u32 {
59        unsafe { fathom::TB_LARGEST }
60    }
61}
62
63impl Drop for Fathom {
64    fn drop(&mut self) {
65        unsafe {
66            fathom::tb_free();
67            INITIALIZED.store(false, Ordering::SeqCst);
68        }
69    }
70}
71
72#[derive(Debug)]
73pub struct RootProber<'a> {
74    phantom: std::marker::PhantomData<&'a ()>,
75}
76
77impl<'a> RootProber<'a> {
78    // This function MUST NOT be public.
79    fn new() -> Self {
80        Self {
81            phantom: Default::default(),
82        }
83    }
84
85    pub fn max_pieces(&self) -> u32 {
86        unsafe { fathom::TB_LARGEST }
87    }
88
89    pub fn probe(&mut self, position: &Position) -> Option<RootProbeResult> {
90        let result = unsafe {
91            fathom::tb_probe_root(
92                position.white,
93                position.black,
94                position.kings,
95                position.queens,
96                position.rooks,
97                position.bishops,
98                position.knights,
99                position.pawns,
100                position.rule50,
101                position.castling,
102                position.ep,
103                position.turn,
104                std::ptr::null_mut(),
105            )
106        };
107
108        if result == fathom::TB_RESULT_CHECKMATE
109            || result == fathom::TB_RESULT_STALEMATE
110            || result == fathom::TB_RESULT_FAILED
111        {
112            return None;
113        }
114
115        let wdl = Wdl::extract(result)?;
116        let best_move = Move::extract(result)?;
117        let dtz =
118            u16::try_from((result & fathom::TB_RESULT_DTZ_MASK) >> fathom::TB_RESULT_DTZ_SHIFT)
119                .ok()?;
120
121        Some(RootProbeResult {
122            wdl,
123            best_move,
124            dtz,
125        })
126    }
127}
128
129#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
130pub struct RootProbeResult {
131    pub wdl: Wdl,
132    pub best_move: Move,
133    pub dtz: u16,
134}
135
136#[derive(Copy, Clone, Debug)]
137pub struct Prober<'a> {
138    phantom: std::marker::PhantomData<&'a ()>,
139}
140
141impl<'a> Prober<'a> {
142    // This function MUST NOT be public.
143    fn new() -> Self {
144        Self {
145            phantom: Default::default(),
146        }
147    }
148
149    pub fn max_pieces(&self) -> u32 {
150        unsafe { fathom::TB_LARGEST }
151    }
152
153    pub fn probe(&self, position: &Position) -> Option<Wdl> {
154        let result = unsafe {
155            fathom::tb_probe_root(
156                position.white,
157                position.black,
158                position.kings,
159                position.queens,
160                position.rooks,
161                position.bishops,
162                position.knights,
163                position.pawns,
164                position.rule50,
165                position.castling,
166                position.ep,
167                position.turn,
168                std::ptr::null_mut(),
169            )
170        };
171
172        if result == fathom::TB_RESULT_FAILED {
173            return None;
174        }
175
176        Wdl::extract(result)
177    }
178}
179
180pub struct Position {
181    pub white: u64,
182    pub black: u64,
183    pub kings: u64,
184    pub queens: u64,
185    pub rooks: u64,
186    pub bishops: u64,
187    pub knights: u64,
188    pub pawns: u64,
189    pub rule50: u32,
190    pub castling: u32,
191    pub ep: u32,
192    pub turn: u8,
193}
194
195#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
196pub struct Square(u8);
197
198impl From<Square> for u8 {
199    fn from(sq: Square) -> Self {
200        sq.0
201    }
202}
203
204impl Square {
205    fn extract_to(result: u32) -> Option<Self> {
206        let sq = (result & fathom::TB_RESULT_TO_MASK) >> fathom::TB_RESULT_TO_SHIFT;
207        let sq = u8::try_from(sq).ok()?;
208        if sq < 64 {
209            Some(Self(sq))
210        } else {
211            None
212        }
213    }
214
215    fn extract_from(result: u32) -> Option<Self> {
216        let sq = (result & fathom::TB_RESULT_FROM_MASK) >> fathom::TB_RESULT_FROM_SHIFT;
217        let sq = u8::try_from(sq).ok()?;
218        if sq < 64 {
219            Some(Self(sq))
220        } else {
221            None
222        }
223    }
224}
225
226#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
227pub enum PromotionPiece {
228    None,
229    Knight,
230    Bishop,
231    Rook,
232    Queen,
233}
234
235impl PromotionPiece {
236    fn extract(result: u32) -> Option<Self> {
237        match (result & fathom::TB_RESULT_PROMOTES_MASK) >> fathom::TB_RESULT_PROMOTES_SHIFT {
238            fathom::TB_PROMOTES_NONE => Some(Self::None),
239            fathom::TB_PROMOTES_KNIGHT => Some(Self::Knight),
240            fathom::TB_PROMOTES_BISHOP => Some(Self::Bishop),
241            fathom::TB_PROMOTES_ROOK => Some(Self::Rook),
242            fathom::TB_PROMOTES_QUEEN => Some(Self::Queen),
243            _ => None,
244        }
245    }
246}
247
248#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
249pub struct Move {
250    pub from: Square,
251    pub to: Square,
252    pub promote: PromotionPiece,
253    pub en_passant: bool,
254}
255
256impl Move {
257    fn extract(result: u32) -> Option<Self> {
258        let from = Square::extract_from(result)?;
259        let to = Square::extract_to(result)?;
260        let promote = PromotionPiece::extract(result)?;
261        let en_passant = (result & fathom::TB_RESULT_EP_MASK) >> fathom::TB_RESULT_EP_SHIFT > 0;
262
263        Some(Self {
264            from,
265            to,
266            promote,
267            en_passant,
268        })
269    }
270}
271
272#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
273pub enum Wdl {
274    Loss,
275    BlessedLoss,
276    Draw,
277    CursedWin,
278    Win,
279}
280
281impl Wdl {
282    fn extract(result: u32) -> Option<Self> {
283        match (result & fathom::TB_RESULT_WDL_MASK) >> fathom::TB_RESULT_WDL_SHIFT {
284            fathom::TB_LOSS => Some(Wdl::Loss),
285            fathom::TB_BLESSED_LOSS => Some(Wdl::BlessedLoss),
286            fathom::TB_DRAW => Some(Wdl::Draw),
287            fathom::TB_CURSED_WIN => Some(Wdl::CursedWin),
288            fathom::TB_WIN => Some(Wdl::Win),
289            _ => None,
290        }
291    }
292}