timecat 1.52.0

A NNUE-based chess engine that implements the Negamax algorithm and can be integrated into any project as a library. It features move generation, advanced position evaluation through NNUE, and move searching capabilities.
Documentation
use super::*;

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)]
pub enum CastleRights {
    None = 0,
    KingSide = 1,
    QueenSide = 2,
    Both = 3,
}

impl CastleRights {
    /// Can I castle kingside?
    #[inline]
    pub const fn has_kingside(self) -> bool {
        self.to_index() & 1 == 1
    }

    /// Can I castle queenside?
    #[inline]
    pub const fn has_queenside(self) -> bool {
        self.to_index() & 2 == 2
    }

    #[inline]
    pub fn square_to_castle_rights(color: Color, square: Square) -> Self {
        *get_item_unchecked!(
            const {
                let mut array = [[Self::None; 64]; 2];
                array[0][63] = Self::KingSide;
                array[1][7] = Self::KingSide;
                array[0][56] = Self::QueenSide;
                array[1][0] = Self::QueenSide;
                array[0][60] = Self::Both;
                array[1][4] = Self::Both;
                array
            },
            color.to_index(),
            square.to_index()
        )
    }

    /// What squares need to be empty to castle kingside?
    #[inline]
    pub fn kingside_squares(self, color: Color) -> BitBoard {
        *get_item_unchecked!(
            const { [BitBoard::new(6917529027641081856), BitBoard::new(96)] },
            color.to_index(),
        )
    }

    /// What squares need to be empty to castle queenside?
    #[inline]
    pub fn queenside_squares(self, color: Color) -> BitBoard {
        *get_item_unchecked!(
            const { [BitBoard::new(1008806316530991104), BitBoard::new(14)] },
            color.to_index(),
        )
    }

    /// Remove castle rights, and return a new `CastleRights`.
    #[inline]
    pub fn remove(self, remove: Self) -> Self {
        unsafe { Self::from_int(self.to_int() & !remove.to_int()) }
    }

    /// Convert `CastleRights` to `u8` for table lookups
    #[inline]
    pub const fn to_int(self) -> u8 {
        self as u8
    }

    /// Convert `CastleRights` to `usize` for table lookups
    #[inline]
    pub const fn to_index(self) -> usize {
        self as usize
    }

    /// Convert `usize` to `CastleRights`.  Panic if invalid number.
    #[inline]
    pub const unsafe fn from_int(i: u8) -> Self {
        std::mem::transmute(i)
    }

    /// Convert `usize` to `CastleRights`.  Panic if invalid number.
    #[inline]
    pub const unsafe fn from_index(i: usize) -> Self {
        Self::from_int(i as u8)
    }

    /// Which rooks can we "guarantee" we haven't moved yet?
    #[inline]
    pub fn unmoved_rooks(self, color: Color) -> BitBoard {
        // TODO: match can be removed.
        match self {
            Self::None => BitBoard::EMPTY,
            Self::KingSide => BitBoard::from_rank_and_file(color.to_my_backrank(), File::H),
            Self::QueenSide => BitBoard::from_rank_and_file(color.to_my_backrank(), File::A),
            Self::Both => {
                let my_backrank = color.to_my_backrank();
                BitBoard::from_rank_and_file(my_backrank, File::A)
                    ^ BitBoard::from_rank_and_file(my_backrank, File::H)
            }
        }
    }

    #[inline]
    pub fn to_string(self, color: Color) -> &'static str {
        get_item_unchecked!(
            const { ["", "k", "q", "kq", "", "K", "Q", "KQ"] },
            color.to_index() << 2 | self.to_index()
        )
    }
}

impl Add for CastleRights {
    type Output = Self;

    #[expect(clippy::suspicious_arithmetic_impl)]
    #[inline]
    fn add(self, rhs: Self) -> Self::Output {
        unsafe { Self::from_int(self.to_int() | rhs.to_int()) }
    }
}

impl AddAssign for CastleRights {
    #[inline]
    fn add_assign(&mut self, rhs: Self) {
        *self = *self + rhs;
    }
}

impl Sub for CastleRights {
    type Output = Self;

    #[inline]
    fn sub(self, rhs: Self) -> Self::Output {
        self.remove(rhs)
    }
}

impl SubAssign for CastleRights {
    #[inline]
    fn sub_assign(&mut self, rhs: Self) {
        *self = *self - rhs;
    }
}

impl TryFrom<char> for CastleRights {
    type Error = TimecatError;

    fn try_from(value: char) -> Result<Self> {
        match value {
            'K' | 'k' => Ok(Self::KingSide),
            'Q' | 'q' => Ok(Self::QueenSide),
            _ => Err(TimecatError::InvalidCastleRightsString {
                s: value.to_string().into(),
            }),
        }
    }
}

impl FromStr for CastleRights {
    type Err = TimecatError;

    fn from_str(s: &str) -> Result<Self> {
        match s.trim() {
            "" => Ok(Self::None),
            "K" | "k" => Ok(Self::KingSide),
            "Q" | "q" => Ok(Self::QueenSide),
            "KQ" | "kq" | "QK" | "qk" => Ok(Self::Both),
            _ => Err(TimecatError::InvalidCastleRightsString {
                s: s.to_string().into(),
            }),
        }
    }
}