1use crate::errors::Result;
2use crate::hybrid_evaluation::{EvaluationComponent, StrategicEvaluator};
3use crate::utils::cache::EvaluationCache;
4use chess::{Board, ChessMove, Color, Piece, Rank, Square};
5use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7
8pub struct StrategicInitiativeEvaluator {
10 initiative_analyzer: InitiativeAnalyzer,
12 pressure_evaluator: PositionalPressureEvaluator,
14 plan_generator: StrategicPlanGenerator,
16 time_analyzer: TimePressureAnalyzer,
18 initiative_cache: Arc<EvaluationCache>,
20}
21
22impl StrategicInitiativeEvaluator {
23 pub fn new() -> Self {
25 Self {
26 initiative_analyzer: InitiativeAnalyzer::new(),
27 pressure_evaluator: PositionalPressureEvaluator::new(),
28 plan_generator: StrategicPlanGenerator::new(),
29 time_analyzer: TimePressureAnalyzer::new(),
30 initiative_cache: Arc::new(EvaluationCache::new(
31 5000,
32 std::time::Duration::from_secs(600),
33 )),
34 }
35 }
36
37 pub fn evaluate_strategic_initiative(
39 &self,
40 board: &Board,
41 ) -> Result<StrategicInitiativeResult> {
42 let fen = board.to_string();
43
44 if let Some(cached_eval) = self.initiative_cache.get_evaluation(&fen) {
46 return Ok(StrategicInitiativeResult {
47 initiative_score: cached_eval,
48 white_initiative: 0.0,
49 black_initiative: 0.0,
50 positional_pressure: PositionalPressure::default(),
51 strategic_plans: Vec::new(),
52 time_pressure: TimePressure::default(),
53 initiative_factors: InitiativeFactors::default(),
54 from_cache: true,
55 });
56 }
57
58 let start_time = std::time::Instant::now();
59
60 let white_initiative = self
62 .initiative_analyzer
63 .analyze_initiative(board, Color::White)?;
64 let black_initiative = self
65 .initiative_analyzer
66 .analyze_initiative(board, Color::Black)?;
67
68 let positional_pressure = self.pressure_evaluator.evaluate_pressure(board)?;
70
71 let strategic_plans = self.plan_generator.generate_plans(board, 3)?;
73
74 let time_pressure = self.time_analyzer.analyze_time_factors(board)?;
76
77 let initiative_score = white_initiative.total_score - black_initiative.total_score;
79
80 let result = StrategicInitiativeResult {
81 initiative_score,
82 white_initiative: white_initiative.total_score,
83 black_initiative: black_initiative.total_score,
84 positional_pressure,
85 strategic_plans,
86 time_pressure,
87 initiative_factors: InitiativeFactors {
88 white: white_initiative,
89 black: black_initiative,
90 },
91 from_cache: false,
92 };
93
94 self.initiative_cache
96 .store_evaluation(&fen, initiative_score);
97
98 Ok(result)
99 }
100
101 pub fn get_statistics(&self) -> StrategicInitiativeStats {
103 let cache_stats = self.initiative_cache.stats();
104
105 StrategicInitiativeStats {
106 evaluations_performed: 0, cache_hit_ratio: cache_stats.hit_ratio,
108 average_evaluation_time_ms: 0.0, plans_generated: 0, }
111 }
112}
113
114impl Default for StrategicInitiativeEvaluator {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120impl StrategicEvaluator for StrategicInitiativeEvaluator {
121 fn evaluate_position(&self, board: &Board) -> Result<EvaluationComponent> {
122 let start_time = std::time::Instant::now();
123 let initiative_result = self.evaluate_strategic_initiative(board)?;
124 let computation_time = start_time.elapsed().as_millis() as u64;
125
126 let mut additional_info = HashMap::new();
127 additional_info.insert(
128 "white_initiative".to_string(),
129 initiative_result.white_initiative,
130 );
131 additional_info.insert(
132 "black_initiative".to_string(),
133 initiative_result.black_initiative,
134 );
135 additional_info.insert(
136 "space_advantage".to_string(),
137 initiative_result.positional_pressure.space_advantage,
138 );
139 additional_info.insert(
140 "development_advantage".to_string(),
141 initiative_result.positional_pressure.development_advantage,
142 );
143 additional_info.insert(
144 "attacking_potential".to_string(),
145 initiative_result.positional_pressure.attacking_potential,
146 );
147 additional_info.insert(
148 "strategic_plans_count".to_string(),
149 initiative_result.strategic_plans.len() as f32,
150 );
151
152 let confidence = self.compute_initiative_confidence(&initiative_result);
154
155 Ok(EvaluationComponent {
156 evaluation: initiative_result.initiative_score,
157 confidence,
158 computation_time_ms: computation_time,
159 additional_info,
160 })
161 }
162}
163
164impl StrategicInitiativeEvaluator {
165 fn compute_initiative_confidence(&self, result: &StrategicInitiativeResult) -> f32 {
166 let mut confidence_factors = Vec::new();
167
168 let initiative_clarity = result.initiative_score.abs().min(1.0);
170 confidence_factors.push(initiative_clarity);
171
172 let plan_confidence = (result.strategic_plans.len() as f32 / 5.0).min(1.0);
174 confidence_factors.push(plan_confidence);
175
176 let pressure_consistency = (result.positional_pressure.space_advantage.abs()
178 + result.positional_pressure.development_advantage.abs()
179 + result.positional_pressure.attacking_potential.abs())
180 / 3.0;
181 confidence_factors.push(pressure_consistency.min(1.0));
182
183 let time_clarity = if result.time_pressure.critical_moves.is_empty() {
185 0.7
186 } else {
187 0.9
188 };
189 confidence_factors.push(time_clarity);
190
191 if confidence_factors.is_empty() {
193 0.5
194 } else {
195 confidence_factors.iter().sum::<f32>() / confidence_factors.len() as f32
196 }
197 }
198}
199
200pub struct InitiativeAnalyzer {
202 cached_analyses: RwLock<HashMap<String, ColorInitiativeAnalysis>>,
203}
204
205impl InitiativeAnalyzer {
206 pub fn new() -> Self {
207 Self {
208 cached_analyses: RwLock::new(HashMap::new()),
209 }
210 }
211
212 pub fn analyze_initiative(
214 &self,
215 board: &Board,
216 color: Color,
217 ) -> Result<ColorInitiativeAnalysis> {
218 let cache_key = format!("{}_{:?}", board.to_string(), color);
219
220 if let Ok(cache) = self.cached_analyses.read() {
222 if let Some(analysis) = cache.get(&cache_key) {
223 return Ok(analysis.clone());
224 }
225 }
226
227 let mut analysis = ColorInitiativeAnalysis::default();
228
229 analysis.development_initiative = self.analyze_development_initiative(board, color);
231
232 analysis.attacking_initiative = self.analyze_attacking_initiative(board, color);
234
235 analysis.space_initiative = self.analyze_space_initiative(board, color);
237
238 analysis.tempo_initiative = self.analyze_tempo_initiative(board, color);
240
241 analysis.pawn_initiative = self.analyze_pawn_initiative(board, color);
243
244 analysis.coordination_initiative = self.analyze_coordination_initiative(board, color);
246
247 analysis.total_score = analysis.development_initiative * 0.2
249 + analysis.attacking_initiative * 0.25
250 + analysis.space_initiative * 0.15
251 + analysis.tempo_initiative * 0.1
252 + analysis.pawn_initiative * 0.15
253 + analysis.coordination_initiative * 0.15;
254
255 if let Ok(mut cache) = self.cached_analyses.write() {
257 if cache.len() > 1000 {
258 let keys_to_remove: Vec<_> = cache.keys().take(500).cloned().collect();
260 for key in keys_to_remove {
261 cache.remove(&key);
262 }
263 }
264 cache.insert(cache_key, analysis.clone());
265 }
266
267 Ok(analysis)
268 }
269
270 fn analyze_development_initiative(&self, board: &Board, color: Color) -> f32 {
271 let pieces = board.color_combined(color);
272 let mut development_score = 0.0;
273
274 let developed_knights = (board.pieces(Piece::Knight) & pieces)
276 .into_iter()
277 .filter(|&square| self.is_piece_developed(square, Piece::Knight, color))
278 .count();
279
280 let developed_bishops = (board.pieces(Piece::Bishop) & pieces)
281 .into_iter()
282 .filter(|&square| self.is_piece_developed(square, Piece::Bishop, color))
283 .count();
284
285 development_score += developed_knights as f32 * 0.3;
286 development_score += developed_bishops as f32 * 0.3;
287
288 if board.castle_rights(color).has_kingside() || board.castle_rights(color).has_queenside() {
290 development_score += 0.2;
291 }
292
293 let central_squares = [Square::E4, Square::E5, Square::D4, Square::D5];
295 let central_control = central_squares
296 .iter()
297 .filter(|&&square| self.attacks_square(board, color, square))
298 .count();
299
300 development_score += central_control as f32 * 0.1;
301
302 development_score.min(1.0)
303 }
304
305 fn analyze_attacking_initiative(&self, board: &Board, color: Color) -> f32 {
306 let opponent_color = !color;
307 let opponent_king_square = board.king_square(opponent_color);
308 let mut attacking_score = 0.0;
309
310 let king_attackers = self.count_attackers_near_king(board, color, opponent_king_square);
312 attacking_score += king_attackers as f32 * 0.2;
313
314 let active_pieces = self.count_active_pieces_in_enemy_territory(board, color);
316 attacking_score += active_pieces as f32 * 0.15;
317
318 let threats = self.count_tactical_threats(board, color);
320 attacking_score += threats as f32 * 0.25;
321
322 let weak_squares_controlled = self.count_weak_squares_controlled(board, color);
324 attacking_score += weak_squares_controlled as f32 * 0.1;
325
326 attacking_score.min(1.0)
327 }
328
329 fn analyze_space_initiative(&self, board: &Board, color: Color) -> f32 {
330 let mut space_score = 0.0;
331
332 let squares_controlled = self.count_controlled_squares(board, color);
334 space_score += (squares_controlled as f32 / 64.0) * 0.5;
335
336 let advanced_pawns = self.count_advanced_pawns(board, color);
338 space_score += advanced_pawns as f32 * 0.1;
339
340 let outposts = self.count_outposts(board, color);
342 space_score += outposts as f32 * 0.2;
343
344 space_score.min(1.0)
345 }
346
347 fn analyze_tempo_initiative(&self, board: &Board, color: Color) -> f32 {
348 let mut tempo_score = 0.0;
349
350 if board.side_to_move() == color {
352 tempo_score += 0.1;
353 }
354
355 let forcing_moves = self.count_forcing_moves(board, color);
357 tempo_score += forcing_moves as f32 * 0.05;
358
359 let pieces_under_attack = self.count_opponent_pieces_under_attack(board, color);
361 tempo_score += pieces_under_attack as f32 * 0.1;
362
363 tempo_score.min(1.0)
364 }
365
366 fn analyze_pawn_initiative(&self, board: &Board, color: Color) -> f32 {
367 let mut pawn_score = 0.0;
368
369 let passed_pawns = self.count_passed_pawns(board, color);
371 pawn_score += passed_pawns as f32 * 0.2;
372
373 let pawn_chains = self.count_pawn_chains(board, color);
375 pawn_score += pawn_chains as f32 * 0.1;
376
377 let pawn_storms = self.evaluate_pawn_storms(board, color);
379 pawn_score += pawn_storms * 0.3;
380
381 pawn_score.min(1.0)
382 }
383
384 fn analyze_coordination_initiative(&self, board: &Board, color: Color) -> f32 {
385 let mut coordination_score = 0.0;
386
387 let coordinated_pieces = self.count_coordinated_pieces(board, color);
389 coordination_score += coordinated_pieces as f32 * 0.1;
390
391 let batteries = self.count_batteries(board, color);
393 coordination_score += batteries as f32 * 0.2;
394
395 let harmony_score = self.evaluate_piece_harmony(board, color);
397 coordination_score += harmony_score * 0.3;
398
399 coordination_score.min(1.0)
400 }
401
402 fn is_piece_developed(&self, _square: Square, _piece: Piece, _color: Color) -> bool {
404 true
406 }
407
408 fn attacks_square(&self, _board: &Board, _color: Color, _square: Square) -> bool {
409 false
411 }
412
413 fn count_attackers_near_king(&self, _board: &Board, _color: Color, _king_square: Square) -> u8 {
414 0
416 }
417
418 fn count_active_pieces_in_enemy_territory(&self, _board: &Board, _color: Color) -> u8 {
419 0
421 }
422
423 fn count_tactical_threats(&self, _board: &Board, _color: Color) -> u8 {
424 0
426 }
427
428 fn count_weak_squares_controlled(&self, _board: &Board, _color: Color) -> u8 {
429 0
431 }
432
433 fn count_controlled_squares(&self, _board: &Board, _color: Color) -> u8 {
434 20
436 }
437
438 fn count_advanced_pawns(&self, board: &Board, color: Color) -> u8 {
439 let pawns = board.pieces(Piece::Pawn) & board.color_combined(color);
440 let mut count = 0;
441
442 for square in pawns {
443 let rank = square.get_rank();
444 let advanced = match color {
445 Color::White => rank >= Rank::Fifth,
446 Color::Black => rank <= Rank::Fourth,
447 };
448
449 if advanced {
450 count += 1;
451 }
452 }
453
454 count
455 }
456
457 fn count_outposts(&self, _board: &Board, _color: Color) -> u8 {
458 0
460 }
461
462 fn count_forcing_moves(&self, _board: &Board, _color: Color) -> u8 {
463 0
465 }
466
467 fn count_opponent_pieces_under_attack(&self, _board: &Board, _color: Color) -> u8 {
468 0
470 }
471
472 fn count_passed_pawns(&self, _board: &Board, _color: Color) -> u8 {
473 0
475 }
476
477 fn count_pawn_chains(&self, _board: &Board, _color: Color) -> u8 {
478 0
480 }
481
482 fn evaluate_pawn_storms(&self, _board: &Board, _color: Color) -> f32 {
483 0.0
485 }
486
487 fn count_coordinated_pieces(&self, _board: &Board, _color: Color) -> u8 {
488 0
490 }
491
492 fn count_batteries(&self, _board: &Board, _color: Color) -> u8 {
493 0
495 }
496
497 fn evaluate_piece_harmony(&self, _board: &Board, _color: Color) -> f32 {
498 0.0
500 }
501}
502
503pub struct PositionalPressureEvaluator;
505
506impl PositionalPressureEvaluator {
507 pub fn new() -> Self {
508 Self
509 }
510
511 pub fn evaluate_pressure(&self, board: &Board) -> Result<PositionalPressure> {
512 let mut pressure = PositionalPressure::default();
513
514 pressure.space_advantage = self.evaluate_space_advantage(board);
516
517 pressure.development_advantage = self.evaluate_development_advantage(board);
519
520 pressure.attacking_potential = self.evaluate_attacking_potential(board);
522
523 pressure.positional_weaknesses = self.evaluate_positional_weaknesses(board);
525
526 Ok(pressure)
527 }
528
529 fn evaluate_space_advantage(&self, _board: &Board) -> f32 {
530 0.0
532 }
533
534 fn evaluate_development_advantage(&self, _board: &Board) -> f32 {
535 0.0
537 }
538
539 fn evaluate_attacking_potential(&self, _board: &Board) -> f32 {
540 0.0
542 }
543
544 fn evaluate_positional_weaknesses(&self, _board: &Board) -> f32 {
545 0.0
547 }
548}
549
550pub struct StrategicPlanGenerator;
552
553impl StrategicPlanGenerator {
554 pub fn new() -> Self {
555 Self
556 }
557
558 pub fn generate_plans(&self, board: &Board, max_plans: usize) -> Result<Vec<StrategicPlan>> {
559 let mut plans = Vec::new();
560
561 plans.extend(self.generate_attacking_plans(board)?);
563 plans.extend(self.generate_positional_plans(board)?);
564 plans.extend(self.generate_endgame_plans(board)?);
565
566 plans.sort_by(|a, b| {
568 b.priority
569 .partial_cmp(&a.priority)
570 .unwrap_or(std::cmp::Ordering::Equal)
571 });
572 plans.truncate(max_plans);
573
574 Ok(plans)
575 }
576
577 fn generate_attacking_plans(&self, _board: &Board) -> Result<Vec<StrategicPlan>> {
578 Ok(vec![StrategicPlan {
580 plan_type: StrategicPlanType::KingsideAttack,
581 priority: 0.8,
582 key_moves: Vec::new(),
583 target_squares: Vec::new(),
584 expected_outcome: PlanOutcome::MaterialGain,
585 }])
586 }
587
588 fn generate_positional_plans(&self, _board: &Board) -> Result<Vec<StrategicPlan>> {
589 Ok(vec![StrategicPlan {
591 plan_type: StrategicPlanType::CentralControl,
592 priority: 0.6,
593 key_moves: Vec::new(),
594 target_squares: Vec::new(),
595 expected_outcome: PlanOutcome::PositionalAdvantage,
596 }])
597 }
598
599 fn generate_endgame_plans(&self, _board: &Board) -> Result<Vec<StrategicPlan>> {
600 Ok(Vec::new())
602 }
603}
604
605pub struct TimePressureAnalyzer;
607
608impl TimePressureAnalyzer {
609 pub fn new() -> Self {
610 Self
611 }
612
613 pub fn analyze_time_factors(&self, board: &Board) -> Result<TimePressure> {
614 let mut time_pressure = TimePressure::default();
615
616 time_pressure.critical_moves = self.identify_critical_moves(board)?;
618
619 time_pressure.zugzwang_potential = self.evaluate_zugzwang_potential(board);
621
622 time_pressure.tempo_importance = self.evaluate_tempo_importance(board);
624
625 Ok(time_pressure)
626 }
627
628 fn identify_critical_moves(&self, _board: &Board) -> Result<Vec<ChessMove>> {
629 Ok(Vec::new())
631 }
632
633 fn evaluate_zugzwang_potential(&self, _board: &Board) -> f32 {
634 0.0
636 }
637
638 fn evaluate_tempo_importance(&self, _board: &Board) -> f32 {
639 0.5
641 }
642}
643
644#[derive(Debug, Clone)]
646pub struct StrategicInitiativeResult {
647 pub initiative_score: f32,
649 pub white_initiative: f32,
651 pub black_initiative: f32,
653 pub positional_pressure: PositionalPressure,
655 pub strategic_plans: Vec<StrategicPlan>,
657 pub time_pressure: TimePressure,
659 pub initiative_factors: InitiativeFactors,
661 pub from_cache: bool,
663}
664
665#[derive(Debug, Clone, Default)]
667pub struct ColorInitiativeAnalysis {
668 pub development_initiative: f32,
669 pub attacking_initiative: f32,
670 pub space_initiative: f32,
671 pub tempo_initiative: f32,
672 pub pawn_initiative: f32,
673 pub coordination_initiative: f32,
674 pub total_score: f32,
675}
676
677#[derive(Debug, Clone, Default)]
679pub struct InitiativeFactors {
680 pub white: ColorInitiativeAnalysis,
681 pub black: ColorInitiativeAnalysis,
682}
683
684#[derive(Debug, Clone, Default)]
686pub struct PositionalPressure {
687 pub space_advantage: f32,
688 pub development_advantage: f32,
689 pub attacking_potential: f32,
690 pub positional_weaknesses: f32,
691}
692
693#[derive(Debug, Clone)]
695pub struct StrategicPlan {
696 pub plan_type: StrategicPlanType,
697 pub priority: f32,
698 pub key_moves: Vec<ChessMove>,
699 pub target_squares: Vec<Square>,
700 pub expected_outcome: PlanOutcome,
701}
702
703#[derive(Debug, Clone)]
705pub enum StrategicPlanType {
706 KingsideAttack,
707 QueensideAttack,
708 CentralControl,
709 PawnStorm,
710 PieceManeuver,
711 EndgameTransition,
712}
713
714#[derive(Debug, Clone)]
716pub enum PlanOutcome {
717 MaterialGain,
718 PositionalAdvantage,
719 KingAttack,
720 Promotion,
721 Draw,
722}
723
724#[derive(Debug, Clone, Default)]
726pub struct TimePressure {
727 pub critical_moves: Vec<ChessMove>,
728 pub zugzwang_potential: f32,
729 pub tempo_importance: f32,
730}
731
732#[derive(Debug, Clone)]
734pub struct StrategicInitiativeStats {
735 pub evaluations_performed: u64,
736 pub cache_hit_ratio: f64,
737 pub average_evaluation_time_ms: f64,
738 pub plans_generated: u64,
739}
740
741#[cfg(test)]
742mod tests {
743 use super::*;
744 use chess::Board;
745 use std::str::FromStr;
746
747 #[test]
748 fn test_strategic_initiative_evaluator() {
749 let evaluator = StrategicInitiativeEvaluator::new();
750 let board =
751 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
752
753 let result = evaluator.evaluate_strategic_initiative(&board).unwrap();
754 assert!(result.initiative_score.is_finite());
755 assert!(result.white_initiative >= 0.0);
756 assert!(result.black_initiative >= 0.0);
757 assert!(!result.from_cache);
758 }
759
760 #[test]
761 fn test_initiative_analyzer() {
762 let analyzer = InitiativeAnalyzer::new();
763 let board =
764 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
765
766 let white_analysis = analyzer.analyze_initiative(&board, Color::White).unwrap();
767 let black_analysis = analyzer.analyze_initiative(&board, Color::Black).unwrap();
768
769 assert!(white_analysis.total_score.is_finite());
770 assert!(black_analysis.total_score.is_finite());
771 assert!(white_analysis.development_initiative >= 0.0);
772 assert!(black_analysis.development_initiative >= 0.0);
773 }
774
775 #[test]
776 fn test_strategic_evaluator_trait() {
777 let evaluator = StrategicInitiativeEvaluator::new();
778 let board =
779 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
780
781 let evaluation = evaluator.evaluate_position(&board).unwrap();
782 assert!(evaluation.evaluation.is_finite());
783 assert!(evaluation.confidence >= 0.0 && evaluation.confidence <= 1.0);
784 assert!(evaluation.computation_time_ms > 0);
785 assert!(!evaluation.additional_info.is_empty());
786 }
787
788 #[test]
789 fn test_strategic_plan_generator() {
790 let generator = StrategicPlanGenerator::new();
791 let board =
792 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
793
794 let plans = generator.generate_plans(&board, 3).unwrap();
795 assert!(plans.len() <= 3);
796
797 for plan in &plans {
798 assert!(plan.priority >= 0.0 && plan.priority <= 1.0);
799 }
800 }
801
802 #[test]
803 fn test_positional_pressure_evaluator() {
804 let evaluator = PositionalPressureEvaluator::new();
805 let board =
806 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
807
808 let pressure = evaluator.evaluate_pressure(&board).unwrap();
809 assert!(pressure.space_advantage.is_finite());
810 assert!(pressure.development_advantage.is_finite());
811 assert!(pressure.attacking_potential.is_finite());
812 assert!(pressure.positional_weaknesses.is_finite());
813 }
814
815 #[test]
816 fn test_time_pressure_analyzer() {
817 let analyzer = TimePressureAnalyzer::new();
818 let board =
819 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
820
821 let time_pressure = analyzer.analyze_time_factors(&board).unwrap();
822 assert!(time_pressure.zugzwang_potential >= 0.0);
823 assert!(time_pressure.tempo_importance >= 0.0);
824 }
825}