1use crate::strategic_motifs::*;
9use crate::ChessVectorEngine;
10use chess::{BitBoard, Board, Color, File, Piece, Square, ALL_FILES};
11use std::collections::HashMap;
12
13#[derive(Debug, Clone)]
15pub struct StrategicPattern {
16 pub pattern_type: SimplePatternType,
17 pub strength: f32,
18 pub reliability: f32,
19}
20
21#[derive(Debug, Clone)]
23pub enum SimplePatternType {
24 MaterialImbalance,
25 KingSafety,
26 CenterControl,
27 PieceActivity,
28}
29
30pub struct MotifExtractor {
32 min_eval_significance: f32,
34 min_pattern_frequency: usize,
36 confidence_threshold: f32,
38 extracted_motifs: Vec<StrategicMotif>,
40 pattern_counts: HashMap<u64, PatternOccurrence>,
42 }
45
46#[derive(Debug, Clone)]
48struct PatternOccurrence {
49 count: usize,
50 evaluations: Vec<f32>,
51 positions: Vec<Board>,
52 game_phases: Vec<GamePhase>,
53}
54
55impl MotifExtractor {
56 pub fn new() -> Self {
58 Self {
59 min_eval_significance: 0.1, min_pattern_frequency: 20, confidence_threshold: 0.4, extracted_motifs: Vec::new(),
63 pattern_counts: HashMap::new(),
64 }
66 }
67
68 pub fn extract_from_engine(
70 &mut self,
71 engine: &ChessVectorEngine,
72 ) -> Result<Vec<StrategicMotif>, Box<dyn std::error::Error>> {
73 println!("๐ Starting strategic motif extraction...");
74
75 let total_positions = engine.knowledge_base_size();
76 println!(
77 "๐ Analyzing {} positions for strategic patterns",
78 total_positions
79 );
80
81 if total_positions == 0 {
82 return Err("No positions in engine database to extract from".into());
83 }
84
85 self.analyze_positions(engine)?;
87
88 self.extract_significant_patterns()?;
90
91 self.validate_patterns()?;
93
94 println!(
95 "โ
Extracted {} strategic motifs",
96 self.extracted_motifs.len()
97 );
98 Ok(self.extracted_motifs.clone())
99 }
100
101 fn analyze_positions(
103 &mut self,
104 engine: &ChessVectorEngine,
105 ) -> Result<(), Box<dyn std::error::Error>> {
106 use indicatif::{ProgressBar, ProgressStyle};
107
108 let total_positions = engine.knowledge_base_size();
109 let pb = ProgressBar::new(total_positions as u64);
110 pb.set_style(
111 ProgressStyle::default_bar()
112 .template("๐ Analyzing patterns [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({per_sec}) {msg}")?
113 .progress_chars("โโโ")
114 );
115
116 for i in 0..total_positions {
118 if let (Some(board), Some(evaluation)) = (
119 engine.get_board_by_index(i),
120 engine.get_evaluation_by_index(i),
121 ) {
122 self.analyze_single_position(board, evaluation);
123 }
124
125 if i % 1000 == 0 {
126 pb.set_position(i as u64);
127 pb.set_message(format!("Found {} patterns", self.pattern_counts.len()));
128 }
129 }
130
131 pb.finish_with_message(format!(
132 "โ
Analysis complete: {} unique patterns found",
133 self.pattern_counts.len()
134 ));
135 Ok(())
136 }
137
138 fn analyze_single_position(&mut self, board: &Board, evaluation: f32) {
140 let game_phase = pattern_utils::determine_game_phase(board);
141
142 let strategic_patterns = self.generate_simple_patterns(board, evaluation);
144
145 for pattern in strategic_patterns {
147 if pattern.strength.abs() > 0.1 && pattern.reliability > 0.6 {
149 let pattern_hash = self.generate_pattern_hash(&pattern);
150 self.record_pattern_occurrence(
151 pattern_hash,
152 board,
153 evaluation,
154 &game_phase,
155 );
156 }
157 }
158 }
159
160 fn generate_simple_patterns(&self, board: &Board, evaluation: f32) -> Vec<StrategicPattern> {
162 let mut patterns = Vec::new();
163
164 let material_balance = self.calculate_material_balance(board);
166 if material_balance.abs() > 1 {
167 patterns.push(StrategicPattern {
168 pattern_type: SimplePatternType::MaterialImbalance,
169 strength: material_balance as f32 * 0.1,
170 reliability: 0.8,
171 });
172 }
173
174 if self.is_king_exposed(board, chess::Color::White) || self.is_king_exposed(board, chess::Color::Black) {
176 patterns.push(StrategicPattern {
177 pattern_type: SimplePatternType::KingSafety,
178 strength: evaluation.signum() * 0.3,
179 reliability: 0.7,
180 });
181 }
182
183 patterns
184 }
185
186 fn is_king_exposed(&self, board: &Board, color: chess::Color) -> bool {
188 let king_square = board.king_square(color);
189 let rank = king_square.get_rank().to_index();
191 rank == 0 || rank == 7
192 }
193
194 fn calculate_material_balance(&self, board: &Board) -> i32 {
196 let mut balance = 0;
197 let piece_values = [1, 3, 3, 5, 9, 0]; for square in chess::ALL_SQUARES {
200 if let Some(piece) = board.piece_on(square) {
201 let value = piece_values[piece as usize];
202 match board.color_on(square) {
203 Some(Color::White) => balance += value,
204 Some(Color::Black) => balance -= value,
205 None => {}
206 }
207 }
208 }
209 balance
210 }
211
212 fn extract_pawn_patterns(&mut self, board: &Board, evaluation: f32, game_phase: &GamePhase) {
214 for color in [Color::White, Color::Black] {
216 let isolated_pawns = self.find_isolated_pawns(board, color);
217 for (file, _square) in isolated_pawns {
218 let pattern_hash = self.hash_isolated_pawn_pattern(file, color);
219 self.record_pattern_occurrence(pattern_hash, board, evaluation, game_phase);
220 }
221 }
222
223 for color in [Color::White, Color::Black] {
225 let passed_pawns = self.find_passed_pawns(board, color);
226 for (square, advancement) in passed_pawns {
227 let pattern_hash = self.hash_passed_pawn_pattern(square, color, advancement);
228 self.record_pattern_occurrence(pattern_hash, board, evaluation, game_phase);
229 }
230 }
231
232 for color in [Color::White, Color::Black] {
234 let doubled_pawns = self.find_doubled_pawns(board, color);
235 for (file, count) in doubled_pawns {
236 let pattern_hash = self.hash_doubled_pawn_pattern(file, color, count);
237 self.record_pattern_occurrence(pattern_hash, board, evaluation, game_phase);
238 }
239 }
240 }
241
242 fn extract_piece_patterns(&mut self, board: &Board, evaluation: f32, game_phase: &GamePhase) {
244 for color in [Color::White, Color::Black] {
246 let outposts = self.find_knight_outposts(board, color);
247 for (square, strength) in outposts {
248 let pattern_hash = self.hash_knight_outpost_pattern(square, color, strength);
249 self.record_pattern_occurrence(pattern_hash, board, evaluation, game_phase);
250 }
251 }
252
253 for color in [Color::White, Color::Black] {
255 if self.has_bishop_pair(board, color) {
256 let open_diagonals = self.count_open_diagonals(board, color);
257 let pattern_hash = self.hash_bishop_pair_pattern(color, open_diagonals);
258 self.record_pattern_occurrence(pattern_hash, board, evaluation, game_phase);
259 }
260 }
261
262 for color in [Color::White, Color::Black] {
264 let rook_patterns = self.analyze_rook_activity(board, color);
265 for (pattern_type, square) in rook_patterns {
266 let pattern_hash = self.hash_rook_pattern(pattern_type, square, color);
267 self.record_pattern_occurrence(pattern_hash, board, evaluation, game_phase);
268 }
269 }
270 }
271
272 fn extract_king_safety_patterns(
274 &mut self,
275 board: &Board,
276 evaluation: f32,
277 game_phase: &GamePhase,
278 ) {
279 for color in [Color::White, Color::Black] {
280 let king_square = board.king_square(color);
281
282 let castling_safety = self.evaluate_castling_safety(board, color, king_square);
284 if castling_safety.is_some() {
285 let pattern_hash =
286 self.hash_king_safety_pattern(king_square, color, castling_safety.unwrap());
287 self.record_pattern_occurrence(pattern_hash, board, evaluation, game_phase);
288 }
289
290 let shield_pattern = self.analyze_pawn_shield(board, color, king_square);
292 let pattern_hash = self.hash_pawn_shield_pattern(king_square, color, shield_pattern);
293 self.record_pattern_occurrence(pattern_hash, board, evaluation, game_phase);
294 }
295 }
296
297 fn extract_initiative_patterns(
299 &mut self,
300 board: &Board,
301 evaluation: f32,
302 game_phase: &GamePhase,
303 ) {
304 for color in [Color::White, Color::Black] {
306 let space_value = self.calculate_space_advantage(board, color);
307 if space_value.abs() > 0.2 {
308 let pattern_hash = self.hash_space_pattern(color, space_value);
310 self.record_pattern_occurrence(pattern_hash, board, evaluation, game_phase);
311 }
312 }
313
314 if matches!(game_phase, GamePhase::Opening) {
316 for color in [Color::White, Color::Black] {
317 let development_score = self.calculate_development_score(board, color);
318 let pattern_hash = self.hash_development_pattern(color, development_score);
319 self.record_pattern_occurrence(pattern_hash, board, evaluation, game_phase);
320 }
321 }
322 }
323
324 fn record_pattern_occurrence(
326 &mut self,
327 pattern_hash: u64,
328 board: &Board,
329 evaluation: f32,
330 game_phase: &GamePhase,
331 ) {
332 let occurrence =
333 self.pattern_counts
334 .entry(pattern_hash)
335 .or_insert_with(|| PatternOccurrence {
336 count: 0,
337 evaluations: Vec::new(),
338 positions: Vec::new(),
339 game_phases: Vec::new(),
340 });
341
342 occurrence.count += 1;
343 occurrence.evaluations.push(evaluation);
344 occurrence.positions.push(*board);
345 occurrence.game_phases.push(game_phase.clone());
346 }
347
348
349 fn generate_pattern_hash(&self, pattern: &StrategicPattern) -> u64 {
351 use std::collections::hash_map::DefaultHasher;
352 use std::hash::{Hash, Hasher};
353
354 let mut hasher = DefaultHasher::new();
355
356 match &pattern.pattern_type {
358 SimplePatternType::MaterialImbalance => {
359 "material_imbalance".hash(&mut hasher);
360 }
361 SimplePatternType::KingSafety => {
362 "king_safety".hash(&mut hasher);
363 }
364 SimplePatternType::CenterControl => {
365 "center_control".hash(&mut hasher);
366 }
367 SimplePatternType::PieceActivity => {
368 "piece_activity".hash(&mut hasher);
369 }
370 }
371
372 let strength_bucket = ((pattern.strength + 2.0) * 10.0) as i32; strength_bucket.hash(&mut hasher);
375
376 hasher.finish()
377 }
378
379 fn extract_significant_patterns(&mut self) -> Result<(), Box<dyn std::error::Error>> {
381 println!("๐ฏ Extracting significant strategic patterns...");
382 println!(
383 " ๐ Found {} unique patterns to analyze",
384 self.pattern_counts.len()
385 );
386
387 let mut motif_id = 1u64;
388 let mut frequency_filtered = 0;
389 let mut significance_filtered = 0;
390 let mut confidence_filtered = 0;
391
392 for (pattern_hash, occurrence) in &self.pattern_counts {
393 if occurrence.count < self.min_pattern_frequency {
394 frequency_filtered += 1;
395 continue;
396 }
397
398 let avg_evaluation =
400 occurrence.evaluations.iter().sum::<f32>() / occurrence.evaluations.len() as f32;
401 let _eval_std_dev = self.calculate_std_dev(&occurrence.evaluations, avg_evaluation);
402
403 if avg_evaluation.abs() < self.min_eval_significance {
405 significance_filtered += 1;
406 continue;
407 }
408
409 let confidence = self.calculate_pattern_confidence(occurrence);
411 if confidence < self.confidence_threshold {
412 confidence_filtered += 1;
413 continue;
414 }
415
416 let motif = StrategicMotif {
418 id: motif_id,
419 pattern_hash: *pattern_hash,
420 motif_type: self.infer_motif_type(&occurrence.positions[0], *pattern_hash),
421 evaluation: avg_evaluation,
422 context: self.determine_context(&occurrence.game_phases, &occurrence.evaluations),
423 confidence,
424 master_games: self.create_game_references(&occurrence.positions),
425 description: format!(
426 "Strategic pattern {} (avg eval: {:.2})",
427 motif_id, avg_evaluation
428 ),
429 };
430
431 self.extracted_motifs.push(motif);
432 motif_id += 1;
433 }
434
435 println!(
436 "๐ Extracted {} statistically significant patterns",
437 self.extracted_motifs.len()
438 );
439 println!(" ๐ซ Filtered out:");
440 println!(
441 " - {} patterns (frequency < {})",
442 frequency_filtered, self.min_pattern_frequency
443 );
444 println!(
445 " - {} patterns (significance < {:.1})",
446 significance_filtered, self.min_eval_significance
447 );
448 println!(
449 " - {} patterns (confidence < {:.1})",
450 confidence_filtered, self.confidence_threshold
451 );
452 Ok(())
453 }
454
455 fn validate_patterns(&mut self) -> Result<(), Box<dyn std::error::Error>> {
457 println!("โ
Validating extracted patterns...");
458
459 let original_count = self.extracted_motifs.len();
461
462 self.extracted_motifs.retain(|motif| {
463 motif.confidence >= self.confidence_threshold
465 && self
466 .pattern_counts
467 .get(&motif.pattern_hash)
468 .map(|occ| occ.count >= self.min_pattern_frequency)
469 .unwrap_or(false)
470 });
471
472 let removed_count = original_count - self.extracted_motifs.len();
473 if removed_count > 0 {
474 println!("๐งน Removed {} low-quality patterns", removed_count);
475 }
476
477 println!(
478 "โจ Validation complete: {} high-quality strategic motifs ready",
479 self.extracted_motifs.len()
480 );
481 Ok(())
482 }
483
484 fn find_isolated_pawns(&self, board: &Board, color: Color) -> Vec<(File, Square)> {
487 let mut isolated = Vec::new();
488 let pawns = board.pieces(Piece::Pawn) & board.color_combined(color);
489
490 for square in pawns {
491 let file = square.get_file();
492 let adjacent_files = [
493 if file != File::A {
494 Some(File::from_index(file.to_index() - 1))
495 } else {
496 None
497 },
498 if file != File::H {
499 Some(File::from_index(file.to_index() + 1))
500 } else {
501 None
502 },
503 ];
504
505 let is_isolated = adjacent_files.iter().filter_map(|&f| f).all(|adj_file| {
506 let mut has_adjacent_pawn = false;
508 for rank in chess::ALL_RANKS {
509 let square = Square::make_square(rank, adj_file);
510 if (pawns & BitBoard::from_square(square)) != BitBoard(0) {
511 has_adjacent_pawn = true;
512 break;
513 }
514 }
515 !has_adjacent_pawn
516 });
517
518 if is_isolated {
519 isolated.push((file, square));
520 }
521 }
522
523 isolated
524 }
525
526 fn find_passed_pawns(&self, _board: &Board, _color: Color) -> Vec<(Square, f32)> {
527 Vec::new()
529 }
530
531 fn find_doubled_pawns(&self, board: &Board, color: Color) -> Vec<(File, u8)> {
532 let mut doubled = Vec::new();
533 let pawns = board.pieces(Piece::Pawn) & board.color_combined(color);
534
535 for file in ALL_FILES {
536 let mut count = 0u8;
537 for rank in chess::ALL_RANKS {
538 let square = Square::make_square(rank, file);
539 if (pawns & BitBoard::from_square(square)) != BitBoard(0) {
540 count += 1;
541 }
542 }
543 if count > 1 {
544 doubled.push((file, count));
545 }
546 }
547
548 doubled
549 }
550
551 fn find_knight_outposts(&self, _board: &Board, _color: Color) -> Vec<(Square, f32)> {
552 Vec::new()
554 }
555
556 fn has_bishop_pair(&self, board: &Board, color: Color) -> bool {
557 let bishops = board.pieces(Piece::Bishop) & board.color_combined(color);
558 bishops.count() >= 2
559 }
560
561 fn count_open_diagonals(&self, _board: &Board, _color: Color) -> u8 {
562 0
564 }
565
566 fn analyze_rook_activity(&self, _board: &Board, _color: Color) -> Vec<(String, Square)> {
567 Vec::new()
569 }
570
571 fn evaluate_castling_safety(
572 &self,
573 _board: &Board,
574 _color: Color,
575 _king_square: Square,
576 ) -> Option<f32> {
577 Some(0.0)
579 }
580
581 fn analyze_pawn_shield(&self, _board: &Board, _color: Color, _king_square: Square) -> u8 {
582 0
584 }
585
586 fn calculate_space_advantage(&self, _board: &Board, _color: Color) -> f32 {
587 0.0
589 }
590
591 fn calculate_development_score(&self, _board: &Board, _color: Color) -> f32 {
592 0.0
594 }
595
596 fn calculate_std_dev(&self, values: &[f32], mean: f32) -> f32 {
597 if values.len() <= 1 {
598 return 0.0;
599 }
600
601 let variance =
602 values.iter().map(|v| (v - mean).powi(2)).sum::<f32>() / (values.len() - 1) as f32;
603
604 variance.sqrt()
605 }
606
607 fn calculate_pattern_confidence(&self, occurrence: &PatternOccurrence) -> f32 {
608 let avg_eval =
610 occurrence.evaluations.iter().sum::<f32>() / occurrence.evaluations.len() as f32;
611 let std_dev = self.calculate_std_dev(&occurrence.evaluations, avg_eval);
612
613 let frequency_factor = (occurrence.count as f32 / 100.0).min(1.0); let consistency_factor = (1.0 - (std_dev / 2.0).min(1.0)).max(0.0);
616
617 (frequency_factor + consistency_factor) / 2.0
618 }
619
620 fn infer_motif_type(&self, _board: &Board, _pattern_hash: u64) -> MotifType {
621 MotifType::PawnStructure(PawnPattern::IsolatedPawn {
623 file_index: 3, is_white: true,
625 weakness_value: 0.3,
626 })
627 }
628
629 fn determine_context(
630 &self,
631 game_phases: &[GamePhase],
632 _evaluations: &[f32],
633 ) -> StrategicContext {
634 let mut phase_counts = HashMap::new();
636 for phase in game_phases {
637 *phase_counts.entry(format!("{:?}", phase)).or_insert(0) += 1;
638 }
639
640 let most_common_phase = phase_counts
641 .into_iter()
642 .max_by_key(|(_, count)| *count)
643 .map(|(phase, _)| phase)
644 .unwrap_or_else(|| "Any".to_string());
645
646 let game_phase = match most_common_phase.as_str() {
647 "Opening" => GamePhase::Opening,
648 "Middlegame" => GamePhase::Middlegame,
649 "Endgame" => GamePhase::Endgame,
650 _ => GamePhase::Any,
651 };
652
653 StrategicContext {
654 game_phase,
655 material_context: MaterialContext::Any,
656 min_ply: 1,
657 max_ply: None,
658 }
659 }
660
661 fn create_game_references(&self, positions: &[Board]) -> Vec<GameReference> {
662 positions
664 .iter()
665 .take(3)
666 .enumerate()
667 .map(|(i, _)| GameReference {
668 game_id: format!("extracted_{}", i),
669 white: "Master".to_string(),
670 black: "Player".to_string(),
671 result: "1-0".to_string(),
672 ply: 20,
673 rating: Some(2500),
674 })
675 .collect()
676 }
677
678 fn hash_isolated_pawn_pattern(&self, file: File, color: Color) -> u64 {
680 let mut hash = 0x1234567890abcdefu64;
681 hash ^= file.to_index() as u64;
682 hash ^= if color == Color::White { 1 } else { 0 };
683 hash
684 }
685
686 fn hash_passed_pawn_pattern(&self, square: Square, color: Color, advancement: f32) -> u64 {
687 let mut hash = 0x2345678901bcdef0u64;
688 hash ^= square.to_index() as u64;
689 hash ^= if color == Color::White { 1 } else { 0 };
690 hash ^= (advancement * 1000.0) as u64;
691 hash
692 }
693
694 fn hash_doubled_pawn_pattern(&self, file: File, color: Color, count: u8) -> u64 {
695 let mut hash = 0x3456789012cdef01u64;
696 hash ^= file.to_index() as u64;
697 hash ^= if color == Color::White { 1 } else { 0 };
698 hash ^= count as u64;
699 hash
700 }
701
702 fn hash_knight_outpost_pattern(&self, square: Square, color: Color, strength: f32) -> u64 {
703 let mut hash = 0x456789013def012u64;
704 hash ^= square.to_index() as u64;
705 hash ^= if color == Color::White { 1 } else { 0 };
706 hash ^= (strength * 1000.0) as u64;
707 hash
708 }
709
710 fn hash_bishop_pair_pattern(&self, color: Color, open_diagonals: u8) -> u64 {
711 let mut hash = 0x56789014ef0123u64;
712 hash ^= if color == Color::White { 1 } else { 0 };
713 hash ^= open_diagonals as u64;
714 hash
715 }
716
717 fn hash_rook_pattern(&self, pattern_type: String, square: Square, color: Color) -> u64 {
718 let mut hash = 0x6789015f01234u64;
719 hash ^= square.to_index() as u64;
720 hash ^= if color == Color::White { 1 } else { 0 };
721 hash ^= pattern_type.len() as u64;
722 hash
723 }
724
725 fn hash_king_safety_pattern(
726 &self,
727 king_square: Square,
728 color: Color,
729 safety_value: f32,
730 ) -> u64 {
731 let mut hash = 0x789016012345u64;
732 hash ^= king_square.to_index() as u64;
733 hash ^= if color == Color::White { 1 } else { 0 };
734 hash ^= (safety_value * 1000.0) as u64;
735 hash
736 }
737
738 fn hash_pawn_shield_pattern(
739 &self,
740 king_square: Square,
741 color: Color,
742 shield_pattern: u8,
743 ) -> u64 {
744 let mut hash = 0x89017123456u64;
745 hash ^= king_square.to_index() as u64;
746 hash ^= if color == Color::White { 1 } else { 0 };
747 hash ^= shield_pattern as u64;
748 hash
749 }
750
751 fn hash_space_pattern(&self, color: Color, space_value: f32) -> u64 {
752 let mut hash = 0x9018234567u64;
753 hash ^= if color == Color::White { 1 } else { 0 };
754 hash ^= (space_value * 1000.0) as u64;
755 hash
756 }
757
758 fn hash_development_pattern(&self, color: Color, development_score: f32) -> u64 {
759 let mut hash = 0xa019345678u64;
760 hash ^= if color == Color::White { 1 } else { 0 };
761 hash ^= (development_score * 1000.0) as u64;
762 hash
763 }
764}