1use crate::strategic_evaluator::{StrategicEvaluator, StrategicConfig, StrategicEvaluation};
8use crate::utils::lazy_motifs::{LazyStrategicDatabase, LazyLoadConfig, LazyLoadStats};
9use crate::strategic_motifs::MotifMatch;
10use crate::strategic_motifs::{GamePhase, MotifType};
11use chess::{Board, ChessMove};
12use std::path::Path;
13use std::sync::Arc;
14
15pub struct LazyStrategicEvaluator {
17 core_evaluator: StrategicEvaluator,
19 motif_database: Arc<LazyStrategicDatabase>,
21 lazy_config: LazyLoadConfig,
23 use_motifs: bool,
25}
26
27#[derive(Debug, Clone)]
29pub struct EnhancedStrategicEvaluation {
30 pub core_evaluation: StrategicEvaluation,
32 pub motif_matches: Vec<MotifMatch>,
34 pub motif_adjustment: f32,
36 pub motif_confidence: f32,
38 pub detected_phase: GamePhase,
40}
41
42impl LazyStrategicEvaluator {
43 pub fn new<P: AsRef<Path>>(
45 config: StrategicConfig,
46 motif_database_path: P,
47 lazy_config: LazyLoadConfig,
48 ) -> Result<Self, Box<dyn std::error::Error>> {
49 let core_evaluator = StrategicEvaluator::new(config);
50
51 let motif_database = match LazyStrategicDatabase::new(motif_database_path, lazy_config.clone()) {
52 Ok(db) => Arc::new(db),
53 Err(e) => {
54 eprintln!("Warning: Failed to load motif database: {}", e);
56 return Ok(Self {
57 core_evaluator,
58 motif_database: Arc::new(Self::create_empty_database()?),
59 lazy_config,
60 use_motifs: false,
61 });
62 }
63 };
64
65 Ok(Self {
66 core_evaluator,
67 motif_database,
68 lazy_config,
69 use_motifs: true,
70 })
71 }
72
73 fn create_empty_database() -> Result<LazyStrategicDatabase, Box<dyn std::error::Error>> {
75 use std::env;
76 use crate::utils::lazy_motifs::MotifSegmentBuilder;
77
78 let temp_dir_path = env::temp_dir().join("chess_empty_motifs");
80 std::fs::create_dir_all(&temp_dir_path)?;
81
82 let config = LazyLoadConfig::default();
83
84 let builder = MotifSegmentBuilder::new(&temp_dir_path, config.clone());
86 let _index = builder.create_segments(Vec::new(), 100)?;
87
88 LazyStrategicDatabase::new(&temp_dir_path, config)
89 }
90
91 pub fn evaluate_position(&self, board: &Board) -> Result<EnhancedStrategicEvaluation, Box<dyn std::error::Error>> {
93 let core_evaluation = self.core_evaluator.evaluate_strategic(board);
95
96 if !self.use_motifs {
97 return Ok(EnhancedStrategicEvaluation {
98 core_evaluation,
99 motif_matches: Vec::new(),
100 motif_adjustment: 0.0,
101 motif_confidence: 0.0,
102 detected_phase: GamePhase::Any,
103 });
104 }
105
106 let detected_phase = self.detect_game_phase(board);
108
109 let motif_matches = self.motif_database.evaluate_position(board)?;
111
112 let (motif_adjustment, motif_confidence) = self.calculate_motif_adjustments(&motif_matches);
114
115 Ok(EnhancedStrategicEvaluation {
116 core_evaluation,
117 motif_matches,
118 motif_adjustment,
119 motif_confidence,
120 detected_phase,
121 })
122 }
123
124 pub fn evaluate_move(&self, board: &Board, chess_move: &ChessMove) -> Result<f32, Box<dyn std::error::Error>> {
126 let mut new_board = *board;
128 new_board = new_board.make_move_new(*chess_move);
129
130 let evaluation = self.evaluate_position(&new_board)?;
132
133 let total_adjustment = evaluation.core_evaluation.total_evaluation + evaluation.motif_adjustment;
135
136 Ok(total_adjustment)
137 }
138
139 pub fn preload_phase_motifs(&self, phase: GamePhase) -> Result<usize, Box<dyn std::error::Error>> {
141 if !self.use_motifs {
142 return Ok(0);
143 }
144
145 self.motif_database.preload_phase(phase)
146 }
147
148 pub fn find_motifs_by_type(&self, _motif_type: &MotifType) -> Result<Vec<MotifMatch>, Box<dyn std::error::Error>> {
150 if !self.use_motifs {
151 return Ok(Vec::new());
152 }
153
154 Ok(Vec::new())
157 }
158
159 pub fn analyze_strategic_themes(&self, board: &Board) -> Result<StrategicThemeAnalysis, Box<dyn std::error::Error>> {
161 let evaluation = self.evaluate_position(board)?;
162
163 let mut themes = StrategicThemeAnalysis::new();
164
165 for motif_match in &evaluation.motif_matches {
167 match &motif_match.motif.motif_type {
168 MotifType::PawnStructure(_) => {
169 themes.pawn_themes.push(motif_match.clone());
170 }
171 MotifType::PieceCoordination(_) => {
172 themes.piece_coordination_themes.push(motif_match.clone());
173 }
174 MotifType::KingSafety(_) => {
175 themes.king_safety_themes.push(motif_match.clone());
176 }
177 MotifType::Initiative(_) => {
178 themes.initiative_themes.push(motif_match.clone());
179 }
180 MotifType::Endgame(_) => {
181 themes.endgame_themes.push(motif_match.clone());
182 }
183 MotifType::Opening(_) => {
184 themes.opening_themes.push(motif_match.clone());
185 }
186 }
187 }
188
189 Ok(themes)
190 }
191
192 pub fn get_lazy_load_stats(&self) -> LazyLoadStats {
194 self.motif_database.get_stats()
195 }
196
197 pub fn clear_motif_caches(&self) {
199 self.motif_database.clear_caches();
200 }
201
202 pub fn total_available_motifs(&self) -> usize {
204 self.motif_database.total_motifs()
205 }
206
207 pub fn cached_motifs_count(&self) -> usize {
209 self.motif_database.cached_motifs()
210 }
211
212 fn detect_game_phase(&self, board: &Board) -> GamePhase {
214 let piece_count = board.combined().popcnt();
215
216 if piece_count > 28 {
217 GamePhase::Opening
218 } else if piece_count > 12 {
219 GamePhase::Middlegame
220 } else {
221 GamePhase::Endgame
222 }
223 }
224
225 fn calculate_motif_adjustments(&self, motif_matches: &[MotifMatch]) -> (f32, f32) {
227 if motif_matches.is_empty() {
228 return (0.0, 0.0);
229 }
230
231 let mut total_adjustment = 0.0;
232 let mut total_confidence = 0.0;
233 let mut weight_sum = 0.0;
234
235 for motif_match in motif_matches {
236 let weight = motif_match.relevance * motif_match.motif.confidence;
237 total_adjustment += motif_match.motif.evaluation * weight;
238 total_confidence += motif_match.motif.confidence * weight;
239 weight_sum += weight;
240 }
241
242 if weight_sum > 0.0 {
243 total_adjustment /= weight_sum;
244 total_confidence /= weight_sum;
245 }
246
247 total_adjustment = total_adjustment.clamp(-1.0, 1.0);
249 total_confidence = total_confidence.clamp(0.0, 1.0);
250
251 (total_adjustment, total_confidence)
252 }
253}
254
255#[derive(Debug, Clone, Default)]
257pub struct StrategicThemeAnalysis {
258 pub pawn_themes: Vec<MotifMatch>,
259 pub piece_coordination_themes: Vec<MotifMatch>,
260 pub king_safety_themes: Vec<MotifMatch>,
261 pub initiative_themes: Vec<MotifMatch>,
262 pub endgame_themes: Vec<MotifMatch>,
263 pub opening_themes: Vec<MotifMatch>,
264}
265
266impl StrategicThemeAnalysis {
267 pub fn new() -> Self {
268 Self::default()
269 }
270
271 pub fn primary_theme(&self) -> Option<&MotifMatch> {
273 let all_themes: Vec<&MotifMatch> = [
274 &self.pawn_themes,
275 &self.piece_coordination_themes,
276 &self.king_safety_themes,
277 &self.initiative_themes,
278 &self.endgame_themes,
279 &self.opening_themes,
280 ]
281 .iter()
282 .flat_map(|themes| themes.iter())
283 .collect();
284
285 all_themes.into_iter().max_by(|a, b| {
286 a.relevance.partial_cmp(&b.relevance).unwrap_or(std::cmp::Ordering::Equal)
287 })
288 }
289
290 pub fn theme_count(&self) -> usize {
292 self.pawn_themes.len()
293 + self.piece_coordination_themes.len()
294 + self.king_safety_themes.len()
295 + self.initiative_themes.len()
296 + self.endgame_themes.len()
297 + self.opening_themes.len()
298 }
299
300 pub fn get_recommendations(&self) -> Vec<String> {
302 let mut recommendations = Vec::new();
303
304 if let Some(primary) = self.primary_theme() {
305 recommendations.push(primary.motif.description.clone());
306 }
307
308 if !self.pawn_themes.is_empty() {
310 recommendations.push("Consider pawn structure improvements".to_string());
311 }
312
313 if !self.king_safety_themes.is_empty() {
314 recommendations.push("Focus on king safety measures".to_string());
315 }
316
317 if !self.initiative_themes.is_empty() {
318 recommendations.push("Maintain initiative and create pressure".to_string());
319 }
320
321 recommendations
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use chess::Board;
329 use std::str::FromStr;
330 use std::env;
331
332 #[test]
333 fn test_lazy_strategic_evaluator_creation() {
334 let temp_dir_path = env::temp_dir().join("chess_test_lazy");
335 std::fs::create_dir_all(&temp_dir_path).unwrap();
336 let config = StrategicConfig::default();
337 let lazy_config = LazyLoadConfig::default();
338
339 let evaluator = LazyStrategicEvaluator::new(config, &temp_dir_path, lazy_config);
340
341 assert!(evaluator.is_ok());
343 }
344
345 #[test]
346 fn test_game_phase_detection() {
347 let temp_dir_path = env::temp_dir().join("chess_test_phase");
348 std::fs::create_dir_all(&temp_dir_path).unwrap();
349 let config = StrategicConfig::default();
350 let lazy_config = LazyLoadConfig::default();
351
352 let evaluator = LazyStrategicEvaluator::new(config, &temp_dir_path, lazy_config).unwrap();
353
354 let board = Board::default();
356 let phase = evaluator.detect_game_phase(&board);
357 matches!(phase, GamePhase::Opening);
358
359 let endgame_fen = "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1";
361 let endgame_board = Board::from_str(endgame_fen).unwrap();
362 let endgame_phase = evaluator.detect_game_phase(&endgame_board);
363 matches!(endgame_phase, GamePhase::Endgame);
364 }
365
366 #[test]
367 fn test_strategic_theme_analysis() {
368 let analysis = StrategicThemeAnalysis::new();
369 assert_eq!(analysis.theme_count(), 0);
370
371 let recommendations = analysis.get_recommendations();
372 assert!(recommendations.is_empty());
373 }
374}