use super::{GENERATION_CYCLE, GENERATION_MASK};
use crate::types::{Bound, Move, Value, DEPTH_ENTRY_OFFSET};
#[derive(Clone, Copy, Default)]
#[repr(C, packed)]
pub struct TTEntry {
key16: u16,
depth8: u8,
gen_bound8: u8,
move16: u16,
value16: i16,
eval16: i16,
}
const _: () = assert!(std::mem::size_of::<TTEntry>() == 10);
impl TTEntry {
#[inline]
pub const fn new() -> Self {
Self {
key16: 0,
depth8: 0,
gen_bound8: 0,
move16: 0,
value16: 0,
eval16: 0,
}
}
#[inline]
pub fn is_occupied(&self) -> bool {
self.depth8 != 0
}
#[inline]
pub fn key16(&self) -> u16 {
self.key16
}
#[inline]
pub fn depth(&self) -> i32 {
self.depth8 as i32 + DEPTH_ENTRY_OFFSET
}
#[inline]
pub fn depth8(&self) -> u8 {
self.depth8
}
pub fn read(&self) -> TTData {
let mv = Move::from_u16_checked(self.move16).unwrap_or(Move::NONE);
TTData {
mv,
value: Value::new(self.value16 as i32),
eval: Value::new(self.eval16 as i32),
depth: self.depth8 as i32 + DEPTH_ENTRY_OFFSET,
bound: Bound::from_u8(self.gen_bound8 & 0x3).unwrap_or(Bound::None),
is_pv: (self.gen_bound8 & 0x4) != 0,
}
}
pub fn save(
&mut self,
key16: u16,
value: Value,
is_pv: bool,
bound: Bound,
depth: i32,
mv: Move,
eval: Value,
generation8: u8,
) {
if mv != Move::NONE || key16 != self.key16 {
self.move16 = mv.to_u16();
}
let d8 = depth - DEPTH_ENTRY_OFFSET;
if bound == Bound::Exact
|| key16 != self.key16
|| d8 + 2 * (is_pv as i32) > self.depth8 as i32 - 4
|| self.relative_age(generation8) != 0
{
debug_assert!(d8 > 0 && d8 < 256);
self.key16 = key16;
self.depth8 = d8 as u8;
self.gen_bound8 = generation8 | ((is_pv as u8) << 2) | bound as u8;
self.value16 = value.raw() as i16;
self.eval16 = eval.raw() as i16;
} else if self.depth8 as i32 + DEPTH_ENTRY_OFFSET >= 5
&& Bound::from_u8(self.gen_bound8 & 0x3) != Some(Bound::Exact)
{
self.depth8 = self.depth8.saturating_sub(1);
}
}
#[inline]
pub fn relative_age(&self, generation8: u8) -> u8 {
let age = GENERATION_CYCLE
.wrapping_add(generation8 as u16)
.wrapping_sub(self.gen_bound8 as u16);
(age & GENERATION_MASK) as u8
}
}
#[derive(Clone, Copy, Debug)]
pub struct TTData {
pub mv: Move,
pub value: Value,
pub eval: Value,
pub depth: i32,
pub bound: Bound,
pub is_pv: bool,
}
impl TTData {
pub const EMPTY: Self = Self {
mv: Move::NONE,
value: Value::NONE,
eval: Value::NONE,
depth: DEPTH_ENTRY_OFFSET,
bound: Bound::None,
is_pv: false,
};
}
impl Default for TTData {
fn default() -> Self {
Self::EMPTY
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{File, Rank, Square};
#[test]
fn test_tt_entry_new() {
let entry = TTEntry::new();
assert!(!entry.is_occupied());
assert_eq!(entry.key16(), 0);
}
#[test]
fn test_tt_entry_save_and_read() {
let mut entry = TTEntry::new();
let key = 0x1234u16;
let value = Value::new(100);
let eval = Value::new(-50);
let depth = 10;
let from = Square::new(File::File7, Rank::Rank7);
let to = Square::new(File::File7, Rank::Rank6);
let mv = Move::new_move(from, to, false);
let bound = Bound::Exact;
let is_pv = true;
let gen = 8;
entry.save(key, value, is_pv, bound, depth, mv, eval, gen);
assert!(entry.is_occupied());
assert_eq!(entry.key16(), key);
let data = entry.read();
assert_eq!(data.value.raw(), 100);
assert_eq!(data.eval.raw(), -50);
assert_eq!(data.depth, 10);
assert_eq!(data.bound, Bound::Exact);
assert!(data.is_pv);
}
#[test]
fn test_tt_entry_relative_age() {
let mut entry = TTEntry::new();
entry.save(0, Value::ZERO, false, Bound::Lower, 10, Move::NONE, Value::ZERO, 8);
assert_eq!(entry.relative_age(8), 0);
assert_eq!(entry.relative_age(16), 8);
}
#[test]
fn test_tt_entry_decay_non_exact() {
let mut entry = TTEntry::new();
let key = 0x1234u16;
entry.save(key, Value::ZERO, false, Bound::Lower, 8, Move::NONE, Value::ZERO, 0);
let depth_before = entry.depth8();
entry.save(key, Value::ZERO, false, Bound::Lower, 1, Move::NONE, Value::ZERO, 0);
assert_eq!(entry.depth8(), depth_before - 1);
}
#[test]
fn test_tt_data_empty() {
let data = TTData::EMPTY;
assert_eq!(data.mv, Move::NONE);
assert_eq!(data.bound, Bound::None);
assert!(!data.is_pv);
}
}