use std::io;
use std::fs;
use std::iter::FromIterator;
use std::marker::PhantomData;
use std::path::Path;
use arrayvec::ArrayVec;
use bitflags::bitflags;
use byteorder::{BE, LE, ByteOrder as _, ReadBytesExt as _};
use itertools::Itertools as _;
use lazy_static::lazy_static;
use num_integer::binomial;
use positioned_io::{RandomAccessFile, ReadAt, ReadBytesAtExt as _};
use shakmaty::{Bitboard, Color, File, Material, Piece, Position, Rank, Role, Square};
use crate::errors::{ProbeError, ProbeResult};
use crate::material::MaterialExt;
use crate::types::{DecisiveWdl, Dtz, Metric, Pieces, Syzygy, Wdl, MAX_PIECES};
trait TableTag {
const METRIC: Metric;
}
#[derive(Debug)]
enum WdlTag {}
impl TableTag for WdlTag {
const METRIC: Metric = Metric::Wdl;
}
#[derive(Debug)]
enum DtzTag {}
impl TableTag for DtzTag {
const METRIC: Metric = Metric::Dtz;
}
bitflags! {
struct Layout: u8 {
const SPLIT = 1;
const HAS_PAWNS = 2;
}
}
bitflags! {
struct Flag: u8 {
const STM = 1;
const MAPPED = 2;
const WIN_PLIES = 4;
const LOSS_PLIES = 8;
const WIDE_DTZ = 16;
const SINGLE_VALUE = 128;
}
}
const MAX_BLOCK_SIZE: usize = 1024;
const TRIANGLE: [u64; 64] = [
6, 0, 1, 2, 2, 1, 0, 6,
0, 7, 3, 4, 4, 3, 7, 0,
1, 3, 8, 5, 5, 8, 3, 1,
2, 4, 5, 9, 9, 5, 4, 2,
2, 4, 5, 9, 9, 5, 4, 2,
1, 3, 8, 5, 5, 8, 3, 1,
0, 7, 3, 4, 4, 3, 7, 0,
6, 0, 1, 2, 2, 1, 0, 6,
];
const INV_TRIANGLE: [usize; 10] = [1, 2, 3, 10, 11, 19, 0, 9, 18, 27];
const LOWER: [u64; 64] = [
28, 0, 1, 2, 3, 4, 5, 6,
0, 29, 7, 8, 9, 10, 11, 12,
1, 7, 30, 13, 14, 15, 16, 17,
2, 8, 13, 31, 18, 19, 20, 21,
3, 9, 14, 18, 32, 22, 23, 24,
4, 10, 15, 19, 22, 33, 25, 26,
5, 11, 16, 20, 23, 25, 34, 27,
6, 12, 17, 21, 24, 26, 27, 35,
];
const MULT_TWIST: [u64; 64] = [
15, 63, 55, 47, 40, 48, 56, 12,
62, 11, 39, 31, 24, 32, 8, 57,
54, 38, 7, 23, 16, 4, 33, 49,
46, 30, 22, 3, 0, 17, 25, 41,
45, 29, 21, 2, 1, 18, 26, 42,
53, 37, 6, 20, 19, 5, 34, 50,
61, 10, 36, 28, 27, 35, 9, 58,
14, 60, 52, 44, 43, 51, 59, 13,
];
const Z0: u64 = u64::max_value();
const KK_IDX: [[u64; 64]; 10] = [[
Z0, Z0, Z0, 0, 1, 2, 3, 4,
Z0, Z0, Z0, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33,
34, 35, 36, 37, 38, 39, 40, 41,
42, 43, 44, 45, 46, 47, 48, 49,
50, 51, 52, 53, 54, 55, 56, 57,
], [
58, Z0, Z0, Z0, 59, 60, 61, 62,
63, Z0, Z0, Z0, 64, 65, 66, 67,
68, 69, 70, 71, 72, 73, 74, 75,
76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 91,
92, 93, 94, 95, 96, 97, 98, 99,
100, 101, 102, 103, 104, 105, 106, 107,
108, 109, 110, 111, 112, 113, 114, 115,
], [
116, 117, Z0, Z0, Z0, 118, 119, 120,
121, 122, Z0, Z0, Z0, 123, 124, 125,
126, 127, 128, 129, 130, 131, 132, 133,
134, 135, 136, 137, 138, 139, 140, 141,
142, 143, 144, 145, 146, 147, 148, 149,
150, 151, 152, 153, 154, 155, 156, 157,
158, 159, 160, 161, 162, 163, 164, 165,
166, 167, 168, 169, 170, 171, 172, 173,
], [
174, Z0, Z0, Z0, 175, 176, 177, 178,
179, Z0, Z0, Z0, 180, 181, 182, 183,
184, Z0, Z0, Z0, 185, 186, 187, 188,
189, 190, 191, 192, 193, 194, 195, 196,
197, 198, 199, 200, 201, 202, 203, 204,
205, 206, 207, 208, 209, 210, 211, 212,
213, 214, 215, 216, 217, 218, 219, 220,
221, 222, 223, 224, 225, 226, 227, 228,
], [
229, 230, Z0, Z0, Z0, 231, 232, 233,
234, 235, Z0, Z0, Z0, 236, 237, 238,
239, 240, Z0, Z0, Z0, 241, 242, 243,
244, 245, 246, 247, 248, 249, 250, 251,
252, 253, 254, 255, 256, 257, 258, 259,
260, 261, 262, 263, 264, 265, 266, 267,
268, 269, 270, 271, 272, 273, 274, 275,
276, 277, 278, 279, 280, 281, 282, 283,
], [
284, 285, 286, 287, 288, 289, 290, 291,
292, 293, Z0, Z0, Z0, 294, 295, 296,
297, 298, Z0, Z0, Z0, 299, 300, 301,
302, 303, Z0, Z0, Z0, 304, 305, 306,
307, 308, 309, 310, 311, 312, 313, 314,
315, 316, 317, 318, 319, 320, 321, 322,
323, 324, 325, 326, 327, 328, 329, 330,
331, 332, 333, 334, 335, 336, 337, 338,
], [
Z0, Z0, 339, 340, 341, 342, 343, 344,
Z0, Z0, 345, 346, 347, 348, 349, 350,
Z0, Z0, 441, 351, 352, 353, 354, 355,
Z0, Z0, Z0, 442, 356, 357, 358, 359,
Z0, Z0, Z0, Z0, 443, 360, 361, 362,
Z0, Z0, Z0, Z0, Z0, 444, 363, 364,
Z0, Z0, Z0, Z0, Z0, Z0, 445, 365,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, 446,
], [
Z0, Z0, Z0, 366, 367, 368, 369, 370,
Z0, Z0, Z0, 371, 372, 373, 374, 375,
Z0, Z0, Z0, 376, 377, 378, 379, 380,
Z0, Z0, Z0, 447, 381, 382, 383, 384,
Z0, Z0, Z0, Z0, 448, 385, 386, 387,
Z0, Z0, Z0, Z0, Z0, 449, 388, 389,
Z0, Z0, Z0, Z0, Z0, Z0, 450, 390,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, 451,
], [
452, 391, 392, 393, 394, 395, 396, 397,
Z0, Z0, Z0, Z0, 398, 399, 400, 401,
Z0, Z0, Z0, Z0, 402, 403, 404, 405,
Z0, Z0, Z0, Z0, 406, 407, 408, 409,
Z0, Z0, Z0, Z0, 453, 410, 411, 412,
Z0, Z0, Z0, Z0, Z0, 454, 413, 414,
Z0, Z0, Z0, Z0, Z0, Z0, 455, 415,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, 456,
], [
457, 416, 417, 418, 419, 420, 421, 422,
Z0, 458, 423, 424, 425, 426, 427, 428,
Z0, Z0, Z0, Z0, Z0, 429, 430, 431,
Z0, Z0, Z0, Z0, Z0, 432, 433, 434,
Z0, Z0, Z0, Z0, Z0, 435, 436, 437,
Z0, Z0, Z0, Z0, Z0, 459, 438, 439,
Z0, Z0, Z0, Z0, Z0, Z0, 460, 440,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, 461,
]];
const PP_IDX: [[u64; 64]; 10] = [[
0, Z0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46,
Z0, 47, 48, 49, 50, 51, 52, 53,
54, 55, 56, 57, 58, 59, 60, 61,
], [
62, Z0, Z0, 63, 64, 65, Z0, 66,
Z0, 67, 68, 69, 70, 71, 72, Z0,
73, 74, 75, 76, 77, 78, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 91, 92, 93, 94, 95, 96,
Z0, 97, 98, 99, 100, 101, 102, 103,
Z0, 104, 105, 106, 107, 108, 109, Z0,
110, Z0, 111, 112, 113, 114, Z0, 115,
], [
116, Z0, Z0, Z0, 117, Z0, Z0, 118,
Z0, 119, 120, 121, 122, 123, 124, Z0,
Z0, 125, 126, 127, 128, 129, 130, Z0,
131, 132, 133, 134, 135, 136, 137, 138,
Z0, 139, 140, 141, 142, 143, 144, 145,
Z0, 146, 147, 148, 149, 150, 151, Z0,
Z0, 152, 153, 154, 155, 156, 157, Z0,
158, Z0, Z0, 159, 160, Z0, Z0, 161,
], [
162, Z0, Z0, Z0, Z0, Z0, Z0, 163,
Z0, 164, Z0, 165, 166, 167, 168, Z0,
Z0, 169, 170, 171, 172, 173, 174, Z0,
Z0, 175, 176, 177, 178, 179, 180, Z0,
Z0, 181, 182, 183, 184, 185, 186, Z0,
Z0, Z0, 187, 188, 189, 190, 191, Z0,
Z0, 192, 193, 194, 195, 196, 197, Z0,
198, Z0, Z0, Z0, Z0, Z0, Z0, 199,
], [
200, Z0, Z0, Z0, Z0, Z0, Z0, 201,
Z0, 202, Z0, Z0, 203, Z0, 204, Z0,
Z0, Z0, 205, 206, 207, 208, Z0, Z0,
Z0, 209, 210, 211, 212, 213, 214, Z0,
Z0, Z0, 215, 216, 217, 218, 219, Z0,
Z0, Z0, 220, 221, 222, 223, Z0, Z0,
Z0, 224, Z0, 225, 226, Z0, 227, Z0,
228, Z0, Z0, Z0, Z0, Z0, Z0, 229,
], [
230, Z0, Z0, Z0, Z0, Z0, Z0, 231,
Z0, 232, Z0, Z0, Z0, Z0, 233, Z0,
Z0, Z0, 234, Z0, 235, 236, Z0, Z0,
Z0, Z0, 237, 238, 239, 240, Z0, Z0,
Z0, Z0, Z0, 241, 242, 243, Z0, Z0,
Z0, Z0, 244, 245, 246, 247, Z0, Z0,
Z0, 248, Z0, Z0, Z0, Z0, 249, Z0,
250, Z0, Z0, Z0, Z0, Z0, Z0, 251,
], [
Z0, Z0, Z0, Z0, Z0, Z0, Z0, 259,
Z0, 252, Z0, Z0, Z0, Z0, 260, Z0,
Z0, Z0, 253, Z0, Z0, 261, Z0, Z0,
Z0, Z0, Z0, 254, 262, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, 255, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, 256, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, Z0, 257, Z0,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, 258,
], [
Z0, Z0, Z0, Z0, Z0, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, Z0, 268, Z0,
Z0, Z0, 263, Z0, Z0, 269, Z0, Z0,
Z0, Z0, Z0, 264, 270, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, 265, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, 266, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, Z0, 267, Z0,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, Z0,
], [
Z0, Z0, Z0, Z0, Z0, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, 274, Z0, Z0,
Z0, Z0, Z0, 271, 275, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, 272, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, 273, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, Z0,
], [
Z0, Z0, Z0, Z0, Z0, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, 277, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, 276, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, Z0,
Z0, Z0, Z0, Z0, Z0, Z0, Z0, Z0
]];
const TEST45: Bitboard = Bitboard(0x1_0307_0000_0000);
lazy_static! {
static ref CONSTS: Consts = Consts::new();
}
struct Consts {
mult_idx: [[u64; 10]; 5],
mult_factor: [u64; 5],
map_pawns: [u64; 64],
lead_pawn_idx: [[u64; 64]; 6],
lead_pawns_size: [[u64; 4]; 6],
}
impl Consts {
fn new() -> Consts {
let mut mult_idx = [[0; 10]; 5];
let mut mult_factor = [0; 5];
for i in 0..5 {
let mut s = 0;
for j in 0..10 {
mult_idx[i][j] = s;
s += if i == 0 { 1 } else { binomial(MULT_TWIST[INV_TRIANGLE[j]], i as u64) };
}
mult_factor[i] = s;
}
let mut available_squares = 48;
let mut map_pawns = [0; 64];
let mut lead_pawn_idx = [[0; 64]; 6];
let mut lead_pawns_size = [[0; 4]; 6];
for lead_pawns_cnt in 1..=5 {
for file in (0..4).map(File::new) {
let mut idx = 0;
for rank in (1..7).map(Rank::new) {
let sq = Square::from_coords(file, rank);
if lead_pawns_cnt == 1 {
available_squares -= 1;
map_pawns[usize::from(sq)] = available_squares;
available_squares -= 1;
map_pawns[usize::from(sq.flip_horizontal())] = available_squares;
}
lead_pawn_idx[lead_pawns_cnt][usize::from(sq)] = idx;
idx += binomial(map_pawns[usize::from(sq)], lead_pawns_cnt as u64 - 1);
}
lead_pawns_size[lead_pawns_cnt][usize::from(file)] = idx;
}
}
Consts {
mult_idx,
mult_factor,
map_pawns,
lead_pawn_idx,
lead_pawns_size,
}
}
}
fn read_magic_header<F: ReadAt>(raf: &F) -> ProbeResult<[u8; 4]> {
let mut buf = [0; 4];
if let Err(error) = raf.read_exact_at(0, &mut buf) {
match error.kind() {
io::ErrorKind::UnexpectedEof => Err(ProbeError::Magic { magic: buf }),
_ => Err(ProbeError::Read { error }),
}
} else {
Ok(buf)
}
}
fn read_lr<F: ReadAt>(raf: &F, ptr: u64) -> io::Result<(u16, u16)> {
let mut buf = [0; 3];
raf.read_exact_at(ptr, &mut buf)?;
let left = (u16::from(buf[1] & 0xf) << 8) | u16::from(buf[0]);
let right = (u16::from(buf[2]) << 4) | (u16::from(buf[1]) >> 4);
Ok((left, right))
}
fn nibble_to_piece(p: u8) -> Option<Piece> {
let color = Color::from_white(p & 8 == 0);
Some(match p & !8 {
1 => color.pawn(),
2 => color.knight(),
3 => color.bishop(),
4 => color.rook(),
5 => color.queen(),
6 => color.king(),
_ => return None,
})
}
fn offdiag(sq: Square) -> bool {
sq.file().flip_diagonal() != sq.rank()
}
fn parse_pieces<F: ReadAt>(raf: &F, ptr: u64, count: usize, side: Color) -> ProbeResult<Pieces> {
let mut buffer = [0; MAX_PIECES];
let bytes = &mut buffer[..count];
raf.read_exact_at(ptr, bytes)?;
let mut pieces = Pieces::new();
for p in bytes {
pieces.push(u!(nibble_to_piece(side.fold(*p & 0xf, *p >> 4))));
}
Ok(pieces)
}
fn group_pieces(pieces: &Pieces) -> ArrayVec<usize, MAX_PIECES> {
let mut result = ArrayVec::new();
let material = Material::from_iter(pieces.clone());
let first_len = if material.has_pawns() {
0
} else if material.unique_pieces() >= 3 {
3
} else if material.unique_pieces() == 2 {
2
} else {
usize::from(material.min_like_man())
};
if first_len > 0 {
result.push(first_len);
}
result.extend(pieces.iter()
.skip(first_len)
.group_by(|p| *p)
.into_iter().map(|(_, g)| g.count()));
result
}
#[derive(Debug, Clone)]
struct GroupData {
pieces: Pieces,
lens: ArrayVec<usize, MAX_PIECES>,
factors: ArrayVec<u64, { MAX_PIECES + 1 }>,
}
impl GroupData {
pub fn new<S: Syzygy>(pieces: Pieces, order: [u8; 2], file: usize) -> ProbeResult<GroupData> {
ensure!(pieces.len() >= 2);
let material = Material::from_iter(pieces.clone());
let lens = group_pieces(&pieces);
let pp = material.white.has_pawns() && material.black.has_pawns();
let mut factors = ArrayVec::from([0; MAX_PIECES + 1]);
factors.truncate(lens.len() + 1);
let mut free_squares = 64 - lens[0] - if pp { lens[1] } else { 0 };
let mut next = if pp { 2 } else { 1 };
let mut idx = 1;
let mut k = 0;
while next < lens.len() || k == order[0] || k == order[1] {
if k == order[0] {
factors[0] = idx;
if material.has_pawns() {
idx *= CONSTS.lead_pawns_size[lens[0]][file];
} else if material.unique_pieces() >= 3 {
idx *= 31_332;
} else if material.unique_pieces() == 2 {
idx *= if S::CONNECTED_KINGS { 518 } else { 462 };
} else if material.min_like_man() == 2 {
idx *= 278;
} else {
idx *= CONSTS.mult_factor[usize::from(material.min_like_man()) - 1];
}
} else if k == order[1] {
factors[1] = idx;
idx *= binomial(48 - lens[0], lens[1]) as u64;
} else {
factors[next] = idx;
idx *= binomial(free_squares, lens[next]) as u64;
free_squares -= lens[next];
next += 1;
}
k += 1;
}
factors[lens.len()] = idx;
Ok(GroupData {
pieces,
lens,
factors,
})
}
}
#[derive(Debug)]
enum DtzMap {
Normal {
map_ptr: u64,
by_wdl: [u16; 4]
},
Wide {
map_ptr: u64,
by_wdl: [u16; 4]
},
}
impl DtzMap {
fn read<F: ReadAt>(&self, raf: &F, wdl: DecisiveWdl, res: u16) -> ProbeResult<u16> {
let wdl = match wdl {
DecisiveWdl::Win => 0,
DecisiveWdl::Loss => 1,
DecisiveWdl::CursedWin => 2,
DecisiveWdl::BlessedLoss => 3,
};
Ok(match *self {
DtzMap::Normal { map_ptr, by_wdl } => {
let offset = map_ptr + u64::from(by_wdl[wdl]) + u64::from(res);
u16::from(raf.read_u8_at(offset)?)
}
DtzMap::Wide { map_ptr, by_wdl } => {
let offset = map_ptr + 2 * (u64::from(by_wdl[wdl]) + u64::from(res));
raf.read_u16_at::<LE>(offset)?
}
})
}
}
#[derive(Debug)]
struct PairsData {
flags: Flag,
groups: GroupData,
block_size: u32,
span: u32,
blocks_num: u32,
btree: u64,
min_symlen: u8,
lowest_sym: u64,
base: Vec<u64>,
symlen: Vec<u8>,
sparse_index: u64,
sparse_index_size: u32,
block_lengths: u64,
block_length_size: u32,
data: u64,
dtz_map: Option<DtzMap>,
}
impl PairsData {
pub fn parse<S: Syzygy, T: TableTag, F: ReadAt>(raf: &F, mut ptr: u64, groups: GroupData) -> ProbeResult<(PairsData, u64)> {
let flags = Flag::from_bits_truncate(raf.read_u8_at(ptr)?);
if flags.contains(Flag::SINGLE_VALUE) {
let single_value = if T::METRIC == Metric::Wdl {
raf.read_u8_at(ptr + 1)?
} else if S::CAPTURES_COMPULSORY {
1 } else {
0
};
return Ok((PairsData {
flags,
min_symlen: single_value,
groups,
base: Vec::new(),
block_lengths: 0,
block_length_size: 0,
block_size: 0,
blocks_num: 0,
btree: 0,
data: 0,
lowest_sym: 0,
span: 0,
sparse_index: 0,
sparse_index_size: 0,
symlen: Vec::new(),
dtz_map: None,
}, ptr + 2));
}
let mut header = [0; 10];
raf.read_exact_at(ptr, &mut header)?;
let tb_size = groups.factors[groups.lens.len()];
let block_size = u!(1u32.checked_shl(u32::from(header[1])));
ensure!(block_size <= MAX_BLOCK_SIZE as u32);
let span = u!(1u32.checked_shl(u32::from(header[2])));
let sparse_index_size = ((tb_size + u64::from(span) - 1) / u64::from(span)) as u32;
let padding = header[3];
let blocks_num = LE::read_u32(&header[4..]);
let block_length_size = u!(blocks_num.checked_add(u32::from(padding)));
let max_symlen = header[8];
ensure!(max_symlen <= 32);
let min_symlen = header[9];
ensure!(min_symlen <= 32);
ensure!(max_symlen >= min_symlen);
let h = usize::from(max_symlen - min_symlen + 1);
let lowest_sym = ptr + 10;
let mut base = vec![0u64; h];
for i in (0..h - 1).rev() {
let ptr = lowest_sym + i as u64 * 2;
base[i] = u!(u!(base[i + 1]
.checked_add(u64::from(raf.read_u16_at::<LE>(ptr)?)))
.checked_sub(u64::from(raf.read_u16_at::<LE>(ptr + 2)?))) / 2;
ensure!(base[i] * 2 >= base[i + 1]);
}
for (i, base) in base.iter_mut().enumerate() {
*base = u!(base.checked_shl(64 - (u32::from(min_symlen) + i as u32)));
}
ptr += 10 + h as u64 * 2;
let sym = raf.read_u16_at::<LE>(ptr)?;
ptr += 2;
let btree = ptr;
let mut symlen = vec![0; usize::from(sym)];
let mut visited = vec![false; symlen.len()];
for s in 0..sym {
read_symlen(raf, btree, &mut symlen, &mut visited, s, 16)?;
}
ptr += symlen.len() as u64 * 3 + (symlen.len() as u64 & 1);
Ok((PairsData {
flags,
groups,
block_size,
span,
blocks_num,
btree,
min_symlen,
lowest_sym,
base,
symlen,
sparse_index: 0, sparse_index_size,
block_lengths: 0, block_length_size,
data: 0,
dtz_map: None, }, ptr))
}
}
fn read_symlen<F: ReadAt>(raf: &F, btree: u64, symlen: &mut Vec<u8>, visited: &mut [bool], sym: u16, depth: u8) -> ProbeResult<()> {
if *u!(visited.get(usize::from(sym))) {
return Ok(());
}
let ptr = btree + 3 * u64::from(sym);
let (left, right) = read_lr(&raf, ptr)?;
if right == 0xfff {
symlen[usize::from(sym)] = 0;
} else {
let depth = u!(depth.checked_sub(1));
read_symlen(raf, btree, symlen, visited, left, depth)?;
read_symlen(raf, btree, symlen, visited, right, depth)?;
symlen[usize::from(sym)] = u!(u!(symlen[usize::from(left)]
.checked_add(symlen[usize::from(right)]))
.checked_add(1))
}
visited[usize::from(sym)] = true;
Ok(())
}
#[derive(Debug)]
struct FileData {
sides: ArrayVec<PairsData, 2>,
}
#[derive(Debug)]
struct Table<T: TableTag, P: Position + Syzygy, F: ReadAt> {
is_wdl: PhantomData<T>,
syzygy: PhantomData<P>,
raf: F,
num_unique_pieces: u8,
min_like_man: u8,
files: ArrayVec<FileData, 4>,
}
impl<T: TableTag, S: Position + Syzygy, F: ReadAt> Table<T, S, F> {
pub fn new(raf: F, material: &Material) -> ProbeResult<Table<T, S, F>> {
let material = material.clone();
assert!(material.count() <= MAX_PIECES);
assert!(material.white.count() >= 1);
assert!(material.black.count() >= 1);
let (magic, pawnless_magic) = match T::METRIC {
Metric::Wdl => (S::TBW.magic, S::PAWNLESS_TBW.map(|t| t.magic)),
Metric::Dtz => (S::TBZ.magic, S::PAWNLESS_TBZ.map(|t| t.magic)),
};
let magic_header = read_magic_header(&raf)?;
if magic != magic_header &&
(material.has_pawns() || pawnless_magic.map_or(true, |m| m != magic_header))
{
return Err(ProbeError::Magic { magic: magic_header });
}
let layout = Layout::from_bits_truncate(raf.read_u8_at(4)?);
let has_pawns = layout.contains(Layout::HAS_PAWNS);
let split = layout.contains(Layout::SPLIT);
ensure!(has_pawns == material.has_pawns());
ensure!(split != material.is_symmetric());
let pp = material.white.has_pawns() && material.black.has_pawns();
let num_files = if has_pawns { 4 } else { 1 };
let num_sides = if T::METRIC == Metric::Wdl && !material.is_symmetric() { 2 } else { 1 };
let mut ptr = 5;
let files = (0..num_files).map(|file| {
let order = [
[raf.read_u8_at(ptr)? & 0xf, if pp { raf.read_u8_at(ptr + 1)? & 0xf } else { 0xf }],
[raf.read_u8_at(ptr)? >> 4, if pp { raf.read_u8_at(ptr + 1)? >> 4 } else { 0xf }],
];
ptr += 1 + if pp { 1 } else { 0 };
let sides = [Color::White, Color::Black].iter().take(num_sides).map(|side| {
let pieces = parse_pieces(&raf, ptr, material.count(), *side)?;
let key = Material::from_iter(pieces.clone());
ensure!(key == material || key.into_flipped() == material);
GroupData::new::<S>(pieces, order[side.fold(0, 1)], file)
}).collect::<ProbeResult<ArrayVec<_, 2>>>()?;
ptr += material.count() as u64;
Ok(sides)
}).collect::<ProbeResult<ArrayVec<_, 4>>>()?;
ptr += ptr & 1;
ensure!((files[0][0].pieces[0].role == Role::Pawn) == has_pawns);
for file in files.iter() {
for side in file.iter() {
let key = Material::from_iter(side.pieces.clone());
ensure!(key == Material::from_iter(files[0][0].pieces.clone()));
}
}
let mut files = files.into_iter().map(|file| {
let sides = file.into_iter().map(|side| {
let (mut pairs, next_ptr) = PairsData::parse::<S, T, _>(&raf, ptr, side)?;
if T::METRIC == Metric::Dtz && S::CAPTURES_COMPULSORY && pairs.flags.contains(Flag::SINGLE_VALUE) {
pairs.min_symlen = 1;
}
ptr = next_ptr;
Ok(pairs)
}).collect::<ProbeResult<ArrayVec<_, 2>>>()?;
Ok(FileData { sides })
}).collect::<ProbeResult<ArrayVec<_, 4>>>()?;
if T::METRIC == Metric::Dtz {
let map_ptr = ptr;
for file in files.iter_mut() {
if file.sides[0].flags.contains(Flag::MAPPED) {
let mut by_wdl = [0; 4];
if file.sides[0].flags.contains(Flag::WIDE_DTZ) {
for idx in &mut by_wdl {
*idx = ((ptr - map_ptr + 2) / 2) as u16;
ptr += u64::from(raf.read_u16_at::<LE>(ptr)?) * 2 + 2;
}
file.sides[0].dtz_map = Some(DtzMap::Wide { map_ptr, by_wdl });
} else {
for idx in &mut by_wdl {
*idx = (ptr - map_ptr + 1) as u16;
ptr += u64::from(raf.read_u8_at(ptr)?) + 1;
}
file.sides[0].dtz_map = Some(DtzMap::Normal { map_ptr, by_wdl });
}
}
}
ptr += ptr & 1;
}
for file in files.iter_mut() {
for side in file.sides.iter_mut() {
side.sparse_index = ptr;
ptr += u64::from(side.sparse_index_size) * 6;
}
}
for file in files.iter_mut() {
for side in file.sides.iter_mut() {
side.block_lengths = ptr;
ptr += u64::from(side.block_length_size) * 2;
}
}
for file in files.iter_mut() {
for side in file.sides.iter_mut() {
ptr = (ptr + 0x3f) & !0x3f; side.data = ptr;
ptr = u!(ptr.checked_add(u64::from(side.blocks_num) * u64::from(side.block_size)));
}
}
Ok(Table {
is_wdl: PhantomData,
syzygy: PhantomData,
raf,
num_unique_pieces: material.unique_pieces(),
min_like_man: material.min_like_man(),
files,
})
}
fn decompress_pairs(&self, d: &PairsData, idx: u64) -> ProbeResult<u16> {
if d.flags.contains(Flag::SINGLE_VALUE) {
return Ok(u16::from(d.min_symlen));
}
let main_idx = idx / u64::from(d.span);
ensure!(main_idx <= u64::from(u32::max_value()));
let mut block = self.raf.read_u32_at::<LE>(d.sparse_index + 6 * main_idx)?;
let offset = i64::from(self.raf.read_u16_at::<LE>(d.sparse_index + 6 * main_idx + 4)?);
let mut lit_idx = idx as i64 % i64::from(d.span) - i64::from(d.span) / 2;
lit_idx += offset;
while lit_idx < 0 {
block = u!(block.checked_sub(1));
lit_idx += i64::from(self.raf.read_u16_at::<LE>(d.block_lengths + u64::from(block) * 2)?) + 1;
}
loop {
let block_length = i64::from(self.raf.read_u16_at::<LE>(d.block_lengths + u64::from(block) * 2)?) + 1;
if lit_idx >= block_length {
lit_idx -= block_length;
block = u!(block.checked_add(1));
} else {
break;
}
}
let mut block_buffer = [0; MAX_BLOCK_SIZE + 4];
let block_buffer = &mut block_buffer[..(d.block_size as usize + 4)];
self.raf.read_exact_at(u!(d.data.checked_add(u64::from(block) * u64::from(d.block_size))), block_buffer)?;
let mut cursor = io::Cursor::new(block_buffer);
let mut buf = cursor.read_u64::<BE>()?;
let mut buf_size = 64;
let mut sym;
loop {
let mut len = 0;
while buf < *u!(d.base.get(len)) {
len += 1;
}
sym = ((buf - d.base[len]) >> (64 - len - usize::from(d.min_symlen))) as u16;
sym += self.raf.read_u16_at::<LE>(d.lowest_sym + 2 * len as u64)?;
if lit_idx < i64::from(*u!(d.symlen.get(usize::from(sym)))) + 1 {
break;
}
lit_idx -= i64::from(*u!(d.symlen.get(usize::from(sym)))) + 1;
len += usize::from(d.min_symlen);
buf <<= len;
buf_size -= len;
if buf_size <= 32 {
buf_size += 32;
buf |= u64::from(cursor.read_u32::<BE>()?) << (64 - buf_size);
}
}
while *u!(d.symlen.get(usize::from(sym))) != 0 {
let (left, right) = read_lr(&self.raf, d.btree + 3 * u64::from(sym))?;
if lit_idx < i64::from(*u!(d.symlen.get(usize::from(left)))) + 1 {
sym = left;
} else {
lit_idx -= i64::from(*u!(d.symlen.get(usize::from(left)))) + 1;
sym = right;
}
}
let w = d.btree + 3 * u64::from(sym);
match T::METRIC {
Metric::Wdl => Ok(u16::from(self.raf.read_u8_at(w)?)),
Metric::Dtz => Ok(self.raf.read_u16_at::<LE>(w)? & 0xfff),
}
}
fn encode(&self, pos: &S) -> ProbeResult<Option<(&PairsData, u64)>> {
let key = pos.board().material();
let material = Material::from_iter(self.files[0].sides[0].groups.pieces.clone());
assert!(key == material || key == material.clone().into_flipped());
let symmetric_btm = material.is_symmetric() && pos.turn().is_black();
let black_stronger = key != material;
let flip = symmetric_btm || black_stronger;
let bside = pos.turn().is_black() ^ flip;
let mut squares: ArrayVec<Square, MAX_PIECES> = ArrayVec::new();
let mut used = Bitboard(0);
let file = &self.files[if material.has_pawns() {
let reference_pawn = self.files[0].sides[0].groups.pieces[0];
assert_eq!(reference_pawn.role, Role::Pawn);
let color = reference_pawn.color ^ flip;
let lead_pawns = pos.board().pawns() & pos.board().by_color(color);
used.extend(lead_pawns);
squares.extend(lead_pawns.into_iter().map(|sq| if flip { sq.flip_vertical() } else { sq }));
for i in 1..squares.len() {
if CONSTS.map_pawns[usize::from(squares[0])] < CONSTS.map_pawns[usize::from(squares[i])] {
squares.swap(0, i);
}
}
if squares[0].file() >= File::E {
squares[0].flip_horizontal().file() as usize
} else {
squares[0].file() as usize
}
} else {
0
}];
let side = &file.sides[if bside { file.sides.len() - 1 } else { 0 }];
if T::METRIC == Metric::Dtz && side.flags.contains(Flag::STM) != bside && (!material.is_symmetric() || material.has_pawns()) {
return Ok(None);
}
let lead_pawns_count = squares.len();
for piece in side.groups.pieces.iter().skip(lead_pawns_count) {
let color = piece.color ^ flip;
let square = u!((pos.board().by_piece(piece.role.of(color)) & !used).first());
squares.push(if flip { square.flip_vertical() } else { square });
used.add(square);
}
assert!(squares.len() >= 2);
if squares[0].file() >= File::E {
for square in &mut squares {
*square = square.flip_horizontal();
}
}
let mut idx = if material.has_pawns() {
let mut idx = CONSTS.lead_pawn_idx[lead_pawns_count][usize::from(squares[0])];
squares[1..lead_pawns_count].sort_unstable_by_key(|sq| CONSTS.map_pawns[usize::from(*sq)]);
for (i, &square) in squares.iter().enumerate().take(lead_pawns_count).skip(1) {
idx += binomial(CONSTS.map_pawns[usize::from(square)], i as u64);
}
idx
} else {
if squares[0].rank() >= Rank::Fifth {
for square in &mut squares {
*square = square.flip_vertical();
}
}
for i in 0..side.groups.lens[0] {
if squares[i].file().flip_diagonal() == squares[i].rank() {
continue;
}
if squares[i].rank().flip_diagonal() > squares[i].file() {
for square in &mut squares[i..] {
*square = square.flip_diagonal();
}
}
break;
}
if self.num_unique_pieces > 2 {
let adjust1 = if squares[1] > squares[0] { 1 } else { 0 };
let adjust2 = if squares[2] > squares[0] { 1 } else { 0 } +
if squares[2] > squares[1] { 1 } else { 0 };
if offdiag(squares[0]) {
TRIANGLE[usize::from(squares[0])] * 63 * 62 +
(u64::from(squares[1]) - adjust1) * 62 +
(u64::from(squares[2]) - adjust2)
} else if offdiag(squares[1]) {
6 * 63 * 62 +
squares[0].rank() as u64 * 28 * 62 +
LOWER[usize::from(squares[1])] * 62 +
u64::from(squares[2]) - adjust2
} else if offdiag(squares[2]) {
6 * 63 * 62 + 4 * 28 * 62 +
squares[0].rank() as u64 * 7 * 28 +
(squares[1].rank() as u64 - adjust1) * 28 +
LOWER[usize::from(squares[2])]
} else {
6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 +
squares[0].rank() as u64 * 7 * 6 +
(squares[1].rank() as u64 - adjust1) * 6 +
(squares[2].rank() as u64 - adjust2)
}
} else if self.num_unique_pieces == 2 {
if S::CONNECTED_KINGS {
let adjust = if squares[1] > squares[0] { 1 } else { 0 };
if offdiag(squares[0]) {
TRIANGLE[usize::from(squares[0])] * 63 +
(u64::from(squares[1]) - adjust)
} else if offdiag(squares[1]) {
6 * 63 +
squares[0].rank() as u64 * 28 +
LOWER[usize::from(squares[1])]
} else {
6 * 63 + 4 * 28 +
squares[0].rank() as u64 * 7 +
(squares[1].rank() as u64 - adjust)
}
} else {
KK_IDX[TRIANGLE[usize::from(squares[0])] as usize][usize::from(squares[1])]
}
} else if self.min_like_man == 2 {
if TRIANGLE[usize::from(squares[0])] > TRIANGLE[usize::from(squares[1])] {
squares.swap(0, 1);
}
if squares[0].file() >= File::E {
for square in &mut squares {
*square = square.flip_horizontal();
}
}
if squares[0].rank() >= Rank::Fifth {
for square in &mut squares {
*square = square.flip_vertical();
}
}
if squares[0].rank().flip_diagonal() > squares[0].file() ||
(!offdiag(squares[0]) && squares[1].rank().flip_diagonal() > squares[1].file()) {
for square in &mut squares {
*square = square.flip_diagonal();
}
}
if TEST45.contains(squares[1]) && TRIANGLE[usize::from(squares[0])] == TRIANGLE[usize::from(squares[1])] {
squares.swap(0, 1);
for square in &mut squares {
*square = square.flip_vertical().flip_diagonal();
}
}
PP_IDX[TRIANGLE[usize::from(squares[0])] as usize][usize::from(squares[1])]
} else {
for i in 1..side.groups.lens[0] {
if TRIANGLE[usize::from(squares[0])] > TRIANGLE[usize::from(squares[i])] {
squares.swap(0, i);
}
}
if squares[0].file() >= File::E {
for square in &mut squares {
*square = square.flip_horizontal();
}
}
if squares[0].rank() >= Rank::Fifth {
for square in &mut squares {
*square = square.flip_vertical();
}
}
if squares[0].rank().flip_diagonal() > squares[0].file() {
for square in &mut squares {
*square = square.flip_diagonal();
}
}
for i in 1..side.groups.lens[0] {
for j in (i + 1)..side.groups.lens[0] {
if MULT_TWIST[usize::from(squares[i])] > MULT_TWIST[usize::from(squares[j])] {
squares.swap(i, j);
}
}
}
let mut idx = CONSTS.mult_idx[side.groups.lens[0] - 1][TRIANGLE[usize::from(squares[0])] as usize];
for i in 1..side.groups.lens[0] {
idx += binomial(MULT_TWIST[usize::from(squares[i])], i as u64);
}
idx
}
};
idx *= side.groups.factors[0];
let mut remaining_pawns = material.white.has_pawns() && material.black.has_pawns();
let mut next = 1;
let mut group_sq = side.groups.lens[0];
for lens in side.groups.lens.iter().cloned().skip(1) {
let (prev_squares, group_squares) = squares.split_at_mut(group_sq);
let group_squares = &mut group_squares[..lens];
group_squares.sort_unstable();
let mut n = 0;
for (i, &group_square) in group_squares.iter().enumerate().take(lens) {
let adjust = prev_squares[..group_sq].iter().filter(|sq| group_square > **sq).count() as u64;
n += binomial(u64::from(group_square) - adjust - if remaining_pawns { 8 } else { 0 }, i as u64 + 1);
}
remaining_pawns = false;
idx += n * side.groups.factors[next];
group_sq += side.groups.lens[next];
next += 1;
}
Ok(Some((side, idx)))
}
pub fn probe_wdl(&self, pos: &S) -> ProbeResult<Wdl> {
assert_eq!(T::METRIC, Metric::Wdl);
let (side, idx) = self.encode(pos)?.expect("wdl tables are two sided");
let decompressed = self.decompress_pairs(side, idx)?;
Ok(match decompressed {
0 => Wdl::Loss,
1 => Wdl::BlessedLoss,
2 => Wdl::Draw,
3 => Wdl::CursedWin,
4 => Wdl::Win,
_ => throw!(),
})
}
pub fn probe_dtz(&self, pos: &S, wdl: DecisiveWdl) -> ProbeResult<Option<Dtz>> {
assert_eq!(T::METRIC, Metric::Dtz);
let (side, idx) = match self.encode(pos)? {
Some(found) => found,
None => return Ok(None), };
let res = self.decompress_pairs(side, idx)?;
let res = i32::from(match side.dtz_map {
None => res,
Some(ref map) => map.read(&self.raf, wdl, res)?,
});
let stores_plies = match wdl {
DecisiveWdl::Win => side.flags.contains(Flag::WIN_PLIES),
DecisiveWdl::Loss => side.flags.contains(Flag::LOSS_PLIES),
DecisiveWdl::CursedWin | DecisiveWdl::BlessedLoss => false,
};
Ok(Some(Dtz(if stores_plies { res } else { 2 * res })))
}
}
fn open_table_file<P: AsRef<Path>>(path: P) -> ProbeResult<RandomAccessFile> {
let file = fs::File::open(path)?;
ensure!(file.metadata()?.len() % 64 == 16);
Ok(RandomAccessFile::try_new(file)?)
}
#[derive(Debug)]
pub struct WdlTable<S: Position + Syzygy, F: ReadAt> {
table: Table<WdlTag, S, F>,
}
impl<S: Position + Syzygy, F: ReadAt> WdlTable<S, F> {
pub fn new(raf: F, material: &Material) -> ProbeResult<WdlTable<S, F>> {
Table::new(raf, material).map(|table| WdlTable { table })
}
pub fn probe_wdl(&self, pos: &S) -> ProbeResult<Wdl> {
self.table.probe_wdl(pos)
}
}
impl<S: Position + Syzygy> WdlTable<S, RandomAccessFile> {
pub fn open<P: AsRef<Path>>(path: P, material: &Material) -> ProbeResult<WdlTable<S, RandomAccessFile>> {
WdlTable::new(open_table_file(path)?, material)
}
}
#[derive(Debug)]
pub struct DtzTable<S: Position + Syzygy, F: ReadAt> {
table: Table<DtzTag, S, F>,
}
impl<S: Position + Syzygy, F: ReadAt> DtzTable<S, F> {
pub fn new(raf: F, material: &Material) -> ProbeResult<DtzTable<S, F>> {
Table::new(raf, material).map(|table| DtzTable { table })
}
pub fn probe_dtz(&self, pos: &S, wdl: DecisiveWdl) -> ProbeResult<Option<Dtz>> {
self.table.probe_dtz(pos, wdl)
}
}
impl<S: Position + Syzygy> DtzTable<S, RandomAccessFile> {
pub fn open<P: AsRef<Path>>(path: P, material: &Material) -> ProbeResult<DtzTable<S, RandomAccessFile>> {
DtzTable::new(open_table_file(path)?, material)
}
}