From fdb2804883deb31e3aeb15bbe588dcc9b7b76bd0 Mon Sep 17 00:00:00 2001 From: Mica White Date: Mon, 8 Dec 2025 19:56:48 -0500 Subject: Stuff --- engine/src/eval.rs | 331 +++++++++++++++++++++++++++-------------------------- 1 file changed, 171 insertions(+), 160 deletions(-) mode change 100644 => 100755 engine/src/eval.rs (limited to 'engine/src/eval.rs') diff --git a/engine/src/eval.rs b/engine/src/eval.rs old mode 100644 new mode 100755 index 94849ce..a666913 --- a/engine/src/eval.rs +++ b/engine/src/eval.rs @@ -1,160 +1,171 @@ -use std::fmt::{self, Display}; -use std::ops::Neg; - -use model::CheckersBitBoard; - -const KING_WORTH: u32 = 2; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Evaluation(i16); - -impl Display for Evaluation { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.is_force_win() { - write!(f, "+M{}", self.force_sequence_length().unwrap()) - } else if self.is_force_loss() { - write!(f, "-M{}", self.force_sequence_length().unwrap()) - } else { - write!(f, "{:+}", self.to_f32().unwrap()) - } - } -} - -impl Neg for Evaluation { - type Output = Self; - - fn neg(self) -> Self::Output { - Self(-self.0) - } -} - -impl Evaluation { - pub(crate) const NULL_MAX: Self = Self(i16::MAX); - pub(crate) const NULL_MIN: Self = Self(i16::MIN + 1); - - pub const WIN: Self = Self(i16::MAX - 1); - pub const DRAW: Self = Self(0); - pub const LOSS: Self = Self(i16::MIN + 2); - - // last fourteen bits set to 1 - const FORCE_WIN_THRESHOLD: i16 = 0x3FFF; - - pub fn new(eval: f32) -> Self { - if eval >= 1.0 { - return Self::WIN; - } else if eval <= -1.0 { - return Self::LOSS; - } - - Self((eval * 16384.0) as i16) - } - - pub fn to_f32(self) -> Option { - if self.is_force_sequence() { - return None; - } - - Some(self.0 as f32 / 16384.0) - } - - pub fn is_force_win(self) -> bool { - self.0 > Self::FORCE_WIN_THRESHOLD - } - - pub fn is_force_loss(self) -> bool { - self.0 < -Self::FORCE_WIN_THRESHOLD - } - - pub fn is_force_sequence(self) -> bool { - self.is_force_win() || self.is_force_loss() - } - - pub fn force_sequence_length(self) -> Option { - if self == Self::NULL_MAX || self == Self::NULL_MIN { - return None; - } - - if self.is_force_win() { - Some((Self::WIN.0 - self.0) as u8) - } else if self.is_force_loss() { - Some((self.0 - Self::LOSS.0) as u8) - } else { - None - } - } - - pub fn increment(self) -> Self { - if self.is_force_win() { - Self(self.0 - 1) - } else if self.is_force_loss() { - Self(self.0 + 1) - } else { - self - } - } - - pub fn add_f32(self, rhs: f32) -> Self { - let Some(eval) = self.to_f32() else { - return self; - }; - - Self::new(eval + rhs) - } -} - -pub fn eval_position(board: CheckersBitBoard) -> Evaluation { - let light_pieces = board.pieces_bits() & !board.color_bits(); - let dark_pieces = board.pieces_bits() & board.color_bits(); - - let light_peasants = light_pieces & !board.king_bits(); - let dark_peasants = dark_pieces & !board.king_bits(); - - let light_kings = light_pieces & board.king_bits(); - let dark_kings = dark_pieces & board.king_bits(); - - // if we assume the black player doesn't exist, how good is this for white? - let light_eval = - (light_peasants.count_ones() as f32) + ((light_kings.count_ones() * KING_WORTH) as f32); - let dark_eval = - (dark_peasants.count_ones() as f32) + ((dark_kings.count_ones() * KING_WORTH) as f32); - - // avoiding a divide by zero error - if dark_eval + light_eval != 0.0 { - Evaluation::new((dark_eval - light_eval) / (dark_eval + light_eval)) - } else { - Evaluation::DRAW - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn zero_eval() { - let draw = Evaluation::new(0.0); - assert_eq!(draw, Evaluation::DRAW); - assert_eq!(draw.to_f32(), Some(0.0)); - assert_eq!(draw.to_string(), "+0"); - } - - #[test] - fn comparisons() { - assert!(Evaluation::NULL_MAX > Evaluation::WIN); - assert!(Evaluation::WIN > Evaluation::new(0.5)); - assert!(Evaluation::new(0.5) > Evaluation::DRAW); - assert!(Evaluation::DRAW > Evaluation::new(-0.5)); - assert!(Evaluation::new(-0.5) > Evaluation::LOSS); - assert!(Evaluation::LOSS > Evaluation::NULL_MIN); - } - - #[test] - fn negations() { - assert_eq!(-Evaluation::NULL_MAX, Evaluation::NULL_MIN); - assert_eq!(-Evaluation::NULL_MIN, Evaluation::NULL_MAX); - assert_eq!(-Evaluation::WIN, Evaluation::LOSS); - assert_eq!(-Evaluation::LOSS, Evaluation::WIN); - assert_eq!(-Evaluation::DRAW, Evaluation::DRAW); - assert_eq!(-Evaluation::new(0.5), Evaluation::new(-0.5)); - } -} +use std::fmt::{self, Display}; +use std::ops::Neg; + +use model::CheckersBitBoard; + +const KING_WORTH: u32 = 2; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Evaluation(i16); + +impl Display for Evaluation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_force_win() { + write!(f, "+W{}", self.force_sequence_length().unwrap()) + } else if self.is_force_loss() { + write!(f, "-W{}", self.force_sequence_length().unwrap()) + } else { + write!(f, "{:+}", self.to_f32().unwrap()) + } + } +} + +impl Neg for Evaluation { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(-self.0) + } +} + +impl Evaluation { + pub(crate) const NULL_MAX: Self = Self(i16::MAX); + pub(crate) const NULL_MIN: Self = Self(i16::MIN + 1); + + pub const WIN: Self = Self(i16::MAX - 1); + pub const DRAW: Self = Self(0); + pub const LOSS: Self = Self(i16::MIN + 2); + + // last fourteen bits set to 1 + const FORCE_WIN_THRESHOLD: i16 = 0x3FFF; + // divisor for converting to a float + const MAX_FLOAT: f32 = 16384.0; + + pub fn new(eval: f32) -> Self { + if eval >= 1.0 { + return Self::WIN; + } else if eval <= -1.0 { + return Self::LOSS; + } + + Self((eval * 16384.0) as i16) + } + + pub fn to_f32(self) -> Option { + if self.is_force_sequence() { + return None; + } + + Some(self.0 as f32 / Self::MAX_FLOAT) + } + + /// Converts to an `f32` without checking to see if the game if a force + /// sequence. + /// + /// # Safety + /// Results in undefined behavior if the evaluation is a force sequence + pub unsafe fn to_f32_unchecked(self) -> f32 { + self.0 as f32 / Self::MAX_FLOAT + } + + pub fn is_force_win(self) -> bool { + self.0 > Self::FORCE_WIN_THRESHOLD + } + + pub fn is_force_loss(self) -> bool { + self.0 < -Self::FORCE_WIN_THRESHOLD + } + + pub fn is_force_sequence(self) -> bool { + self.is_force_win() || self.is_force_loss() + } + + pub fn force_sequence_length(self) -> Option { + if self == Self::NULL_MAX || self == Self::NULL_MIN { + return None; + } + + if self.is_force_win() { + Some((Self::WIN.0 - self.0) as u8) + } else if self.is_force_loss() { + Some((self.0 - Self::LOSS.0) as u8) + } else { + None + } + } + + pub fn increment(self) -> Self { + if self.is_force_win() { + Self(self.0 - 1) + } else if self.is_force_loss() { + Self(self.0 + 1) + } else { + self + } + } + + pub fn add_f32(self, rhs: f32) -> Self { + let Some(eval) = self.to_f32() else { + return self; + }; + + Self::new(eval + rhs) + } +} + +pub fn eval_position(board: CheckersBitBoard) -> Evaluation { + let light_pieces = board.pieces_bits() & !board.color_bits(); + let dark_pieces = board.pieces_bits() & board.color_bits(); + + let light_peasants = light_pieces & !board.king_bits(); + let dark_peasants = dark_pieces & !board.king_bits(); + + let light_kings = light_pieces & board.king_bits(); + let dark_kings = dark_pieces & board.king_bits(); + + // if we assume the black player doesn't exist, how good is this for white? + let light_eval = + (light_peasants.count_ones() as f32) + ((light_kings.count_ones() * KING_WORTH) as f32); + let dark_eval = + (dark_peasants.count_ones() as f32) + ((dark_kings.count_ones() * KING_WORTH) as f32); + + // avoiding a divide by zero error + if dark_eval + light_eval != 0.0 { + Evaluation::new((dark_eval - light_eval) / (dark_eval + light_eval)) + } else { + Evaluation::DRAW + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn zero_eval() { + let draw = Evaluation::new(0.0); + assert_eq!(draw, Evaluation::DRAW); + assert_eq!(draw.to_f32(), Some(0.0)); + assert_eq!(draw.to_string(), "+0"); + } + + #[test] + fn comparisons() { + assert!(Evaluation::NULL_MAX > Evaluation::WIN); + assert!(Evaluation::WIN > Evaluation::new(0.5)); + assert!(Evaluation::new(0.5) > Evaluation::DRAW); + assert!(Evaluation::DRAW > Evaluation::new(-0.5)); + assert!(Evaluation::new(-0.5) > Evaluation::LOSS); + assert!(Evaluation::LOSS > Evaluation::NULL_MIN); + } + + #[test] + fn negations() { + assert_eq!(-Evaluation::NULL_MAX, Evaluation::NULL_MIN); + assert_eq!(-Evaluation::NULL_MIN, Evaluation::NULL_MAX); + assert_eq!(-Evaluation::WIN, Evaluation::LOSS); + assert_eq!(-Evaluation::LOSS, Evaluation::WIN); + assert_eq!(-Evaluation::DRAW, Evaluation::DRAW); + assert_eq!(-Evaluation::new(0.5), Evaluation::new(-0.5)); + } +} -- cgit v1.2.3