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::*;
use TimecatError::*;

#[cfg(feature = "pyo3")]
#[derive(Debug)]
pub enum Pyo3Error {
    #[cfg(feature = "pyo3")]
    Pyo3TypeConversionError {
        from: Cow<'static, str>,
        to: Cow<'static, str>,
    },
}

#[cfg(feature = "pyo3")]
impl From<Pyo3Error> for PyErr {
    fn from(err: Pyo3Error) -> Self {
        match err {
            Pyo3Error::Pyo3TypeConversionError { from, to } => {
                pyo3::exceptions::PyTypeError::new_err(format!(
                    "Failed to convert {from} into {to}"
                ))
            }
        }
    }
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub enum TimecatError {
    UnknownCommand,
    NoInput,
    NotImplemented,
    EngineNotRunning,
    BadFen {
        fen: Cow<'static, str>,
    },
    InvalidDepth {
        depth: Depth,
    },
    IllegalMove {
        valid_or_null_move: ValidOrNullMove,
        board_fen: Cow<'static, str>,
    },
    ColoredOutputUnchanged {
        b: bool,
    },
    UCIModeUnchanged,
    ConsoleModeUnchanged,
    EmptyStack,
    BestMoveNotFound {
        fen: Cow<'static, str>,
    },
    NullMoveInCheck {
        fen: Cow<'static, str>,
    },
    WTimeNotMentioned,
    BTimeNotMentioned,
    GameAlreadyOver,
    UnknownDebugCommand {
        command: Cow<'static, str>,
    },
    InvalidSpinValue {
        name: Cow<'static, str>,
        value: Spin,
        min: Spin,
        max: Spin,
    },
    SameSourceAndDestination {
        move_: Move,
    },
    InvalidPromotion {
        move_: Move,
    },
    InvalidSanOrLanMove {
        valid_or_null_move: ValidOrNullMove,
        fen: Cow<'static, str>,
    },
    InvalidSanMoveString {
        s: Cow<'static, str>,
    },
    InvalidLanMoveString {
        s: Cow<'static, str>,
    },
    InvalidMoveString {
        s: Cow<'static, str>,
    },
    InvalidRankString {
        s: Cow<'static, str>,
    },
    InvalidFileString {
        s: Cow<'static, str>,
    },
    InvalidColorString {
        s: Cow<'static, str>,
    },
    InvalidCastleRightsString {
        s: Cow<'static, str>,
    },
    InvalidSquareString {
        s: Cow<'static, str>,
    },
    InvalidPieceTypeString {
        s: Cow<'static, str>,
    },
    InvalidPieceString {
        s: Cow<'static, str>,
    },
    InvalidUciMoveString {
        s: Cow<'static, str>,
    },
    InvalidBoardPosition {
        position: Box<ChessPosition>,
    },
    InvalidGoCommand {
        s: Cow<'static, str>,
    },
    IllegalSearchMoves {
        illegal_moves: Vec<Move>,
    },
    FeatureNotEnabled {
        s: Cow<'static, str>,
    },
    BadNNUEFile,
    BadPolyglotFile,
    PolyglotTableParseError,
    DecompressionFailed {
        value: Cow<'static, str>,
        type_name: Cow<'static, str>,
    },
    CustomError {
        err_msg: Cow<'static, str>,
    },
}

impl TimecatError {
    pub fn get_custom_error<E: Error>(error: E) -> Self {
        Self::CustomError {
            err_msg: format!("{error}! Please try again!").into(),
        }
    }
}

impl fmt::Display for TimecatError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            UnknownCommand => write!(
                f,
                "{}",
                UnknownCommand.stringify_with_optional_raw_input(None)
            ),
            NoInput => write!(f, "No input! Please try again!"),
            NotImplemented => write!(f, "Sorry, this command is not implemented yet :("),
            EngineNotRunning => write!(f, "Engine is not running! Please try again!"),
            BadFen { fen } => write!(f, "Bad FEN string: {fen}! Please try Again!"),
            InvalidDepth { depth } => write!(f, "Invalid depth {depth}! Please try again!"),
            IllegalMove {
                valid_or_null_move,
                board_fen,
            } => write!(
                f,
                "Illegal move {valid_or_null_move} in position {board_fen}! Please try again!"
            ),
            ColoredOutputUnchanged { b } => {
                write!(f, "Colored output already set to {b}! Please try again!")
            }
            UCIModeUnchanged => write!(f, "Already in UCI Mode! Please try again!"),
            ConsoleModeUnchanged => write!(f, "Already in Console Mode! Please try again!"),
            EmptyStack => write!(
                f,
                "Move Stack is empty, pop not possible! Please try again!"
            ),
            BestMoveNotFound { fen } => write!(
                f,
                "Best move not found in position {fen}! Please try again!"
            ),
            NullMoveInCheck { fen } => write!(
                f,
                "Cannot apply null move in position {fen}, as king is in check! Please try again!"
            ),
            WTimeNotMentioned => write!(f, "You didn't mention wtime! Please try again!"),
            BTimeNotMentioned => write!(f, "You didn't mention btime! Please try again!"),
            GameAlreadyOver => write!(
                f,
                "Game is already over! Please start a game from another position!"
            ),
            UnknownDebugCommand { command } => write!(
                f,
                "Debug command {command} is unknown! The possible commands are on or off! Please try again!"
            ),
            InvalidSpinValue {
                name,
                value,
                min,
                max,
            } => write!(
                f,
                "Cannot set value of {name} to {value}, the value must be from {min} to {max}! Please try again!"
            ),
            SameSourceAndDestination { move_ } => {
                write!(
                    f,
                    "The source and destination squares of the move {} cannot be the same!",
                    move_
                )
            }
            InvalidPromotion { move_ } => {
                write!(f, "Invalid promotion for the move {}!", move_)
            }
            InvalidSanOrLanMove {
                valid_or_null_move,
                fen,
            } => write!(
                f,
                "san() and lan() expect move to be legal or null, but got {} in {}",
                valid_or_null_move, fen
            ),
            InvalidSanMoveString { s } => {
                write!(f, "Got invalid SAN move string {s}! Please try again!")
            }
            InvalidLanMoveString { s } => {
                write!(f, "Got invalid LAN move string {s}! Please try again!")
            }
            InvalidMoveString { s } => write!(f, "Got invalid move string {s}! Please try again!"),
            InvalidRankString { s } => write!(f, "Got invalid rank string {s}! Please try again!"),
            InvalidFileString { s } => write!(f, "Got invalid file string {s}! Please try again!"),
            InvalidColorString { s } => {
                write!(f, "Got invalid color string {s}! Please try again!")
            }
            InvalidCastleRightsString { s } => {
                write!(f, "Got invalid castle rights string {s}! Please try again!")
            }
            InvalidSquareString { s } => {
                write!(f, "Got invalid square string {s}! Please try again!")
            }
            InvalidPieceTypeString { s } => {
                write!(f, "Got invalid piece type string {s}! Please try again!")
            }
            InvalidPieceString { s } => {
                write!(f, "Got invalid piece string {s}! Please try again!")
            }
            InvalidUciMoveString { s } => {
                write!(f, "Invalid uci move string {s}! Please try again!")
            }
            InvalidBoardPosition { position } => {
                write!(f, "Invalid position generated:\n\n{position:#?}")
            }
            InvalidGoCommand { s } => write!(f, "Got invalid go command: {s:?}! Please try again!"),
            IllegalSearchMoves { illegal_moves } => write!(
                f,
                "Got illegal search moves: {}! Please try again!",
                illegal_moves.iter().map(ToString::to_string).join(", ")
            ),
            FeatureNotEnabled { s } => write!(
                f,
                "The feature {s:?} is not enabled. Please recompile the chess engine with this feature enabled!"
            ),
            BadNNUEFile => write!(
                f,
                "The NNUE file cannot be parsed properly! Try again with a different NNUE file!"
            ),
            BadPolyglotFile => write!(
                f,
                "The Polyglot file cannot be parsed properly! Try again with a different Polyglot file!"
            ),
            PolyglotTableParseError => write!(
                f,
                "The Polyglot Table cannot be parsed properly! Try again with a different Polyglot file!"
            ),
            DecompressionFailed { value, type_name } => write!(
                f,
                "Failed to decompress value {} into {:?}",
                value, type_name
            ),
            CustomError { err_msg } => write!(f, "{err_msg}"),
        }
    }
}

impl Error for TimecatError {}

impl TimecatError {
    pub fn stringify_with_optional_raw_input(&self, optional_raw_input: Option<&str>) -> String {
        match self {
            Self::UnknownCommand => {
                let command_type = if GLOBAL_TIMECAT_STATE.is_in_console_mode() {
                    "Console"
                } else {
                    "UCI"
                };
                optional_raw_input.map_or_else(
                    || format!("Unknown {command_type} Command!\nPlease try again!"),
                    |raw_input| {
                        format!(
                            "Unknown {command_type} Command: {:?}\nType help for more information!",
                            raw_input.trim_end_matches('\n')
                        )
                    },
                )
            }
            other_err => other_err.to_string(),
        }
    }
}

impl From<TimecatError> for String {
    fn from(error: TimecatError) -> Self {
        error.stringify().into()
    }
}

impl From<&Self> for TimecatError {
    fn from(error: &Self) -> Self {
        error.clone()
    }
}

impl From<ParseBoolError> for TimecatError {
    fn from(error: ParseBoolError) -> Self {
        CustomError {
            err_msg: format!("Failed to parse bool, {error}! Please try again!").into(),
        }
    }
}

impl From<ParseIntError> for TimecatError {
    fn from(error: ParseIntError) -> Self {
        CustomError {
            err_msg: format!("Failed to parse integer, {error}! Please try again!").into(),
        }
    }
}

macro_rules! impl_error_convert {
    ($class:ty) => {
        impl From<$class> for TimecatError {
            fn from(error: $class) -> Self {
                Self::get_custom_error(error)
            }
        }
    };
}

impl_error_convert!(std::io::Error);
impl_error_convert!(std::array::TryFromSliceError);

impl From<String> for TimecatError {
    fn from(err_msg: String) -> Self {
        Cow::from(err_msg).into()
    }
}

impl From<&'static str> for TimecatError {
    fn from(err_msg: &'static str) -> Self {
        Cow::from(err_msg).into()
    }
}

impl From<Cow<'static, str>> for TimecatError {
    fn from(err_msg: Cow<'static, str>) -> Self {
        CustomError { err_msg }
    }
}

#[cfg(feature = "pyo3")]
impl From<TimecatError> for PyErr {
    fn from(err: TimecatError) -> Self {
        pyo3::exceptions::PyRuntimeError::new_err(format!("TimecatError occurred: {:?}", err))
    }
}