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 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 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}