use chess::Board;
use ndarray::Array1;
use crate::similarity_search::{SimilaritySearch, SearchResult};
use crate::tactical_search::{TacticalSearch, TacticalConfig};
use crate::position_encoder::PositionEncoder;
use std::collections::HashMap;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct CacheStats {
pub position_cache_size: usize,
pub evaluation_cache_size: usize,
pub max_cache_size: usize,
pub cache_ttl_secs: u64,
}
pub struct CoreEvaluator {
tactical_evaluator: TacticalSearch,
pub similarity_engine: SimilarityEngine,
strategic_analyzer: StrategicAnalyzer,
evaluation_blender: EvaluationBlender,
position_encoder: PositionEncoder,
position_cache: HashMap<String, (Array1<f32>, Instant)>,
evaluation_cache: HashMap<String, (CoreEvaluationResult, Instant)>,
max_cache_size: usize,
cache_ttl: Duration,
}
impl CoreEvaluator {
pub fn new() -> Self {
Self {
tactical_evaluator: TacticalSearch::new(TacticalConfig::default()),
similarity_engine: SimilarityEngine::new(),
strategic_analyzer: StrategicAnalyzer::new(),
evaluation_blender: EvaluationBlender::new(),
position_encoder: PositionEncoder::new(1024),
position_cache: HashMap::with_capacity(1000),
evaluation_cache: HashMap::with_capacity(500),
max_cache_size: 1000,
cache_ttl: Duration::from_secs(300), }
}
pub fn new_with_cache_config(max_cache_size: usize, cache_ttl_secs: u64) -> Self {
Self {
tactical_evaluator: TacticalSearch::new(TacticalConfig::default()),
similarity_engine: SimilarityEngine::new(),
strategic_analyzer: StrategicAnalyzer::new(),
evaluation_blender: EvaluationBlender::new(),
position_encoder: PositionEncoder::new(1024),
position_cache: HashMap::with_capacity(max_cache_size),
evaluation_cache: HashMap::with_capacity(max_cache_size / 2),
max_cache_size,
cache_ttl: Duration::from_secs(cache_ttl_secs),
}
}
pub fn evaluate_position(&mut self, board: &Board) -> CoreEvaluationResult {
let fen = board.to_string();
let now = Instant::now();
if let Some((cached_result, cached_time)) = self.evaluation_cache.get(&fen) {
if now.duration_since(*cached_time) < self.cache_ttl {
return cached_result.clone();
}
}
self.evict_expired_cache_entries(now);
let tactical_result = self.tactical_evaluator.search(board);
let tactical_eval = tactical_result.evaluation;
let position_vector = self.get_cached_position_vector(board, &fen, now);
let similarity_insights = self.similarity_engine.find_strategic_insights_with_vector(&position_vector);
let strategic_insights = if similarity_insights.confidence > 0.8 {
self.strategic_analyzer.analyze_initiative_fast(board)
} else {
self.strategic_analyzer.analyze_initiative(board)
};
let final_evaluation = self.evaluation_blender.blend_all(
tactical_eval,
&similarity_insights,
&strategic_insights,
);
let unique_insights_provided = self.provides_unique_insights(&similarity_insights, &strategic_insights);
let result = CoreEvaluationResult {
final_evaluation,
tactical_component: tactical_eval,
similarity_insights,
strategic_insights,
unique_insights_provided,
};
self.evaluation_cache.insert(fen, (result.clone(), now));
result
}
pub fn learn_from_position(&mut self, board: &Board, evaluation: f32) {
let fen = board.to_string();
let now = Instant::now();
let position_vector = self.get_cached_position_vector(board, &fen, now);
self.similarity_engine.add_position_with_vector(position_vector, evaluation);
}
fn get_cached_position_vector(&mut self, board: &Board, fen: &str, now: Instant) -> Array1<f32> {
if let Some((cached_vector, cached_time)) = self.position_cache.get(fen) {
if now.duration_since(*cached_time) < self.cache_ttl {
return cached_vector.clone();
}
}
let position_vector = self.position_encoder.encode(board);
self.position_cache.insert(fen.to_string(), (position_vector.clone(), now));
if self.position_cache.len() > self.max_cache_size {
self.evict_oldest_position_cache_entry();
}
position_vector
}
fn evict_expired_cache_entries(&mut self, now: Instant) {
self.evaluation_cache.retain(|_, (_, cached_time)| {
now.duration_since(*cached_time) < self.cache_ttl
});
self.position_cache.retain(|_, (_, cached_time)| {
now.duration_since(*cached_time) < self.cache_ttl
});
}
fn evict_oldest_position_cache_entry(&mut self) {
if let Some(oldest_key) = self.position_cache
.iter()
.min_by_key(|(_, (_, time))| *time)
.map(|(key, _)| key.clone()) {
self.position_cache.remove(&oldest_key);
}
}
pub fn get_cache_stats(&self) -> CacheStats {
CacheStats {
position_cache_size: self.position_cache.len(),
evaluation_cache_size: self.evaluation_cache.len(),
max_cache_size: self.max_cache_size,
cache_ttl_secs: self.cache_ttl.as_secs(),
}
}
pub fn clear_caches(&mut self) {
self.position_cache.clear();
self.evaluation_cache.clear();
}
fn provides_unique_insights(
&self,
similarity: &SimilarityInsights,
strategic: &StrategicInsights,
) -> bool {
!similarity.similar_positions.is_empty() || strategic.initiative_advantage.abs() > 0.1
}
}
#[derive(Debug, Clone)]
pub struct CoreEvaluationResult {
pub final_evaluation: f32,
pub tactical_component: f32,
pub similarity_insights: SimilarityInsights,
pub strategic_insights: StrategicInsights,
pub unique_insights_provided: bool,
}
pub struct SimilarityEngine {
similarity_search: SimilaritySearch,
position_database: Vec<(Array1<f32>, f32)>, }
impl SimilarityEngine {
pub fn new() -> Self {
Self {
similarity_search: SimilaritySearch::new(1024),
position_database: Vec::new(),
}
}
pub fn add_position(&mut self, board: &Board, evaluation: f32) {
let vector = self.encode_position(board);
self.add_position_with_vector(vector, evaluation);
}
pub fn add_position_with_vector(&mut self, vector: Array1<f32>, evaluation: f32) {
self.position_database.push((vector.clone(), evaluation));
self.similarity_search.add_position(vector, evaluation);
}
pub fn find_strategic_insights(&self, board: &Board) -> SimilarityInsights {
let query_vector = self.encode_position(board);
self.find_strategic_insights_with_vector(&query_vector)
}
pub fn find_strategic_insights_with_vector(&self, query_vector: &Array1<f32>) -> SimilarityInsights {
let raw_results = self.similarity_search.search_optimized(query_vector, 3);
let similar_positions: Vec<SearchResult> = raw_results
.into_iter()
.map(|(vector, evaluation, similarity)| SearchResult {
vector,
evaluation,
similarity,
})
.collect();
let average_evaluation = if !similar_positions.is_empty() {
similar_positions.iter().map(|s| s.evaluation).sum::<f32>() / similar_positions.len() as f32
} else {
0.0
};
let confidence = self.calculate_confidence_from_results(&similar_positions);
SimilarityInsights {
similar_positions,
suggested_evaluation: average_evaluation,
confidence,
}
}
fn encode_position(&self, board: &Board) -> Array1<f32> {
let mut vector = Array1::zeros(1024);
let piece_values = [1.0, 3.0, 3.0, 5.0, 9.0, 0.0];
let mut material_index = 0;
for square in chess::ALL_SQUARES {
if let Some(piece) = board.piece_on(square) {
let value = piece_values[piece as usize];
if material_index < 1024 {
if board.color_on(square) == Some(chess::Color::White) {
vector[material_index] = value;
} else {
vector[material_index] = -value;
}
material_index += 1;
}
}
}
vector
}
fn calculate_confidence_from_results(&self, similar_positions: &[SearchResult]) -> f32 {
if similar_positions.is_empty() {
0.0
} else {
similar_positions.iter().map(|s| s.similarity).sum::<f32>() / similar_positions.len() as f32
}
}
}
pub struct StrategicAnalyzer {
initiative_factors: InitiativeFactors,
}
impl StrategicAnalyzer {
pub fn new() -> Self {
Self {
initiative_factors: InitiativeFactors::default(),
}
}
pub fn analyze_initiative(&self, board: &Board) -> StrategicInsights {
let development_advantage = self.calculate_development_advantage(board);
let center_control = self.calculate_center_control(board);
let piece_activity = self.calculate_piece_activity(board);
let initiative_advantage = (development_advantage + center_control + piece_activity) / 3.0;
StrategicInsights {
initiative_advantage,
development_advantage,
center_control,
piece_activity,
strategic_recommendation: self.generate_recommendation(initiative_advantage),
}
}
pub fn analyze_initiative_fast(&self, board: &Board) -> StrategicInsights {
let development_advantage = self.calculate_development_advantage(board);
let center_control = self.calculate_center_control(board);
let piece_activity = 0.0;
let initiative_advantage = (development_advantage + center_control) / 2.0;
StrategicInsights {
initiative_advantage,
development_advantage,
center_control,
piece_activity,
strategic_recommendation: self.generate_recommendation(initiative_advantage),
}
}
fn calculate_development_advantage(&self, board: &Board) -> f32 {
let mut advantage = 0.0;
let starting_squares = [
(chess::Square::B1, chess::Color::White), (chess::Square::G1, chess::Color::White),
(chess::Square::C1, chess::Color::White), (chess::Square::F1, chess::Color::White),
(chess::Square::B8, chess::Color::Black), (chess::Square::G8, chess::Color::Black),
(chess::Square::C8, chess::Color::Black), (chess::Square::F8, chess::Color::Black),
];
for (square, color) in starting_squares {
if board.piece_on(square).is_none() {
match color {
chess::Color::White => advantage += 0.1,
chess::Color::Black => advantage -= 0.1,
}
}
}
advantage
}
fn calculate_center_control(&self, board: &Board) -> f32 {
let mut control = 0.0;
let center_squares = [chess::Square::D4, chess::Square::D5, chess::Square::E4, chess::Square::E5];
for square in center_squares {
if let Some(piece) = board.piece_on(square) {
if piece == chess::Piece::Pawn {
match board.color_on(square) {
Some(chess::Color::White) => control += 0.2,
Some(chess::Color::Black) => control -= 0.2,
None => {}
}
}
}
}
control
}
fn calculate_piece_activity(&self, _board: &Board) -> f32 {
0.0 }
fn generate_recommendation(&self, initiative_advantage: f32) -> String {
if initiative_advantage > 0.2 {
"Maintain aggressive stance, capitalize on initiative".to_string()
} else if initiative_advantage < -0.2 {
"Consolidate position, seek counterplay opportunities".to_string()
} else {
"Balanced position, seek gradual improvements".to_string()
}
}
}
pub struct EvaluationBlender {
tactical_weight: f32,
similarity_weight: f32,
strategic_weight: f32,
}
impl EvaluationBlender {
pub fn new() -> Self {
Self {
tactical_weight: 0.6, similarity_weight: 0.25, strategic_weight: 0.15, }
}
pub fn blend_all(
&self,
tactical_eval: f32,
similarity: &SimilarityInsights,
strategic: &StrategicInsights,
) -> f32 {
let mut final_eval = tactical_eval * self.tactical_weight;
if similarity.confidence > 0.5 {
final_eval += similarity.suggested_evaluation * self.similarity_weight;
}
final_eval += strategic.initiative_advantage * self.strategic_weight;
final_eval
}
}
#[derive(Debug, Clone)]
pub struct SimilarityInsights {
pub similar_positions: Vec<SearchResult>,
pub suggested_evaluation: f32,
pub confidence: f32,
}
#[derive(Debug, Clone)]
pub struct StrategicInsights {
pub initiative_advantage: f32,
pub development_advantage: f32,
pub center_control: f32,
pub piece_activity: f32,
pub strategic_recommendation: String,
}
#[derive(Debug)]
struct InitiativeFactors {
development_weight: f32,
center_weight: f32,
activity_weight: f32,
}
impl Default for InitiativeFactors {
fn default() -> Self {
Self {
development_weight: 0.4,
center_weight: 0.3,
activity_weight: 0.3,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use chess::Board;
use std::str::FromStr;
#[test]
fn test_core_evaluator_basic() {
let mut evaluator = CoreEvaluator::new();
let board = Board::default();
evaluator.learn_from_position(&board, 0.0);
let result = evaluator.evaluate_position(&board);
assert!(result.final_evaluation.is_finite());
assert!(result.tactical_component.is_finite());
}
#[test]
fn test_provides_unique_insights() {
let mut evaluator = CoreEvaluator::new();
let positions = [
("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", 0.0),
("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2", 0.0),
];
for (fen, eval) in positions {
let board = Board::from_str(fen).unwrap();
evaluator.learn_from_position(&board, eval);
}
let test_board = Board::from_str("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2").unwrap();
let result = evaluator.evaluate_position(&test_board);
assert!(result.unique_insights_provided);
assert!(!result.similarity_insights.similar_positions.is_empty());
}
}